libdrmの使い方

2 minute read

libdrmのドキュメントがあまりにも少ないので,使い方を調べるために libdrmdrm-howto のソースコードを解析した。 Linuxのグラフィックを使いこなすにはkernelのdrm,グラフィックデバイスのドライバ,libdrmを理解する必要がある。

参考

DRMの簡単な構成

framebuffer -> CRTC -> Encoder -> Connector -> Display
                 |
               planes

それぞれの勝手な解釈

  • CRTCはCRT Controller
  • ConnectorはHDMIやDisplayPortなど文字通りコネクタ
  • Encoderはframe bufferをVideo信号に変換する仮想デバイス
  • PlaneはFramebufferを含むメモリオブジェクト。デバイスが対応していれば複数持ってoverlayも可能。

libdrmはデバイス依存でnVidia用libdrmとかバリエーションがあるようだ?

drm-howtoソースコード解析

drm-howto のソースコードを順番に解析する。 これでlibdrmの使い方は大体分かる。

modeset_open

まずは main の中の modeset_open から。 /dev/dri/card0 をopenしてfile descriptorを取得。

fd = open(node, O_RDWR | O_CLOEXEC);

DUMB_BUFFERが使えるか確認。

if (drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &has_dumb) < 0 || !has_dumb)

DRM deviceの情報を取得。 CRTC, Connector, Encoderの情報が入っている。

res = drmModeGetResources(fd);

modeset_prepare

すべてのConnectorを調べる。

for (i = 0; i < res->count_connectors; ++i)

Connectorの情報を取得。

conn = drmModeGetConnector(fd, res->connectors[i]);

modeset_setup_dev

Displayが接続されてるか確認。

if (conn->connection != DRM_MODE_CONNECTED)

mode情報をコピーする。

memcpy(&dev->mode, &conn->modes[0], sizeof(dev->mode));

conn->modesstruct drmModeModeInfo でDisplayで使える解像度情報が入っている。 つまりEDIDの情報。 drmModeModeInfohdisplayvdisplay が解像度,vrefresh がフレームレート。

modeset_prepare

現在Connectorに接続しているEncoderとCRTCが使用可能か調べる。

/* first try the currently conected encoder+crtc */
if (conn->encoder_id)
	enc = drmModeGetEncoder(fd, conn->encoder_id);
else
	enc = NULL;

現在のEncoder+CRTCが使用不可だった場合は使えるEncoder+CRTCを探す。

for (i = 0; i < conn->count_encoders; ++i)

modeset_create_fb

使用可能なEncoder+CRTCがあったらframe bufferをつくる。

/* create dumb buffer */
...
ret = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq);
...
/* create framebuffer object for the dumb-buffer */
ret = drmModeAddFB(fd, dev->width, dev->height, 32, 32, dev->stride, dev->handle, &dev->fb);
...
/* prepare buffer for memory mapping */
memset(&mreq, 0, sizeof(mreq));
mreq.handle = dev->handle;
ret = drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq);
...
/* perform actual memory mapping */
dev->map = mmap(0, dev->size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, mreq.offset);

再びmain

選択したmodeと作ったframe bufferでCRTCを設定する。

ret = drmModeSetCrtc(fd, iter->crtc, iter->fb, 0, 0, &iter->conn, 1, &iter->mode);

ここまでで描画の準備完了。

modeset_draw 描画

frame bufferにデータを書き込む

modeset_draw (single bufferの場合)

*(uint32_t*)&iter->map[off] = (r << 16) | (g << 8) | b;

modeset_draw (double bufferを使う場合)

frame bufferにデータを書き込み drmModeSetCrtc でframe bufferを切り替える。

ret = drmModeSetCrtc(fd, iter->crtc, buf->fb, 0, 0, &iter->conn, 1, &iter->mode)

modeset_draw (vsyncで同期する場合)

page_flip_handler のevent handlerを使う。

drmHandleEvent(fd, &ev);

page_flip_event が発生したらframebufferにデータを書き込み drmModePageFlip で切り替える。

ret = drmModePageFlip(fd, dev->crtc, buf->fb, DRM_MODE_PAGE_FLIP_EVENT, dev);

グラフィックデバイスのDMAの仕様によると思うが, double bufferを使ってもV同期で切り替わっている気がする。 frame rateやframe切り替わりのタイミングをコントロールしたいときにvsync同期を使うのだろうか?

modeset_cleanup お掃除

設定したCRTCを元の設定にもどす。

/* restore saved CRTC configuration */
drmModeSetCrtc(fd,
               iter->saved_crtc->crtc_id,
               iter->saved_crtc->buffer_id,
               iter->saved_crtc->x,
               iter->saved_crtc->y,
               &iter->conn,
               1,
               &iter->saved_crtc->mode);
drmModeFreeCrtc(iter->saved_crtc);

あとはframe bufferを開放したりデバイスをcloseしたり。

drmModeAddFB2

drmModeAddFB の代わりに drmModeAddFB2 をつかうとYUVやRGBといったpixel_formatの指定ができる。 pixel_format は fourcc codeで指定。forccの定義はlibdrmの <drm/drm_fourcc.h> に書かれている。 指定できるfourccはデバイス依存があるかもしれない。

drmModeAddFB2のprototypeは以下のようになっている。

drm_public int drmModeAddFB2(int fd, uint32_t width, uint32_t height,
 		uint32_t pixel_format, const uint32_t bo_handles[4],
 		const uint32_t pitches[4], const uint32_t offsets[4],
 		uint32_t *buf_id, uint32_t flags)

bo_handles(buffer object),pichesoffsetsは4つあるが,1つしか使わない場合は最初の1個だけ設定すればよいみたい。 2planeや3plane使うforcc指定があり,そのときにbo_handlesを複数指定するようだ。

include/drm/drm_mode.h にbuffer objectを4つ指定できる理由が書いてあるので引用する。

/*
 * In case of planar formats, this ioctl allows up to 4
 * buffer objects with offsets and pitches per plane.
 * The pitch and offset order is dictated by the fourcc,
 * e.g. NV12 (http://fourcc.org/yuv.php#NV12) is described as:
 *
 *   YUV 4:2:0 image with a plane of 8 bit Y samples
 *   followed by an interleaved U/V plane containing
 *   8 bit 2x2 subsampled colour difference samples.
 *
 * So it would consist of Y as offsets[0] and UV as
 * offsets[1].  Note that offsets[0] will generally
 * be 0 (but this is not required).
 *
 * To accommodate tiled, compressed, etc formats, a
 * modifier can be specified.  The default value of zero
 * indicates "native" format as specified by the fourcc.
 * Vendor specific modifier token.  Note that even though
 * it looks like we have a modifier per-plane, we in fact
 * do not. The modifier for each plane must be identical.
 * Thus all combinations of different data layouts for
 * multi plane formats must be enumerated as separate
 * modifiers.
 */

履歴

  • 2019-02-23: 初稿

Tags:

Updated: