SDL: testffmpeg: added support for EGL OES frame formats

From 41b7036f37297cc64d8692a599c0670d1c7a3e46 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Tue, 12 Mar 2024 16:12:08 -0700
Subject: [PATCH] testffmpeg: added support for EGL OES frame formats

---
 test/testffmpeg.c | 214 +++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 211 insertions(+), 3 deletions(-)

diff --git a/test/testffmpeg.c b/test/testffmpeg.c
index 45ddfdb7152d7..fa17af91443ab 100644
--- a/test/testffmpeg.c
+++ b/test/testffmpeg.c
@@ -46,6 +46,21 @@
 #endif
 #endif
 
+#define DRM_FORMAT_MOD_VENDOR_NONE    0
+#define DRM_FORMAT_RESERVED	      ((1ULL << 56) - 1)
+
+#define fourcc_mod_get_vendor(modifier) \
+	(((modifier) >> 56) & 0xff)
+
+#define fourcc_mod_is_vendor(modifier, vendor) \
+	(fourcc_mod_get_vendor(modifier) == DRM_FORMAT_MOD_VENDOR_## vendor)
+
+#define fourcc_mod_code(vendor, val) \
+	((((__u64)DRM_FORMAT_MOD_VENDOR_## vendor) << 56) | ((val) & 0x00ffffffffffffffULL))
+
+#define DRM_FORMAT_MOD_INVALID  fourcc_mod_code(NONE, DRM_FORMAT_RESERVED)
+#define DRM_FORMAT_MOD_LINEAR   fourcc_mod_code(NONE, 0)
+
 #ifdef SDL_PLATFORM_APPLE
 #include <CoreVideo/CoreVideo.h>
 #endif
@@ -635,6 +650,189 @@ static SDL_bool GetTextureForMemoryFrame(AVFrame *frame, SDL_Texture **texture)
     return SDL_TRUE;
 }
 
+#ifdef HAVE_EGL
+
+static SDL_bool GetOESTextureForDRMFrame(AVFrame *frame, SDL_Texture **texture)
+{
+    AVHWFramesContext *frames = (AVHWFramesContext *)(frame->hw_frames_ctx->data);
+    const AVDRMFrameDescriptor *desc = (const AVDRMFrameDescriptor *)frame->data[0];
+    int i, j, k, image_index;
+    EGLDisplay display = eglGetCurrentDisplay();
+    SDL_PropertiesID props;
+    GLuint textureID;
+    EGLAttrib attr[64];
+    SDL_Colorspace colorspace;
+
+    if (*texture) {
+        /* Free the previous texture now that we're about to render a new one */
+        SDL_DestroyTexture(*texture);
+    }
+
+    props = CreateVideoTextureProperties(frame, SDL_PIXELFORMAT_EXTERNAL_OES, SDL_TEXTUREACCESS_STATIC);
+    *texture = SDL_CreateTextureWithProperties(renderer, props);
+    SDL_DestroyProperties(props);
+    if (!*texture) {
+        return SDL_FALSE;
+    }
+    SDL_SetTextureBlendMode(*texture, SDL_BLENDMODE_NONE);
+    SDL_SetTextureScaleMode(*texture, SDL_SCALEMODE_LINEAR);
+
+    props = SDL_GetTextureProperties(*texture);
+    textureID = (GLuint)SDL_GetNumberProperty(props, SDL_PROP_TEXTURE_OPENGLES2_TEXTURE_NUMBER, 0);
+    if (!textureID) {
+        SDL_SetError("Couldn't get OpenGL texture");
+        return SDL_FALSE;
+    }
+    colorspace = (SDL_Colorspace)SDL_GetNumberProperty(props, SDL_PROP_TEXTURE_COLORSPACE_NUMBER, SDL_COLORSPACE_UNKNOWN);
+
+    /* import the frame into OpenGL */
+    k = 0;
+    attr[k++] = EGL_LINUX_DRM_FOURCC_EXT;
+    attr[k++] = desc->layers[0].format;
+    attr[k++] = EGL_WIDTH;
+    attr[k++] = frames->width;
+    attr[k++] = EGL_HEIGHT;
+    attr[k++] = frames->height;
+    image_index = 0;
+    for (i = 0; i < desc->nb_layers; ++i) {
+        const AVDRMLayerDescriptor *layer = &desc->layers[i];
+        for (j = 0; j < layer->nb_planes; ++j) {
+            const AVDRMPlaneDescriptor *plane = &layer->planes[j];
+            const AVDRMObjectDescriptor *object = &desc->objects[plane->object_index];
+
+            switch (image_index) {
+            case 0:
+                attr[k++] = EGL_DMA_BUF_PLANE0_FD_EXT;
+                attr[k++] = object->fd;
+                attr[k++] = EGL_DMA_BUF_PLANE0_OFFSET_EXT;
+                attr[k++] = plane->offset;
+                attr[k++] = EGL_DMA_BUF_PLANE0_PITCH_EXT;
+                attr[k++] = plane->pitch;
+                if (has_EGL_EXT_image_dma_buf_import_modifiers && object->format_modifier != DRM_FORMAT_MOD_INVALID) {
+                    attr[k++] = EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT;
+                    attr[k++] = (object->format_modifier & 0xFFFFFFFF);
+                    attr[k++] = EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT;
+                    attr[k++] = (object->format_modifier >> 32);
+                }
+                break;
+            case 1:
+                attr[k++] = EGL_DMA_BUF_PLANE1_FD_EXT;
+                attr[k++] = object->fd;
+                attr[k++] = EGL_DMA_BUF_PLANE1_OFFSET_EXT;
+                attr[k++] = plane->offset;
+                attr[k++] = EGL_DMA_BUF_PLANE1_PITCH_EXT;
+                attr[k++] = plane->pitch;
+                if (has_EGL_EXT_image_dma_buf_import_modifiers && object->format_modifier != DRM_FORMAT_MOD_INVALID) {
+                    attr[k++] = EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT;
+                    attr[k++] = (object->format_modifier & 0xFFFFFFFF);
+                    attr[k++] = EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT;
+                    attr[k++] = (object->format_modifier >> 32);
+                }
+                break;
+            case 2:
+                attr[k++] = EGL_DMA_BUF_PLANE2_FD_EXT;
+                attr[k++] = object->fd;
+                attr[k++] = EGL_DMA_BUF_PLANE2_OFFSET_EXT;
+                attr[k++] = plane->offset;
+                attr[k++] = EGL_DMA_BUF_PLANE2_PITCH_EXT;
+                attr[k++] = plane->pitch;
+                if (has_EGL_EXT_image_dma_buf_import_modifiers && object->format_modifier != DRM_FORMAT_MOD_INVALID) {
+                    attr[k++] = EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT;
+                    attr[k++] = (object->format_modifier & 0xFFFFFFFF);
+                    attr[k++] = EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT;
+                    attr[k++] = (object->format_modifier >> 32);
+                }
+                break;
+            case 3:
+                attr[k++] = EGL_DMA_BUF_PLANE3_FD_EXT;
+                attr[k++] = object->fd;
+                attr[k++] = EGL_DMA_BUF_PLANE3_OFFSET_EXT;
+                attr[k++] = plane->offset;
+                attr[k++] = EGL_DMA_BUF_PLANE3_PITCH_EXT;
+                attr[k++] = plane->pitch;
+                if (has_EGL_EXT_image_dma_buf_import_modifiers && object->format_modifier != DRM_FORMAT_MOD_INVALID) {
+                    attr[k++] = EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT;
+                    attr[k++] = (object->format_modifier & 0xFFFFFFFF);
+                    attr[k++] = EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT;
+                    attr[k++] = (object->format_modifier >> 32);
+                }
+                break;
+
+            default:
+                break;
+            }
+            ++image_index;
+        }
+    }
+
+    switch (SDL_COLORSPACEPRIMARIES(colorspace)) {
+    case SDL_COLOR_PRIMARIES_BT601:
+    case SDL_COLOR_PRIMARIES_SMPTE240:
+        attr[k++] = EGL_YUV_COLOR_SPACE_HINT_EXT;
+        attr[k++] = EGL_ITU_REC601_EXT;
+        break;
+    case SDL_COLOR_PRIMARIES_BT709:
+        attr[k++] = EGL_YUV_COLOR_SPACE_HINT_EXT;
+        attr[k++] = EGL_ITU_REC709_EXT;
+        break;
+    case SDL_COLOR_PRIMARIES_BT2020:
+        attr[k++] = EGL_YUV_COLOR_SPACE_HINT_EXT;
+        attr[k++] = EGL_ITU_REC2020_EXT;
+    default:
+        break;
+    }
+
+    switch (SDL_COLORSPACERANGE(colorspace)) {
+    case SDL_COLOR_RANGE_FULL:
+        attr[k++] = EGL_SAMPLE_RANGE_HINT_EXT;
+        attr[k++] = EGL_YUV_FULL_RANGE_EXT;
+        break;
+    case SDL_COLOR_RANGE_LIMITED:
+    default:
+        attr[k++] = EGL_SAMPLE_RANGE_HINT_EXT;
+        attr[k++] = EGL_YUV_NARROW_RANGE_EXT;
+        break;
+    }
+
+    switch (SDL_COLORSPACECHROMA(colorspace)) {
+    case SDL_CHROMA_LOCATION_LEFT:
+        attr[k++] = EGL_YUV_CHROMA_HORIZONTAL_SITING_HINT_EXT;
+        attr[k++] = EGL_YUV_CHROMA_SITING_0_EXT;
+        attr[k++] = EGL_YUV_CHROMA_VERTICAL_SITING_HINT_EXT;
+        attr[k++] = EGL_YUV_CHROMA_SITING_0_5_EXT;
+        break;
+    case SDL_CHROMA_LOCATION_CENTER:
+        attr[k++] = EGL_YUV_CHROMA_HORIZONTAL_SITING_HINT_EXT;
+        attr[k++] = EGL_YUV_CHROMA_SITING_0_5_EXT;
+        attr[k++] = EGL_YUV_CHROMA_VERTICAL_SITING_HINT_EXT;
+        attr[k++] = EGL_YUV_CHROMA_SITING_0_5_EXT;
+        break;
+    case SDL_CHROMA_LOCATION_TOPLEFT:
+        attr[k++] = EGL_YUV_CHROMA_HORIZONTAL_SITING_HINT_EXT;
+        attr[k++] = EGL_YUV_CHROMA_SITING_0_EXT;
+        attr[k++] = EGL_YUV_CHROMA_VERTICAL_SITING_HINT_EXT;
+        attr[k++] = EGL_YUV_CHROMA_SITING_0_EXT;
+        break;
+    default:
+        break;
+    }
+
+    SDL_assert(k < SDL_arraysize(attr));
+    attr[k++] = EGL_NONE;
+
+    EGLImage image = eglCreateImage(display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, NULL, attr);
+    if (image == EGL_NO_IMAGE) {
+        SDL_Log("Couldn't create image: %d\n", glGetError());
+        return SDL_FALSE;
+    }
+
+    glActiveTextureARBFunc(GL_TEXTURE0_ARB);
+    glBindTexture(GL_TEXTURE_EXTERNAL_OES, textureID);
+    glEGLImageTargetTexture2DOESFunc(GL_TEXTURE_EXTERNAL_OES, image);
+    return SDL_TRUE;
+}
+#endif // HAVE_EGL
+
 static SDL_bool GetTextureForDRMFrame(AVFrame *frame, SDL_Texture **texture)
 {
 #ifdef HAVE_EGL
@@ -644,6 +842,12 @@ static SDL_bool GetTextureForDRMFrame(AVFrame *frame, SDL_Texture **texture)
     EGLDisplay display = eglGetCurrentDisplay();
     SDL_PropertiesID props;
     GLuint textures[2];
+    uint64_t format_modifier = desc->objects[0].format_modifier;
+
+    if (format_modifier != DRM_FORMAT_MOD_INVALID &&
+        format_modifier != DRM_FORMAT_MOD_LINEAR) {
+        return GetOESTextureForDRMFrame(frame, texture);
+    }
 
     /* FIXME: Assuming NV12 data format */
     num_planes = 0;
@@ -663,7 +867,7 @@ static SDL_bool GetTextureForDRMFrame(AVFrame *frame, SDL_Texture **texture)
         SDL_SetHint("SDL_RENDER_OPENGL_NV12_RG_SHADER", "1");
     }
 
-    props = CreateVideoTextureProperties(frame, SDL_PIXELFORMAT_UNKNOWN, SDL_TEXTUREACCESS_STATIC);
+    props = CreateVideoTextureProperties(frame, SDL_PIXELFORMAT_NV12, SDL_TEXTUREACCESS_STATIC);
     *texture = SDL_CreateTextureWithProperties(renderer, props);
     SDL_DestroyProperties(props);
     if (!*texture) {
@@ -720,11 +924,15 @@ static SDL_bool GetTextureForDRMFrame(AVFrame *frame, SDL_Texture **texture)
 
             attr[k++] = EGL_NONE;
 
-            EGLImage pImage = eglCreateImage(display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, NULL, attr);
+            EGLImage image = eglCreateImage(display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, NULL, attr);
+            if (image == EGL_NO_IMAGE) {
+                SDL_Log("Couldn't create image: %d\n", glGetError());
+                return SDL_FALSE;
+            }
 
             glActiveTextureARBFunc(GL_TEXTURE0_ARB + image_index);
             glBindTexture(GL_TEXTURE_2D, textures[image_index]);
-            glEGLImageTargetTexture2DOESFunc(GL_TEXTURE_2D, pImage);
+            glEGLImageTargetTexture2DOESFunc(GL_TEXTURE_2D, image);
             ++image_index;
         }
     }