Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qdmabuftextureimporter.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5
6#include <QtMultimedia/private/qeglimagefunctions_p.h>
7#include <QtMultimedia/private/qmultimedia_gl_support_p.h>
8#include <QtMultimedia/private/qmultimedia_ranges_p.h>
9#include <QtMultimedia/private/qvideotexturehelper_p.h>
10
11#include <QtGui/qopenglcontext.h>
12#include <QtGui/rhi/qrhi.h>
13#include <QtCore/qloggingcategory.h>
14
15#include <EGL/egl.h>
16#include <EGL/eglext.h>
17
18#include <array>
19#include <vector>
20
21static_assert(QT_CONFIG(linux_dmabuf));
22
24
25Q_STATIC_LOGGING_CATEGORY(qLDmaBuf, "qt.multimedia.dmabuf")
26
27namespace QtMultimediaPrivate {
28
30{
31 if (!rhi || rhi->backend() != QRhi::OpenGLES2) {
32 qWarning() << "DmaBufEglContext: No rhi or non openGL based RHI";
33 return;
34 }
35
37 if (!m_glContext) {
38 qCDebug(qLDmaBuf) << " no GL context, disabling";
39 return;
40 }
41
43 if (!eglContext) {
44 qCDebug(qLDmaBuf) << " no EGL context, disabling";
45 return;
46 }
47
49 if (!m_eglDisplay) {
50 qCDebug(qLDmaBuf) << " no egl display, disabling";
51 return;
52 }
53
55 qCDebug(qLDmaBuf) << " no eglImageTargetTexture2D, disabling";
56 return;
57 }
58
59 m_valid = true;
60}
61
71
73{
74 // m_textures is destroyed right after this body runs, calling glDeleteTextures on each plane.
76}
77
78static bool dmaModifierIsNotSupportedByDisplay(const DmaBufPlane &plane, EGLDisplay eglDisplay)
79{
80 if (plane.modifier == DmaBufFormatModifierInvalid)
81 return true; // no modifier info to check
82
83 auto &eglFunctions = QEglImageFunctions::instance();
84 constexpr bool assumeSupportedWhenUnknown = true;
85 const bool formatIsSupported =
86 eglFunctions.isDmaBufModifierSupported(eglDisplay, plane.drmFormat, plane.modifier)
87 .value_or(assumeSupportedWhenUnknown);
88 return formatIsSupported;
89}
90
91static bool isExternalOesOnly(const QEglImageFunctions &eglFunctions, EGLDisplay eglDisplay,
92 const DmaBufPlane &plane)
93{
94 // an implicit modifier is assumed to be linear
95 const DRMModifier modifier = plane.modifier == DmaBufFormatModifierInvalid
96 ? DrmFormatModifierLinear
97 : plane.modifier;
98 return eglFunctions.isDmaBufModifierExternalOesOnly(eglDisplay, plane.drmFormat, modifier)
99 .value_or(false);
100}
101
103importDmaBufTextures(QRhi &rhi, const DmaBufEglContext &eglContext, QSpan<const DmaBufPlane> planes,
104 QVideoFrameFormat::PixelFormat qtFormat, QSize frameSize,
105 std::shared_ptr<void> parentKeepAlive, DmaBufImportMode importMode)
106{
107 qCDebug(qLDmaBuf) << "Importing textures from DMA buffer," << qtFormat;
108 Qt::HANDLE eglDisplay = eglContext.eglDisplay();
109 QOpenGLContext *glContext = eglContext.glContext();
110
111 QOpenGLFunctions functions(glContext);
112 const auto &elgImageFunctions = QEglImageFunctions::instance();
113
114 auto *desc = QVideoTextureHelper::textureDescription(qtFormat);
115 int nPlanes = desc->nplanes;
116 Q_ASSERT(planes.size() == nPlanes);
117
118 rhi.makeThreadLocalNativeContextCurrent();
119
120 // Binding an external-only image to GL_TEXTURE_2D fails (e.g. NVIDIA, for dma-bufs it did not
121 // allocate itself), so skip the import and let the caller use the CPU path.
122 if (importMode == DmaBufImportMode::PerPlane
123 && ranges::any_of(planes.first(nPlanes), [&](const DmaBufPlane &plane) {
124 return isExternalOesOnly(elgImageFunctions, eglDisplay, plane);
125 })) {
126 qCDebug(qLDmaBuf) << "Planes can only be sampled as external textures, not importing";
127 return q23::unexpected{ FailureSeverity::recoverable };
128 }
129
130 QVideoFrameFormat::PixelFormat texturePixelFormat = qtFormat;
131 GLenum glTarget = GL_TEXTURE_2D;
132 if (importMode == DmaBufImportMode::SingleImage) {
133 // the driver converts the planes to RGB when sampling
134 texturePixelFormat = QVideoFrameFormat::Format_RGBA8888;
135#ifdef GL_OES_EGL_image_external
136 if (QVideoTextureHelper::forceGlTextureExternalOesIsSet()) {
137 texturePixelFormat = QVideoFrameFormat::Format_SamplerExternalOES;
138 glTarget = GL_TEXTURE_EXTERNAL_OES;
139 }
140#endif
141 }
142
143 const int nImages = importMode == DmaBufImportMode::SingleImage ? 1 : nPlanes;
144
145 std::array<EGLImageHandle, 4> images;
146 std::array<GLuint, 4> rawGlTextures{};
147 functions.glGenTextures(nImages, rawGlTextures.data());
148
149 std::vector<GlTextureHandle> glTextures;
150 glTextures.reserve(nImages);
151 for (int i = 0; i < nImages; ++i)
152 glTextures.emplace_back(rawGlTextures[i], GlTextureDeleter(glContext));
153
154 auto importImage =
155 [&](int image, DRMFormat drmFormat, QSize imageSize,
156 QSpan<const DmaBufPlane> imagePlanes) -> q23::expected<void, FailureSeverity> {
157 constexpr EGLint fdAttrs[4] = {
158 EGL_DMA_BUF_PLANE0_FD_EXT,
159 EGL_DMA_BUF_PLANE1_FD_EXT,
160 EGL_DMA_BUF_PLANE2_FD_EXT,
161 EGL_DMA_BUF_PLANE3_FD_EXT,
162 };
163 constexpr EGLint offsetAttrs[4] = {
164 EGL_DMA_BUF_PLANE0_OFFSET_EXT,
165 EGL_DMA_BUF_PLANE1_OFFSET_EXT,
166 EGL_DMA_BUF_PLANE2_OFFSET_EXT,
167 EGL_DMA_BUF_PLANE3_OFFSET_EXT,
168 };
169 constexpr EGLint pitchAttrs[4] = {
170 EGL_DMA_BUF_PLANE0_PITCH_EXT,
171 EGL_DMA_BUF_PLANE1_PITCH_EXT,
172 EGL_DMA_BUF_PLANE2_PITCH_EXT,
173 EGL_DMA_BUF_PLANE3_PITCH_EXT,
174 };
175 constexpr EGLint modifierLoAttrs[4] = {
176 EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT,
177 EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT,
178 EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT,
179 EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT,
180 };
181 constexpr EGLint modifierHiAttrs[4] = {
182 EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT,
183 EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT,
184 EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT,
185 EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT,
186 };
187
188 constexpr size_t maxAttrCount = 6 + 4 * 10 + 1;
189 std::array<EGLAttrib, maxAttrCount> img_attr = {
190 EGL_LINUX_DRM_FOURCC_EXT,
191 (EGLint)qToUnderlying(drmFormat),
192 EGL_WIDTH,
193 imageSize.width(),
194 EGL_HEIGHT,
195 imageSize.height(),
196 };
197 size_t img_attr_idx = 6;
198 for (int plane = 0; plane < imagePlanes.size() && plane < 4; ++plane) {
199 const DmaBufPlane &dmabuf = imagePlanes[plane];
200 img_attr[img_attr_idx++] = fdAttrs[plane];
201 img_attr[img_attr_idx++] = dmabuf.fd;
202 img_attr[img_attr_idx++] = offsetAttrs[plane];
203 img_attr[img_attr_idx++] = (EGLint)dmabuf.offset;
204 img_attr[img_attr_idx++] = pitchAttrs[plane];
205 img_attr[img_attr_idx++] = (EGLint)dmabuf.pitch;
206 if (dmabuf.modifier != DmaBufFormatModifierInvalid) {
207 const auto modifier = qToUnderlying(dmabuf.modifier);
208 img_attr[img_attr_idx++] = modifierLoAttrs[plane];
209 img_attr[img_attr_idx++] = EGLAttrib(modifier & 0xFFFFFFFF);
210 img_attr[img_attr_idx++] = modifierHiAttrs[plane];
211 img_attr[img_attr_idx++] = EGLAttrib(modifier >> 32);
212 }
213 }
214 img_attr[img_attr_idx++] = EGL_NONE;
215 Q_ASSERT(img_attr_idx <= maxAttrCount);
216
217 images[image] = elgImageFunctions.eglCreateImage(
218 eglDisplay, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr,
219 QSpan<const EGLAttrib>(img_attr.data(), img_attr_idx));
220 if (!images[image]) {
221 const EGLError error = EGLError(eglGetError());
222 switch (error) {
223 case EGLError::Success: {
224 break;
225 }
226 case EGLError::BadMatch: {
227 qWarning() << "eglCreateImage failed for image" << image << "with" << error
228 << ", disabling hardware acceleration. "
229 "This could indicate an EGL implementation issue."
230 "\nEGL vendor:"
231 << eglQueryString(eglDisplay, EGL_VENDOR);
232 // Disabling texture conversion here to fix QTBUG-112312
233 return q23::unexpected{ FailureSeverity::unrecoverable };
234 }
235 case EGLError::BadParameter: {
236 bool dmaModifierNotSupported =
237 ranges::any_of(imagePlanes, [&](const DmaBufPlane &plane) {
238 return dmaModifierIsNotSupportedByDisplay(plane, eglDisplay);
239 });
240 if (dmaModifierNotSupported) {
241 qWarning() << "eglCreateImage failed for image" << image << "with" << error
242 << ", disabling hardware acceleration. "
243 "This could indicate an EGL implementation issue."
244 "\nEGL vendor:"
245 << eglQueryString(eglDisplay, EGL_VENDOR);
246 // Disabling texture conversion here to fix QTBUG-112312
247 return q23::unexpected{ FailureSeverity::unrecoverable };
248 }
249 [[fallthrough]];
250 }
251 default: {
252 qWarning() << "eglCreateImage failed for image" << image << "with" << error;
253 return q23::unexpected{ FailureSeverity::recoverable };
254 }
255 }
256 }
257 functions.glActiveTexture(GL_TEXTURE0 + image);
258 functions.glBindTexture(glTarget, glTextures[image].get());
259
260 elgImageFunctions.glEGLImageTargetTexture2DOES(glTarget, images[image].get());
261 const GLError error = GLError(glGetError());
262 if (error != GLError::NoError) {
263 qWarning() << "eglImageTargetTexture2D failed for image" << image << "with" << error
264 << "(the driver may not support importing this dma-buf as a GL texture)";
265 return q23::unexpected{ FailureSeverity::recoverable };
266 }
267 return {};
268 };
269
270 switch (importMode) {
271 case DmaBufImportMode::SingleImage: {
272 const auto combinedFormat = dmaBufSingleImageFourccFromPixelFormat(qtFormat);
273 if (!combinedFormat) {
274 qCWarning(qLDmaBuf) << "No single-image DRM format for" << qtFormat;
275 return q23::unexpected(FailureSeverity::recoverable);
276 }
277 auto result = importImage(0, *combinedFormat, frameSize, planes.first(nPlanes));
278 if (!result)
279 return q23::unexpected(result.error());
280 break;
281 }
282 case DmaBufImportMode::PerPlane:
283 for (int i = 0; i < nPlanes; ++i) {
284 const QSize planeSize = desc->rhiPlaneSize(frameSize, i, &rhi);
285 auto result = importImage(i, planes[i].drmFormat, planeSize, planes.subspan(i, 1));
286 if (!result)
287 return q23::unexpected(result.error());
288 }
289 break;
290 default:
291 Q_UNREACHABLE();
292 }
293
294 for (int i = 0; i < nImages; ++i) {
295 functions.glActiveTexture(GL_TEXTURE0 + i);
296 functions.glBindTexture(glTarget, 0);
297 }
298
299 return std::make_unique<DmaBufTextureHandles>(rhi, std::move(glTextures), texturePixelFormat,
300 std::move(parentKeepAlive));
301}
302
303} // namespace QtMultimediaPrivate
304
305QT_END_NAMESPACE
static bool dmaModifierIsNotSupportedByDisplay(const DmaBufPlane &plane, EGLDisplay eglDisplay)
static bool isExternalOesOnly(const QEglImageFunctions &eglFunctions, EGLDisplay eglDisplay, const DmaBufPlane &plane)
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)
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)