Revision control
++ // Copyright (C) 2026 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++ #include "qdmabuftextureimporter_p.h"
++
++ #include <QtMultimedia/private/qeglimagefunctions_p.h>
++ #include <QtMultimedia/private/qmultimedia_gl_support_p.h>
++ #include <QtMultimedia/private/qmultimedia_ranges_p.h>
++ #include <QtMultimedia/private/qvideotexturehelper_p.h>
++
++ #include <QtGui/qopenglcontext.h>
++ #include <QtGui/rhi/qrhi.h>
++ #include <QtCore/qloggingcategory.h>
++
++ #include <EGL/egl.h>
++ #include <EGL/eglext.h>
++
++ #include <array>
++ #include <vector>
++
++ static_assert(QT_CONFIG(linux_dmabuf));
++
++ QT_BEGIN_NAMESPACE
++
++ Q_STATIC_LOGGING_CATEGORY(qLDmaBuf, "qt.multimedia.dmabuf")
++
++ namespace QtMultimediaPrivate {
++
++ DmaBufEglContext::DmaBufEglContext(QRhi *rhi)
++ {
++ if (!rhi || rhi->backend() != QRhi::OpenGLES2) {
++ qWarning() << "DmaBufEglContext: No rhi or non openGL based RHI";
++ return;
++ }
++
++ m_glContext = static_cast<const QRhiGles2NativeHandles *>(rhi->nativeHandles())->context;
++ if (!m_glContext) {
++ qCDebug(qLDmaBuf) << " no GL context, disabling";
++ return;
++ }
++
++ auto *eglContext = m_glContext->nativeInterface<QNativeInterface::QEGLContext>();
++ if (!eglContext) {
++ qCDebug(qLDmaBuf) << " no EGL context, disabling";
++ return;
++ }
++
++ m_eglDisplay = eglContext->display();
++ if (!m_eglDisplay) {
++ qCDebug(qLDmaBuf) << " no egl display, disabling";
++ return;
++ }
++
++ if (!QEglImageFunctions::instance().isValid()) {
++ qCDebug(qLDmaBuf) << " no eglImageTargetTexture2D, disabling";
++ return;
++ }
++
++ m_valid = true;
++ }
++
++ DmaBufTextureHandles::DmaBufTextureHandles(QRhi &rhi, std::vector<GlTextureHandle> textures,
++ QVideoFrameFormat::PixelFormat format,
++ std::shared_ptr<void> parentKeepAlive)
++ : m_parentKeepAlive(std::move(parentKeepAlive)),
++ m_rhi(rhi),
++ m_textures(std::move(textures)),
++ m_texturePixelFormat(format)
++ {
++ }
++
++ DmaBufTextureHandles::~DmaBufTextureHandles()
++ {
++ // m_textures is destroyed right after this body runs, calling glDeleteTextures on each plane.
++ m_rhi.makeThreadLocalNativeContextCurrent();
++ }
++
++ static bool dmaModifierIsNotSupportedByDisplay(const DmaBufPlane &plane, EGLDisplay eglDisplay)
++ {
++ if (plane.modifier == DmaBufFormatModifierInvalid)
++ return true; // no modifier info to check
++
++ auto &eglFunctions = QEglImageFunctions::instance();
++ constexpr bool assumeSupportedWhenUnknown = true;
++ const bool formatIsSupported =
++ eglFunctions.isDmaBufModifierSupported(eglDisplay, plane.drmFormat, plane.modifier)
++ .value_or(assumeSupportedWhenUnknown);
++ return formatIsSupported;
++ }
++
++ q23::expected<QVideoFrameTexturesHandlesUPtr, FailureSeverity>
++ importDmaBufTextures(QRhi &rhi, const DmaBufEglContext &eglContext, QSpan<const DmaBufPlane> planes,
++ QVideoFrameFormat::PixelFormat qtFormat, QSize frameSize,
++ std::shared_ptr<void> parentKeepAlive, DmaBufImportMode importMode)
++ {
++ qCDebug(qLDmaBuf) << "Importing textures from DMA buffer," << qtFormat;
++ Qt::HANDLE eglDisplay = eglContext.eglDisplay();
++ QOpenGLContext *glContext = eglContext.glContext();
++
++ QOpenGLFunctions functions(glContext);
++ const auto &elgImageFunctions = QEglImageFunctions::instance();
++
++ auto *desc = QVideoTextureHelper::textureDescription(qtFormat);
++ int nPlanes = desc->nplanes;
++ Q_ASSERT(planes.size() == nPlanes);
++
++ rhi.makeThreadLocalNativeContextCurrent();
++
++ QVideoFrameFormat::PixelFormat texturePixelFormat = qtFormat;
++ GLenum glTarget = GL_TEXTURE_2D;
++ if (importMode == DmaBufImportMode::SingleImage) {
++ // the driver converts the planes to RGB when sampling
++ texturePixelFormat = QVideoFrameFormat::Format_RGBA8888;
++ #ifdef GL_OES_EGL_image_external
++ if (QVideoTextureHelper::forceGlTextureExternalOesIsSet()) {
++ texturePixelFormat = QVideoFrameFormat::Format_SamplerExternalOES;
++ glTarget = GL_TEXTURE_EXTERNAL_OES;
++ }
++ #endif
++ }
++
++ const int nImages = importMode == DmaBufImportMode::SingleImage ? 1 : nPlanes;
++
++ std::array<EGLImageHandle, 4> images;
++ std::array<GLuint, 4> rawGlTextures{};
++ functions.glGenTextures(nImages, rawGlTextures.data());
++
++ std::vector<GlTextureHandle> glTextures;
++ glTextures.reserve(nImages);
++ for (int i = 0; i < nImages; ++i)
++ glTextures.emplace_back(rawGlTextures[i], GlTextureDeleter(glContext));
++
++ auto importImage =
++ [&](int image, DRMFormat drmFormat, QSize imageSize,
++ QSpan<const DmaBufPlane> imagePlanes) -> q23::expected<void, FailureSeverity> {
++ constexpr EGLint fdAttrs[4] = {
++ EGL_DMA_BUF_PLANE0_FD_EXT,
++ EGL_DMA_BUF_PLANE1_FD_EXT,
++ EGL_DMA_BUF_PLANE2_FD_EXT,
++ EGL_DMA_BUF_PLANE3_FD_EXT,
++ };
++ constexpr EGLint offsetAttrs[4] = {
++ EGL_DMA_BUF_PLANE0_OFFSET_EXT,
++ EGL_DMA_BUF_PLANE1_OFFSET_EXT,
++ EGL_DMA_BUF_PLANE2_OFFSET_EXT,
++ EGL_DMA_BUF_PLANE3_OFFSET_EXT,
++ };
++ constexpr EGLint pitchAttrs[4] = {
++ EGL_DMA_BUF_PLANE0_PITCH_EXT,
++ EGL_DMA_BUF_PLANE1_PITCH_EXT,
++ EGL_DMA_BUF_PLANE2_PITCH_EXT,
++ EGL_DMA_BUF_PLANE3_PITCH_EXT,
++ };
++ constexpr EGLint modifierLoAttrs[4] = {
++ EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT,
++ EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT,
++ EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT,
++ EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT,
++ };
++ constexpr EGLint modifierHiAttrs[4] = {
++ EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT,
++ EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT,
++ EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT,
++ EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT,
++ };
++
++ constexpr size_t maxAttrCount = 6 + 4 * 10 + 1;
++ std::array<EGLAttrib, maxAttrCount> img_attr = {
++ EGL_LINUX_DRM_FOURCC_EXT,
++ (EGLint)qToUnderlying(drmFormat),
++ EGL_WIDTH,
++ imageSize.width(),
++ EGL_HEIGHT,
++ imageSize.height(),
++ };
++ size_t img_attr_idx = 6;
++ for (int plane = 0; plane < imagePlanes.size() && plane < 4; ++plane) {
++ const DmaBufPlane &dmabuf = imagePlanes[plane];
++ img_attr[img_attr_idx++] = fdAttrs[plane];
++ img_attr[img_attr_idx++] = dmabuf.fd;
++ img_attr[img_attr_idx++] = offsetAttrs[plane];
++ img_attr[img_attr_idx++] = (EGLint)dmabuf.offset;
++ img_attr[img_attr_idx++] = pitchAttrs[plane];
++ img_attr[img_attr_idx++] = (EGLint)dmabuf.pitch;
++ if (dmabuf.modifier != DmaBufFormatModifierInvalid) {
++ const auto modifier = qToUnderlying(dmabuf.modifier);
++ img_attr[img_attr_idx++] = modifierLoAttrs[plane];
++ img_attr[img_attr_idx++] = EGLAttrib(modifier & 0xFFFFFFFF);
++ img_attr[img_attr_idx++] = modifierHiAttrs[plane];
++ img_attr[img_attr_idx++] = EGLAttrib(modifier >> 32);
++ }
++ }
++ img_attr[img_attr_idx++] = EGL_NONE;
++ Q_ASSERT(img_attr_idx <= maxAttrCount);
++
++ images[image] = elgImageFunctions.eglCreateImage(
++ eglDisplay, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr,
++ QSpan<const EGLAttrib>(img_attr.data(), img_attr_idx));
++ if (!images[image]) {
++ const EGLError error = EGLError(eglGetError());
++ switch (error) {
++ case EGLError::Success: {
++ break;
++ }
++ case EGLError::BadMatch: {
++ qWarning() << "eglCreateImage failed for image" << image << "with" << error
++ << ", disabling hardware acceleration. "
++ "This could indicate an EGL implementation issue."
++ "\nEGL vendor:"
++ << eglQueryString(eglDisplay, EGL_VENDOR);
++ // Disabling texture conversion here to fix QTBUG-112312
++ return q23::unexpected{ FailureSeverity::unrecoverable };
++ }
++ case EGLError::BadParameter: {
++ bool dmaModifierNotSupported =
++ ranges::any_of(imagePlanes, [&](const DmaBufPlane &plane) {
++ return dmaModifierIsNotSupportedByDisplay(plane, eglDisplay);
++ });
++ if (dmaModifierNotSupported) {
++ qWarning() << "eglCreateImage failed for image" << image << "with" << error
++ << ", disabling hardware acceleration. "
++ "This could indicate an EGL implementation issue."
++ "\nEGL vendor:"
++ << eglQueryString(eglDisplay, EGL_VENDOR);
++ // Disabling texture conversion here to fix QTBUG-112312
++ return q23::unexpected{ FailureSeverity::unrecoverable };
++ }
++ [[fallthrough]];
++ }
++ default: {
++ qWarning() << "eglCreateImage failed for image" << image << "with" << error;
++ return q23::unexpected{ FailureSeverity::recoverable };
++ }
++ }
++ }
++ functions.glActiveTexture(GL_TEXTURE0 + image);
++ functions.glBindTexture(glTarget, glTextures[image].get());
++
++ elgImageFunctions.glEGLImageTargetTexture2DOES(glTarget, images[image].get());
++ const GLError error = GLError(glGetError());
++ if (error != GLError::NoError) {
++ qWarning() << "eglImageTargetTexture2D failed for image" << image << "with" << error
++ << "(the driver may not support importing this dma-buf as a GL texture)";
++ return q23::unexpected{ FailureSeverity::recoverable };
++ }
++ return {};
++ };
++
++ switch (importMode) {
++ case DmaBufImportMode::SingleImage: {
++ const auto combinedFormat = dmaBufSingleImageFourccFromPixelFormat(qtFormat);
++ if (!combinedFormat) {
++ qCWarning(qLDmaBuf) << "No single-image DRM format for" << qtFormat;
++ return q23::unexpected(FailureSeverity::recoverable);
++ }
++ auto result = importImage(0, *combinedFormat, frameSize, planes.first(nPlanes));
++ if (!result)
++ return q23::unexpected(result.error());
++ break;
++ }
++ case DmaBufImportMode::PerPlane:
++ for (int i = 0; i < nPlanes; ++i) {
++ const QSize planeSize = desc->rhiPlaneSize(frameSize, i, &rhi);
++ auto result = importImage(i, planes[i].drmFormat, planeSize, planes.subspan(i, 1));
++ if (!result)
++ return q23::unexpected(result.error());
++ }
++ break;
++ default:
++ Q_UNREACHABLE();
++ }
++
++ for (int i = 0; i < nImages; ++i) {
++ functions.glActiveTexture(GL_TEXTURE0 + i);
++ functions.glBindTexture(glTarget, 0);
++ }
++
++ return std::make_unique<DmaBufTextureHandles>(rhi, std::move(glTextures), texturePixelFormat,
++ std::move(parentKeepAlive));
++ }
++
++ } // namespace QtMultimediaPrivate
++
++ QT_END_NAMESPACE