Linux
OpenGLES
DRM
EGL

NV12フォーマットのGEM FLINK名からテクスチャを生成する

YUVフォーマット(ここではNV12)のDRMバッファのGEM FLINK名がわかっている状態でそこからテクスチャを生成してみる。

GEM FLINKについては
https://qiita.com/maueki/items/8392decdd36c3fa19117#gem-flink

記載するコードについてはエラー処理は省略している。

EGL, GLの初期化

省略

GBMデバイスオープン

int drv_fd = open("/dev/dri/card0", O_RDWR);
struct gbm_device* gbm = gbm_create_device(drv_fd);

PRIME FD取得

GEM FLINK名はuint32_t nameという変数に格納されているものとする。

struct drm_gem_open gem_open = {};
gem_open.name = name;
drmIoctl(gbm_device_get_fd(gbm), DRM_IOCTL_GEM_OPEN, &gem_open));

struct drm_prime_handle prime_handle = {.handle = gem_open.handle, .flags = 0 /* O_CLOSEXEC */ };
drmIoctl(gbm_device_get_fd(gbm), DRM_IOCTL_PRIME_HANDLE_TO_FD, &prime_handle);

int fd = prime_handle.fd;

fdがPRIME FDとなる

eglImageKHR作成

EGL拡張のEGL_EXT_image_dma_buf_importを使用してPRIME FDからeglImageKHRを取得する。

    EGLint img_attribs[] = {
        EGL_WIDTH,                  <width>,
        EGL_HEIGHT,                 <height>,
        EGL_LINUX_DRM_FOURCC_EXT,   DRM_FORMAT_NV11, /// =0x3231564e,
        EGL_DMA_BUF_PLANE0_FD_EXT,  fd,
        EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0,
        EGL_DMA_BUF_PLANE0_PITCH_EXT, <一行あたりのバイト数(アライメント注意)>,
        EGL_DMA_BUF_PLANE1_FD_EXT,  fd,
        EGL_DMA_BUF_PLANE1_OFFSET_EXT, <PLANE1の開始位置>,
        EGL_DMA_BUF_PLANE1_PITCH_EXT, <一行あたりのバイト数(PLANE0と同じはず)>,
        EGL_NONE
    };

    eglImageKHR eglImage = eglImageKHR(egl_dpy, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, (EGLClientBuffer)0, img_attribs);

PLANEは2まであって、フォーマットに合わせて使い分ける。NV12の場合はYとUVの2面に分かれているのでPLANE0,PLANE1を使用する。
YUVフォーマットについて詳しくは以下参照
http://klabgames.tech.blog.jp.klab.com/archives/1054828175.html

EGL_DMA_BUF_PLANE1_OFFSET_EXTの値は基本的にEGL_DMA_BUF_PLANE0_PITCH_EXT * EGL_HEIGHTのはずだが、アライメント調整されることがあるので注意(自分の環境だと32の倍数である必要があってかなりハマった)

テクスチャ作成

GL拡張のGL_OES_EGL_image_externalを使用してテクスチャを作成する。

glBindTexture(GL_TEXTURE_EXTERNAL_OES, texture_id);
glEglTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, eglImage);

GL_TEXTURE_2DではなくGL_TEXTURE_EXTERNAL_OESを使っていることに注意。

あとは通常のテクスチャ同様に描画すれば内部でNV12 -> RGBの変換を行ってくれる。

描画時の注意

GL_TEXTURE_EXTERNAL_OESを使っているため、sampler2Dは使用できない。
フラグメントシェーダの先頭に

#extension GL_OES_EGL_image_external: require

を記載し、samplerExternalOESを使用する。texture2D()は同様に使用可能。