QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
GstDmaBufVideoBuffer.cc
Go to the documentation of this file.
2#include "GstHwVideoBuffer.h"
3
4#if defined(QGC_HAS_GST_DMABUF_GPU_PATH)
5
7
8#include <QtCore/QLoggingCategory>
9#include <QtCore/QHash>
10#include <QtCore/QMutex>
11#include <QtCore/QMutexLocker>
12#include <QtCore/QSize>
13#include <QtGui/QOpenGLContext>
14#include <QtGui/QOpenGLFunctions>
15#include <private/qvideotexturehelper_p.h>
16#include <rhi/qrhi.h>
17
18#include <gst/allocators/gstdmabuf.h>
19#include <gst/video/video.h>
20#if defined(QGC_GST_BUILD_VERSION_MAJOR) && \
21 (QGC_GST_BUILD_VERSION_MAJOR > 1 || \
22 (QGC_GST_BUILD_VERSION_MAJOR == 1 && QGC_GST_BUILD_VERSION_MINOR >= 24))
23#include <gst/video/video-info-dma.h>
24#endif
25
26#include <GLES2/gl2.h>
27#include <GLES2/gl2ext.h>
28#include <drm_fourcc.h>
29
30#include <array>
31#include <atomic>
32#include <cstring>
33
34QGC_LOGGING_CATEGORY(GstDmaBufLog, "Video.GStreamer.HwBuffers.GstDmaBuf")
35
36namespace {
37
38constexpr int kMaxPlanes = 4;
39
40// Process-wide failure tally; read+reset by GstDmaBufVideoBuffer::takeMapFailureCount.
41std::atomic<quint64> s_mapFailureCount{0};
42std::atomic<bool> s_loggedBadBackend{false};
43
44QMutex s_modExtMutex;
45QHash<EGLDisplay, bool> s_modExtCache;
46
47bool queryHasModifiersExt(EGLDisplay display)
48{
49 QMutexLocker lock(&s_modExtMutex);
50 auto it = s_modExtCache.find(display);
51 if (it != s_modExtCache.end()) {
52 return it.value();
53 }
54 // eglQueryString(display, EGL_EXTENSIONS) returns NULL on un-initialized displays.
55 // eglInitialize is idempotent, so this is a safe defensive call when we got the display
56 // from eglGetDisplay(EGL_DEFAULT_DISPLAY) and Qt happens to have init'd a *different* one.
57 EGLint major = 0, minor = 0;
58 eglInitialize(display, &major, &minor);
59 const char *exts = eglQueryString(display, EGL_EXTENSIONS);
60 const bool supported = exts != nullptr
61 && std::strstr(exts, "EGL_EXT_image_dma_buf_import_modifiers") != nullptr;
62 qCDebug(GstDmaBufLog) << "EGL display" << display
63 << "modifiers ext supported:" << supported
64 << " (driver:" << eglQueryString(display, EGL_VENDOR) << ")";
65 s_modExtCache.insert(display, supported);
66 return supported;
67}
68
69// DRM fourcc for a given GstVideoInfo plane. Modeled on Qt's
70// fourccFromVideoInfo() in qgstvideobuffer.cpp (LGPL-3).
71int drmFourccFor(const GstVideoInfo *info, int plane)
72{
73 const GstVideoFormat fmt = GST_VIDEO_INFO_FORMAT(info);
74#if G_BYTE_ORDER == G_LITTLE_ENDIAN
75 constexpr int rgbaFourcc = DRM_FORMAT_ABGR8888;
76 constexpr int rgFourcc = DRM_FORMAT_GR88;
77#else
78 constexpr int rgbaFourcc = DRM_FORMAT_RGBA8888;
79 constexpr int rgFourcc = DRM_FORMAT_RG88;
80#endif
81
82 switch (fmt) {
83 case GST_VIDEO_FORMAT_RGBA:
84 case GST_VIDEO_FORMAT_RGBx:
85 case GST_VIDEO_FORMAT_BGRA:
86 case GST_VIDEO_FORMAT_BGRx:
87 case GST_VIDEO_FORMAT_ARGB:
88 case GST_VIDEO_FORMAT_xRGB:
89 case GST_VIDEO_FORMAT_ABGR:
90 case GST_VIDEO_FORMAT_xBGR:
91 case GST_VIDEO_FORMAT_AYUV:
92 return rgbaFourcc;
93 case GST_VIDEO_FORMAT_GRAY8:
94 return DRM_FORMAT_R8;
95 case GST_VIDEO_FORMAT_YUY2:
96 case GST_VIDEO_FORMAT_UYVY:
97 case GST_VIDEO_FORMAT_GRAY16_LE:
98 return rgFourcc;
99 case GST_VIDEO_FORMAT_NV12:
100 case GST_VIDEO_FORMAT_NV21:
101 return plane == 0 ? DRM_FORMAT_R8 : rgFourcc;
102 case GST_VIDEO_FORMAT_I420:
103 case GST_VIDEO_FORMAT_YV12:
104 case GST_VIDEO_FORMAT_Y42B:
105 case GST_VIDEO_FORMAT_Y444:
106 return DRM_FORMAT_R8;
107 case GST_VIDEO_FORMAT_P010_10LE:
108 return plane == 0 ? DRM_FORMAT_R16 : DRM_FORMAT_GR1616;
109 default:
110 return -1;
111 }
112}
113
114// DRM fourcc for formats that can be imported as a single EGLImage (all planes, one fd).
115int drmFourccForSingleFd(const GstVideoInfo *info)
116{
117 switch (GST_VIDEO_INFO_FORMAT(info)) {
118 case GST_VIDEO_FORMAT_NV12: return DRM_FORMAT_NV12;
119 case GST_VIDEO_FORMAT_NV21: return DRM_FORMAT_NV21;
120 case GST_VIDEO_FORMAT_P010_10LE: return DRM_FORMAT_P010;
121 case GST_VIDEO_FORMAT_I420: return DRM_FORMAT_YUV420;
122 case GST_VIDEO_FORMAT_YV12: return DRM_FORMAT_YVU420;
123 // packed single-memory formats: planeCount==1, memCount==1
124 case GST_VIDEO_FORMAT_YUY2: return DRM_FORMAT_YUYV;
125 case GST_VIDEO_FORMAT_UYVY: return DRM_FORMAT_UYVY;
126#ifdef DRM_FORMAT_YVYU
127 case GST_VIDEO_FORMAT_YVYU: return DRM_FORMAT_YVYU;
128#endif
129#ifdef DRM_FORMAT_VYUY
130 case GST_VIDEO_FORMAT_VYUY: return DRM_FORMAT_VYUY;
131#endif
132 // AYUV excluded: EGL importers don't support DRM_FORMAT_AYUV; per-plane RGBA path handles it.
133#ifdef DRM_FORMAT_Y210
134 case GST_VIDEO_FORMAT_Y210: return DRM_FORMAT_Y210;
135#endif
136#ifdef DRM_FORMAT_Y410
137 case GST_VIDEO_FORMAT_Y410: return DRM_FORMAT_Y410;
138#endif
139 default: return -1;
140 }
141}
142
143// Per-plane EGL attrib keys — spec defines distinct enums per plane index.
144static constexpr EGLint kPlFd[4] = {
145 EGL_DMA_BUF_PLANE0_FD_EXT, EGL_DMA_BUF_PLANE1_FD_EXT,
146 EGL_DMA_BUF_PLANE2_FD_EXT, EGL_DMA_BUF_PLANE3_FD_EXT,
147};
148static constexpr EGLint kPlOffset[4] = {
149 EGL_DMA_BUF_PLANE0_OFFSET_EXT, EGL_DMA_BUF_PLANE1_OFFSET_EXT,
150 EGL_DMA_BUF_PLANE2_OFFSET_EXT, EGL_DMA_BUF_PLANE3_OFFSET_EXT,
151};
152static constexpr EGLint kPlPitch[4] = {
153 EGL_DMA_BUF_PLANE0_PITCH_EXT, EGL_DMA_BUF_PLANE1_PITCH_EXT,
154 EGL_DMA_BUF_PLANE2_PITCH_EXT, EGL_DMA_BUF_PLANE3_PITCH_EXT,
155};
156static constexpr EGLint kPlModLo[4] = {
157 EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT,
158 EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT,
159};
160static constexpr EGLint kPlModHi[4] = {
161 EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT,
162 EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT, EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT,
163};
164
165class FrameTextures final : public QVideoFrameTextures
166{
167public:
168 FrameTextures(QRhi *rhi, QSize size, QVideoFrameFormat::PixelFormat pixelFormat,
169 std::array<GLuint, kMaxPlanes> names, int count)
170 : _rhi(rhi)
171 , _names(names)
172 , _count(count)
173 {
174 const auto *desc = QVideoTextureHelper::textureDescription(pixelFormat);
175 if (!desc) {
176 qCWarning(GstDmaBufLog) << "no QVideoTextureHelper description for format" << pixelFormat;
177 return;
178 }
179 for (int i = 0; i < _count; ++i) {
180 const QSize planeSize = desc->rhiPlaneSize(size, i, rhi);
181 _textures[i].reset(rhi->newTexture(
182 desc->rhiTextureFormat(i, rhi, QVideoTextureHelper::TextureDescription::FallbackPolicy::Disable),
183 planeSize, 1, {}));
184 if (_textures[i] && !_textures[i]->createFrom({_names[i], 0})) {
185 qCWarning(GstDmaBufLog) << "QRhiTexture::createFrom failed for plane" << i;
186 _textures[i].reset();
187 }
188 }
189 }
190
191 ~FrameTextures() override
192 {
193 releaseGLTextures(); // safety net if onFrameEndInvoked() was never called
194 }
195
196 void onFrameEndInvoked() override
197 {
198 releaseGLTextures();
199 }
200
201 QRhiTexture *texture(uint plane) const override
202 {
203 return (int(plane) < _count) ? _textures[plane].get() : nullptr;
204 }
205
206private:
207 void releaseGLTextures()
208 {
209 if (_released || !_rhi || _count == 0) return;
210 _released = true;
211 // makeThreadLocalNativeContextCurrent avoids crash when Qt drops frame off the render thread.
212 _rhi->makeThreadLocalNativeContextCurrent();
213 if (auto *ctx = QOpenGLContext::currentContext()) {
214 ctx->functions()->glDeleteTextures(_count, _names.data());
215 }
216 }
217
218 QRhi *_rhi = nullptr;
219 std::array<GLuint, kMaxPlanes> _names{};
220 int _count = 0;
221 bool _released = false;
222 std::unique_ptr<QRhiTexture> _textures[kMaxPlanes];
223};
224
225} // namespace
226
227GstDmaBufVideoBuffer::GstDmaBufVideoBuffer(GstSample *sample,
228 const GstVideoInfo &videoInfo,
229 const QVideoFrameFormat &format,
230 EGLDisplay eglDisplay)
231 : GstHwVideoBuffer(QVideoFrame::RhiTextureHandle, sample, videoInfo, format)
232 , _eglDisplay(eglDisplay)
233{
234}
235
236GstDmaBufVideoBuffer::~GstDmaBufVideoBuffer() = default;
237
238QAbstractVideoBuffer::MapData GstDmaBufVideoBuffer::map(QVideoFrame::MapMode /*mode*/)
239{
240 // GPU-only buffer; CPU map intentionally unsupported. Qt will draw black if it
241 // ever calls map() — that means mapTextures() failed, which is logged below.
242 return {};
243}
244
245bool GstDmaBufVideoBuffer::validatePlaneHandles() const
246{
247 if (!_sample) return false;
248 GstBuffer *buffer = gst_sample_get_buffer(_sample);
249 if (!buffer) return false;
250 const int memCount = qMin(int(gst_buffer_n_memory(buffer)), kMaxPlanes);
251 if (memCount <= 0) return false;
252 for (int i = 0; i < memCount; ++i) {
253 GstMemory *mem = gst_buffer_peek_memory(buffer, i);
254 if (!mem || !gst_is_dmabuf_memory(mem)) return false;
255 if (gst_dmabuf_memory_get_fd(mem) < 0) return false;
256 }
257 // Early non-LINEAR rejection — without this, Qt warns "Cannot map ReadOnly" and drops the frame.
258#if defined(QGC_GST_BUILD_VERSION_MAJOR) && \
259 (QGC_GST_BUILD_VERSION_MAJOR > 1 || \
260 (QGC_GST_BUILD_VERSION_MAJOR == 1 && QGC_GST_BUILD_VERSION_MINOR >= 24))
261 if (GstCaps *caps = gst_sample_get_caps(_sample); caps && gst_video_is_dma_drm_caps(caps)) {
262 GstVideoInfoDmaDrm drmInfo{};
263 gst_video_info_dma_drm_init(&drmInfo);
264 if (gst_video_info_dma_drm_from_caps(&drmInfo, caps) && drmInfo.drm_modifier != 0) {
265 return false;
266 }
267 }
268#endif
269 return true;
270}
271
272QVideoFrameTexturesUPtr GstDmaBufVideoBuffer::mapTextures(QRhi &rhi, QVideoFrameTexturesUPtr & /*old*/)
273{
274 Q_ASSERT(rhi.thread()->isCurrentThread()); // Qt's contract: mapTextures runs on the QRhi (render) thread.
275
276 auto fail = []() -> QVideoFrameTexturesUPtr {
277 s_mapFailureCount.fetch_add(1, std::memory_order_relaxed);
278 return {};
279 };
280
281 if (!_sample) {
282 return fail();
283 }
284
285 // Parse modifier before the sync mmap — mmap assumes LINEAR; tiled strides fault libgstvideo.
286 guint64 drmModifier = 0;
287#if defined(QGC_GST_BUILD_VERSION_MAJOR) && \
288 (QGC_GST_BUILD_VERSION_MAJOR > 1 || \
289 (QGC_GST_BUILD_VERSION_MAJOR == 1 && QGC_GST_BUILD_VERSION_MINOR >= 24))
290 if (GstCaps *caps = gst_sample_get_caps(_sample); caps && gst_video_is_dma_drm_caps(caps)) {
291 GstVideoInfoDmaDrm drmInfo{};
292 gst_video_info_dma_drm_init(&drmInfo);
293 if (gst_video_info_dma_drm_from_caps(&drmInfo, caps)) {
294 drmModifier = drmInfo.drm_modifier;
295 }
296 }
297#endif
298
299 // dmabuf mmap as a sync barrier — Intel iHD legacy LINEAR exporter has no implicit fence
300 // and vaSyncSurface() is a no-op there. Tiled paths carry fences and aren't safe to mmap.
301 if (drmModifier == 0 /* DRM_FORMAT_MOD_LINEAR / unknown */) {
302 if (GstBuffer *syncBuf = gst_sample_get_buffer(_sample)) {
303 GstVideoFrame syncFrame;
304 if (gst_video_frame_map(&syncFrame, &_videoInfo, syncBuf, GST_MAP_READ)) {
305 gst_video_frame_unmap(&syncFrame);
306 }
307 }
308 }
309
310 // Bind Qt's GL context on the render thread BEFORE any EGL/GL call. Without this,
311 // glBindTexture / glEGLImageTargetTexture2DOES silently no-op into a foreign or
312 // null context — symptom: import succeeds, texture samples empty (green).
313 rhi.makeThreadLocalNativeContextCurrent();
314
315 // Use Qt's actual EGLDisplay, not the adapter's eglGetDisplay(EGL_DEFAULT_DISPLAY) —
316 // those can resolve to different EGLDisplay objects under xcb_egl, and EGLImages
317 // imported in display A cannot be sampled by a context bound to display B.
318 EGLDisplay eglDpy = eglGetCurrentDisplay();
319 if (eglDpy == EGL_NO_DISPLAY) {
320 eglDpy = _eglDisplay;
321 }
322
323 if (eglDpy == EGL_NO_DISPLAY) {
324 return fail();
325 }
326 if (rhi.backend() != QRhi::OpenGLES2) {
327 if (!s_loggedBadBackend.exchange(true, std::memory_order_relaxed)) {
328 qCWarning(GstDmaBufLog) << "QRhi backend is not OpenGLES2; zero-copy DMABuf path unsupported";
329 }
330 return fail();
331 }
332
333 GstBuffer *buffer = gst_sample_get_buffer(_sample);
334 if (!buffer || !gst_is_dmabuf_memory(gst_buffer_peek_memory(buffer, 0))) {
335 return fail();
336 }
337 const bool hasModifiersExt = queryHasModifiersExt(eglDpy);
338 if (drmModifier != 0) {
339 // Tiled+CCS (e.g. I915_FORMAT_MOD_Y_TILED_CCS) needs multi-plane EXTERNAL_OES import;
340 // per-plane GL_TEXTURE_2D crashes the driver in glEGLImageTargetTexture2DOES.
341 static std::atomic<bool> warned{false};
342 if (!warned.exchange(true, std::memory_order_relaxed)) {
343 qCWarning(GstDmaBufLog) << "DMABuf modifier" << Qt::hex << drmModifier
344 << "is non-LINEAR; per-plane EGLImage import is unsafe"
345 " on tiled/CCS layouts — falling back to CPU"
346 " (modifiersExt=" << hasModifiersExt << ")";
347 }
348 return fail();
349 }
350
351 const auto *nativeHandles = static_cast<const QRhiGles2NativeHandles *>(rhi.nativeHandles());
352 if (!nativeHandles || !nativeHandles->context) {
353 qCWarning(GstDmaBufLog) << "QRhi exposes no GL context";
354 return fail();
355 }
356
357 static const auto eglImageTargetTexture2D =
358 reinterpret_cast<PFNGLEGLIMAGETARGETTEXTURE2DOESPROC>(
359 eglGetProcAddress("glEGLImageTargetTexture2DOES"));
360 if (!eglImageTargetTexture2D) {
361 qCWarning(GstDmaBufLog) << "glEGLImageTargetTexture2DOES unavailable";
362 return fail();
363 }
364
365 // gst_video_frame_map(GST_MAP_READ) on DMABuf triggers a CPU mmap defeating zero-copy; read offsets from GstVideoMeta directly.
366 GstVideoMeta *vmeta = gst_buffer_get_video_meta(buffer);
367 const int planeCount = qBound(1, int(GST_VIDEO_INFO_N_PLANES(&_videoInfo)), kMaxPlanes);
368 const int memCount = int(gst_buffer_n_memory(buffer));
369 // memCount==1: either multi-plane shared fd (NV12) or packed single-plane (YUY2/UYVY/…).
370 const int cachedSingleFourcc = (memCount == 1) ? drmFourccForSingleFd(&_videoInfo) : -1;
371 const bool singleFdPacking = (cachedSingleFourcc != -1);
372 if (memCount != planeCount && !singleFdPacking) {
373 return fail();
374 }
375
376 // No texture reuse: a previous attempt to cache QRhiTextures across frames
377 // (keyed first on fd values, then on GstMemory pointers) caused periodic
378 // green frames in real RTSP H.264 streams. The optimization is worth ~µs per
379 // frame and not worth the lifetime hazards. Always rebuild.
380 std::array<GLuint, kMaxPlanes> names{};
381 QOpenGLFunctions functions(nativeHandles->context);
382 functions.glGenTextures(planeCount, names.data());
383
384 // Pass modifier attribs only when extension is available; otherwise the implementation
385 // assumes LINEAR, which matches our earlier rejection of non-LINEAR without the ext.
386 const EGLAttrib modLo = static_cast<EGLAttrib>(drmModifier & 0xFFFFFFFFu);
387 const EGLAttrib modHi = static_cast<EGLAttrib>((drmModifier >> 32) & 0xFFFFFFFFu);
388
389 bool ok = true;
390
391 // Single-fd fast path: one EGLImage covering all planes, one eglCreateImage call.
392 // Mesa VA-API NV12 default ships memCount==1; iHD rejects split-plane R8/GR88 imports.
393 if (singleFdPacking) {
394 if (planeCount > kMaxPlanes) {
395 qCWarning(GstDmaBufLog) << "single-fd format has" << planeCount
396 << "planes (>" << kMaxPlanes << "); falling back to per-plane";
397 goto per_plane_path;
398 }
399 const int singleFourcc = cachedSingleFourcc;
400 if (singleFourcc == -1) {
401 goto per_plane_path;
402 }
403 const int fd0 = gst_dmabuf_memory_get_fd(gst_buffer_peek_memory(buffer, 0));
404 // maxAttr: WIDTH/HEIGHT/FOURCC + 3 attribs/plane * kMaxPlanes + 2 modifier attribs/plane * kMaxPlanes + EGL_NONE
405 EGLAttrib attribs[3 + kMaxPlanes * 5 + 1];
406 int n = 0;
407#define ATTRIB_PUSH(k, v) do { Q_ASSERT(n < int(std::size(attribs)) - 1); attribs[n++] = (k); attribs[n++] = (v); } while (0)
408 ATTRIB_PUSH(EGL_WIDTH, GST_VIDEO_INFO_WIDTH(&_videoInfo));
409 ATTRIB_PUSH(EGL_HEIGHT, GST_VIDEO_INFO_HEIGHT(&_videoInfo));
410 ATTRIB_PUSH(EGL_LINUX_DRM_FOURCC_EXT, singleFourcc);
411 for (int p = 0; p < planeCount; ++p) {
412 const auto off = vmeta ? vmeta->offset[p] : GST_VIDEO_INFO_PLANE_OFFSET(&_videoInfo, p);
413 const auto pitch = vmeta ? vmeta->stride[p] : GST_VIDEO_INFO_PLANE_STRIDE(&_videoInfo, p);
414 ATTRIB_PUSH(kPlFd[p], fd0);
415 ATTRIB_PUSH(kPlOffset[p], static_cast<EGLAttrib>(off));
416 ATTRIB_PUSH(kPlPitch[p], static_cast<EGLAttrib>(pitch));
417 if (hasModifiersExt) { // interleaved per spec: modifier attribs follow FD/OFFSET/PITCH for same plane
418 ATTRIB_PUSH(kPlModLo[p], modLo);
419 ATTRIB_PUSH(kPlModHi[p], modHi);
420 }
421 }
422#undef ATTRIB_PUSH
423 attribs[n++] = EGL_NONE;
424 EGLImage singleImage = eglCreateImage(eglDpy, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT,
425 nullptr, attribs);
426 if (singleImage != EGL_NO_IMAGE_KHR) {
427 for (int p = 0; p < planeCount; ++p) {
428 functions.glBindTexture(GL_TEXTURE_2D, names[p]);
429 eglImageTargetTexture2D(GL_TEXTURE_2D, singleImage);
430 }
431 eglDestroyImage(eglDpy, singleImage);
432 goto textures_built;
433 }
434#if defined(DRM_FORMAT_Y210) || defined(DRM_FORMAT_Y410)
435 // Y210/Y410 have no per-plane fallback; warn once so the user knows frames will be black.
436 {
437 static std::atomic<bool> s_warnedY210{false};
438 static std::atomic<bool> s_warnedY410{false};
439#if defined(DRM_FORMAT_Y210)
440 if (singleFourcc == DRM_FORMAT_Y210 && !s_warnedY210.exchange(true, std::memory_order_relaxed))
441 qCWarning(GstDmaBufLog) << "EGL DMABuf import of Y210 unsupported on this driver; frames will be black";
442#endif
443#if defined(DRM_FORMAT_Y410)
444 if (singleFourcc == DRM_FORMAT_Y410 && !s_warnedY410.exchange(true, std::memory_order_relaxed))
445 qCWarning(GstDmaBufLog) << "EGL DMABuf import of Y410 unsupported on this driver; frames will be black";
446#endif
447 }
448#endif
449 qCWarning(GstDmaBufLog) << "single-fd eglCreateImage failed (" << Qt::hex
450 << eglGetError() << "); falling back to per-plane";
451 }
452
453per_plane_path:
454 for (int i = 0; i < planeCount && ok; ++i) {
455 const int memIdx = singleFdPacking ? 0 : i;
456 const int fd = gst_dmabuf_memory_get_fd(gst_buffer_peek_memory(buffer, memIdx));
457 const auto offset = vmeta ? vmeta->offset[i] : GST_VIDEO_INFO_PLANE_OFFSET(&_videoInfo, i);
458 const auto stride = vmeta ? vmeta->stride[i] : GST_VIDEO_INFO_PLANE_STRIDE(&_videoInfo, i);
459 Q_ASSERT(stride > 0);
460 Q_ASSERT(static_cast<quint64>(offset) <= static_cast<quint64>(std::numeric_limits<EGLAttrib>::max()));
461 const int planeWidth = GST_VIDEO_INFO_COMP_WIDTH(&_videoInfo, i);
462 const int planeHeight = GST_VIDEO_INFO_COMP_HEIGHT(&_videoInfo, i);
463 const int fourcc = drmFourccFor(&_videoInfo, i);
464 if (fourcc == -1) {
465 qCWarning(GstDmaBufLog) << "no DRM fourcc for format"
466 << gst_video_format_to_string(GST_VIDEO_INFO_FORMAT(&_videoInfo));
467 ok = false;
468 break;
469 }
470
471 // EGL attribute list — sized for the maximum (with modifier ext) and zero-terminated.
472 EGLAttrib attribs[18];
473 int n = 0;
474#define ATTRIB_PUSH(k, v) do { Q_ASSERT(n < int(std::size(attribs)) - 1); attribs[n++] = (k); attribs[n++] = (v); } while (0)
475 ATTRIB_PUSH(EGL_WIDTH, planeWidth);
476 ATTRIB_PUSH(EGL_HEIGHT, planeHeight);
477 ATTRIB_PUSH(EGL_LINUX_DRM_FOURCC_EXT, fourcc);
478 ATTRIB_PUSH(kPlFd[0], fd);
479 ATTRIB_PUSH(kPlOffset[0], static_cast<EGLAttrib>(offset));
480 ATTRIB_PUSH(kPlPitch[0], static_cast<EGLAttrib>(stride));
481 if (hasModifiersExt) {
482 ATTRIB_PUSH(kPlModLo[i], modLo); // plane-specific key per EGL_EXT_image_dma_buf_import_modifiers spec
483 ATTRIB_PUSH(kPlModHi[i], modHi);
484 }
485#undef ATTRIB_PUSH
486 attribs[n++] = EGL_NONE;
487 EGLImage image = eglCreateImage(eglDpy, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT,
488 nullptr, attribs);
489 if (image == EGL_NO_IMAGE_KHR) {
490 qCWarning(GstDmaBufLog) << "eglCreateImage failed plane" << i << "err"
491 << Qt::hex << eglGetError();
492 ok = false;
493 break;
494 }
495 functions.glBindTexture(GL_TEXTURE_2D, names[i]);
496 eglImageTargetTexture2D(GL_TEXTURE_2D, image);
497 eglDestroyImage(eglDpy, image);
498 }
499
500textures_built:
501
502 if (!ok) {
503 functions.glDeleteTextures(planeCount, names.data());
504 return fail();
505 }
506
507 auto frameTextures = std::make_unique<FrameTextures>(&rhi, _format.frameSize(),
508 _format.pixelFormat(), names, planeCount);
509 // FrameTextures ctor null-resets any plane that QRhiTexture::createFrom() rejects;
510 // returning a partially-empty textures bundle would render black without
511 // incrementing the failure counter (Qt sees a "successful" frame). Bail loudly instead.
512 for (int i = 0; i < planeCount; ++i) {
513 if (!frameTextures->texture(i)) {
514 qCWarning(GstDmaBufLog) << "FrameTextures plane" << i << "createFrom failed — dropping frame";
515 // ~FrameTextures will glDeleteTextures via releaseGLTextures() — names are owned now.
516 return fail();
517 }
518 }
519 return frameTextures;
520}
521
522quint64 GstDmaBufVideoBuffer::takeMapFailureCount()
523{
524 return s_mapFailureCount.exchange(0, std::memory_order_relaxed);
525}
526
527quint64 GstDmaBufVideoBuffer::peekMapFailureCount()
528{
529 return s_mapFailureCount.load(std::memory_order_relaxed);
530}
531
532#endif // QGC_HAS_GST_DMABUF_GPU_PATH
#define QGC_LOGGING_CATEGORY(name, categoryStr)
QByteArray format(const QList< LogEntry > &entries, int fmt)