diff --git a/app/resources.qrc b/app/resources.qrc index 1a553a05..0dc11a53 100644 --- a/app/resources.qrc +++ b/app/resources.qrc @@ -29,6 +29,8 @@ ModeSeven.ttf shaders/egl_nv12.frag shaders/egl_nv12.vert + shaders/egl_opaque.frag + shaders/egl_opaque.vert shaders/egl_overlay.frag shaders/egl_overlay.vert diff --git a/app/shaders/egl_opaque.frag b/app/shaders/egl_opaque.frag new file mode 100644 index 00000000..3c462657 --- /dev/null +++ b/app/shaders/egl_opaque.frag @@ -0,0 +1,12 @@ +#version 300 es +#extension GL_OES_EGL_image_external : require +precision mediump float; +out vec4 FragColor; + +in vec2 vTextCoord; + +uniform samplerExternalOES uTexture; + +void main() { + FragColor = texture2D(uTexture, vTextCoord); +} diff --git a/app/shaders/egl_opaque.vert b/app/shaders/egl_opaque.vert new file mode 100644 index 00000000..bf0848d9 --- /dev/null +++ b/app/shaders/egl_opaque.vert @@ -0,0 +1,10 @@ +#version 300 es + +layout (location = 0) in vec2 aPosition; // 2D: X,Y +layout (location = 1) in vec2 aTexCoord; +out vec2 vTextCoord; + +void main() { + vTextCoord = aTexCoord; + gl_Position = vec4(aPosition, 0, 1); +} diff --git a/app/streaming/video/ffmpeg-renderers/drm.cpp b/app/streaming/video/ffmpeg-renderers/drm.cpp index 70227317..3a3f81ce 100644 --- a/app/streaming/video/ffmpeg-renderers/drm.cpp +++ b/app/streaming/video/ffmpeg-renderers/drm.cpp @@ -26,6 +26,7 @@ DrmRenderer::DrmRenderer() m_CurrentFbId(0) { #ifdef HAVE_EGL + m_EGLExtDmaBuf = false; m_eglCreateImage = nullptr; m_eglCreateImageKHR = nullptr; m_eglDestroyImage = nullptr; @@ -396,7 +397,8 @@ bool DrmRenderer::canExportEGL() { } AVPixelFormat DrmRenderer::getEGLImagePixelFormat() { - return AV_PIX_FMT_NV12; + // This tells EGLRenderer to treat the EGLImage as a single opaque texture + return AV_PIX_FMT_DRM_PRIME; } bool DrmRenderer::initializeEGL(EGLDisplay, @@ -407,6 +409,8 @@ bool DrmRenderer::initializeEGL(EGLDisplay, return false; } + m_EGLExtDmaBuf = ext.isSupported("EGL_EXT_image_dma_buf_import_modifiers"); + // NB: eglCreateImage() and eglCreateImageKHR() have slightly different definitions m_eglCreateImage = (typeof(m_eglCreateImage))eglGetProcAddress("eglCreateImage"); m_eglCreateImageKHR = (typeof(m_eglCreateImageKHR))eglGetProcAddress("eglCreateImageKHR"); @@ -432,54 +436,103 @@ ssize_t DrmRenderer::exportEGLImages(AVFrame *frame, EGLDisplay dpy, SDL_assert(drmFrame->nb_objects == 1); SDL_assert(drmFrame->nb_layers == 1); - SDL_assert(drmFrame->layers[0].nb_planes == 2); + + const int MAX_ATTRIB_COUNT = 30; + EGLAttrib attribs[MAX_ATTRIB_COUNT] = { + EGL_LINUX_DRM_FOURCC_EXT, (EGLAttrib)drmFrame->layers[0].format, + EGL_WIDTH, frame->width, + EGL_HEIGHT, frame->height, + }; + int attribIndex = 6; for (int i = 0; i < drmFrame->layers[0].nb_planes; ++i) { const auto &plane = drmFrame->layers[0].planes[i]; const auto &object = drmFrame->objects[plane.object_index]; - const int EGL_ATTRIB_COUNT = 13; - EGLAttrib attribs[EGL_ATTRIB_COUNT] = { - EGL_LINUX_DRM_FOURCC_EXT, i == 0 ? DRM_FORMAT_R8 : DRM_FORMAT_GR88, - EGL_WIDTH, i == 0 ? frame->width : frame->width / 2, - EGL_HEIGHT, i == 0 ? frame->height : frame->height / 2, - EGL_DMA_BUF_PLANE0_FD_EXT, object.fd, - EGL_DMA_BUF_PLANE0_OFFSET_EXT, plane.offset, - EGL_DMA_BUF_PLANE0_PITCH_EXT, plane.pitch, - EGL_NONE - }; - - if (m_eglCreateImage) { - images[i] = m_eglCreateImage(dpy, EGL_NO_CONTEXT, - EGL_LINUX_DMA_BUF_EXT, - nullptr, attribs); - if (!images[i]) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, - "eglCreateImage() Failed: %d", eglGetError()); - goto fail; + switch (i) { + case 0: + attribs[attribIndex++] = EGL_DMA_BUF_PLANE0_FD_EXT; + attribs[attribIndex++] = object.fd; + attribs[attribIndex++] = EGL_DMA_BUF_PLANE0_OFFSET_EXT; + attribs[attribIndex++] = plane.offset; + attribs[attribIndex++] = EGL_DMA_BUF_PLANE0_PITCH_EXT; + attribs[attribIndex++] = plane.pitch; + if (m_EGLExtDmaBuf && object.format_modifier != DRM_FORMAT_MOD_INVALID) { + attribs[attribIndex++] = EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT; + attribs[attribIndex++] = (EGLint)(object.format_modifier & 0xFFFFFFFF); + attribs[attribIndex++] = EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT; + attribs[attribIndex++] = (EGLint)(object.format_modifier >> 32); } + break; + + case 1: + attribs[attribIndex++] = EGL_DMA_BUF_PLANE1_FD_EXT; + attribs[attribIndex++] = object.fd; + attribs[attribIndex++] = EGL_DMA_BUF_PLANE1_OFFSET_EXT; + attribs[attribIndex++] = plane.offset; + attribs[attribIndex++] = EGL_DMA_BUF_PLANE1_PITCH_EXT; + attribs[attribIndex++] = plane.pitch; + if (m_EGLExtDmaBuf && object.format_modifier != DRM_FORMAT_MOD_INVALID) { + attribs[attribIndex++] = EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT; + attribs[attribIndex++] = (EGLint)(object.format_modifier & 0xFFFFFFFF); + attribs[attribIndex++] = EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT; + attribs[attribIndex++] = (EGLint)(object.format_modifier >> 32); + } + break; + + case 2: + attribs[attribIndex++] = EGL_DMA_BUF_PLANE2_FD_EXT; + attribs[attribIndex++] = object.fd; + attribs[attribIndex++] = EGL_DMA_BUF_PLANE2_OFFSET_EXT; + attribs[attribIndex++] = plane.offset; + attribs[attribIndex++] = EGL_DMA_BUF_PLANE2_PITCH_EXT; + attribs[attribIndex++] = plane.pitch; + if (m_EGLExtDmaBuf && object.format_modifier != DRM_FORMAT_MOD_INVALID) { + attribs[attribIndex++] = EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT; + attribs[attribIndex++] = (EGLint)(object.format_modifier & 0xFFFFFFFF); + attribs[attribIndex++] = EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT; + attribs[attribIndex++] = (EGLint)(object.format_modifier >> 32); + } + break; + + default: + Q_UNREACHABLE(); } - else { - // Cast the EGLAttrib array elements to EGLint for the KHR extension - EGLint intAttribs[EGL_ATTRIB_COUNT]; - for (int i = 0; i < EGL_ATTRIB_COUNT; i++) { - intAttribs[i] = (EGLint)attribs[i]; - } - - images[i] = m_eglCreateImageKHR(dpy, EGL_NO_CONTEXT, - EGL_LINUX_DMA_BUF_EXT, - nullptr, intAttribs); - if (!images[i]) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, - "eglCreateImageKHR() Failed: %d", eglGetError()); - goto fail; - } - } - - ++count; } - return count; + // Terminate the attribute list + attribs[attribIndex++] = EGL_NONE; + SDL_assert(attribIndex <= MAX_ATTRIB_COUNT); + + // Our EGLImages are non-planar, so we only populate the first entry + if (m_eglCreateImage) { + images[0] = m_eglCreateImage(dpy, EGL_NO_CONTEXT, + EGL_LINUX_DMA_BUF_EXT, + nullptr, attribs); + if (!images[0]) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "eglCreateImage() Failed: %d", eglGetError()); + goto fail; + } + } + else { + // Cast the EGLAttrib array elements to EGLint for the KHR extension + EGLint intAttribs[MAX_ATTRIB_COUNT]; + for (int i = 0; i < MAX_ATTRIB_COUNT; i++) { + intAttribs[i] = (EGLint)attribs[i]; + } + + images[0] = m_eglCreateImageKHR(dpy, EGL_NO_CONTEXT, + EGL_LINUX_DMA_BUF_EXT, + nullptr, intAttribs); + if (!images[0]) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "eglCreateImageKHR() Failed: %d", eglGetError()); + goto fail; + } + } + + return 1; fail: freeEGLImages(dpy, images); @@ -487,14 +540,16 @@ fail: } void DrmRenderer::freeEGLImages(EGLDisplay dpy, EGLImage images[EGL_MAX_PLANES]) { - for (size_t i = 0; i < EGL_MAX_PLANES; ++i) { - if (m_eglDestroyImage) { - m_eglDestroyImage(dpy, images[i]); - } - else { - m_eglDestroyImageKHR(dpy, images[i]); - } + if (m_eglDestroyImage) { + m_eglDestroyImage(dpy, images[0]); } + else { + m_eglDestroyImageKHR(dpy, images[0]); + } + + // Our EGLImages are non-planar + SDL_assert(images[1] == 0); + SDL_assert(images[2] == 0); } #endif diff --git a/app/streaming/video/ffmpeg-renderers/drm.h b/app/streaming/video/ffmpeg-renderers/drm.h index 5762a769..c346ec82 100644 --- a/app/streaming/video/ffmpeg-renderers/drm.h +++ b/app/streaming/video/ffmpeg-renderers/drm.h @@ -35,6 +35,7 @@ private: SDL_Rect m_OutputRect; #ifdef HAVE_EGL + bool m_EGLExtDmaBuf; PFNEGLCREATEIMAGEPROC m_eglCreateImage; PFNEGLDESTROYIMAGEPROC m_eglDestroyImage; PFNEGLCREATEIMAGEKHRPROC m_eglCreateImageKHR; diff --git a/app/streaming/video/ffmpeg-renderers/eglvid.cpp b/app/streaming/video/ffmpeg-renderers/eglvid.cpp index 1e25ff85..cf5c1d55 100644 --- a/app/streaming/video/ffmpeg-renderers/eglvid.cpp +++ b/app/streaming/video/ffmpeg-renderers/eglvid.cpp @@ -384,6 +384,14 @@ bool EGLRenderer::compileShaders() { m_ShaderProgramParams[NV12_PARAM_PLANE1] = glGetUniformLocation(m_ShaderProgram, "plane1"); m_ShaderProgramParams[NV12_PARAM_PLANE2] = glGetUniformLocation(m_ShaderProgram, "plane2"); } + else if (m_EGLImagePixelFormat == AV_PIX_FMT_DRM_PRIME) { + m_ShaderProgram = compileShader("egl_opaque.vert", "egl_opaque.frag"); + if (!m_ShaderProgram) { + return false; + } + + m_ShaderProgramParams[OPAQUE_PARAM_TEXTURE] = glGetUniformLocation(m_ShaderProgram, "uTexture"); + } else { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unsupported EGL pixel format: %d", @@ -785,13 +793,16 @@ void EGLRenderer::renderFrame(AVFrame* frame) glUseProgram(m_ShaderProgram); m_glBindVertexArrayOES(m_VAO); - // Bind parameters for the NV12 shaders + // Bind parameters for the shaders if (m_EGLImagePixelFormat == AV_PIX_FMT_NV12) { glUniformMatrix3fv(m_ShaderProgramParams[NV12_PARAM_YUVMAT], 1, GL_FALSE, getColorMatrix()); glUniform3fv(m_ShaderProgramParams[NV12_PARAM_OFFSET], 1, getColorOffsets()); glUniform1i(m_ShaderProgramParams[NV12_PARAM_PLANE1], 0); glUniform1i(m_ShaderProgramParams[NV12_PARAM_PLANE2], 1); } + else if (m_EGLImagePixelFormat == AV_PIX_FMT_DRM_PRIME) { + glUniform1i(m_ShaderProgramParams[OPAQUE_PARAM_TEXTURE], 0); + } glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); diff --git a/app/streaming/video/ffmpeg-renderers/eglvid.h b/app/streaming/video/ffmpeg-renderers/eglvid.h index aff5adb1..2dbcc175 100644 --- a/app/streaming/video/ffmpeg-renderers/eglvid.h +++ b/app/streaming/video/ffmpeg-renderers/eglvid.h @@ -55,6 +55,7 @@ private: #define NV12_PARAM_OFFSET 1 #define NV12_PARAM_PLANE1 2 #define NV12_PARAM_PLANE2 3 +#define OPAQUE_PARAM_TEXTURE 0 int m_ShaderProgramParams[4]; #define OVERLAY_PARAM_TEXTURE 0