4#if defined(QGC_HAS_GST_DMABUF_GPU_PATH)
8#include <QtCore/QLoggingCategory>
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>
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>
27#include <GLES2/gl2ext.h>
28#include <drm_fourcc.h>
38constexpr int kMaxPlanes = 4;
41std::atomic<quint64> s_mapFailureCount{0};
42std::atomic<bool> s_loggedBadBackend{
false};
45QHash<EGLDisplay, bool> s_modExtCache;
47bool queryHasModifiersExt(EGLDisplay display)
49 QMutexLocker lock(&s_modExtMutex);
50 auto it = s_modExtCache.find(display);
51 if (it != s_modExtCache.end()) {
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);
71int drmFourccFor(
const GstVideoInfo *info,
int plane)
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;
78 constexpr int rgbaFourcc = DRM_FORMAT_RGBA8888;
79 constexpr int rgFourcc = DRM_FORMAT_RG88;
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:
93 case GST_VIDEO_FORMAT_GRAY8:
95 case GST_VIDEO_FORMAT_YUY2:
96 case GST_VIDEO_FORMAT_UYVY:
97 case GST_VIDEO_FORMAT_GRAY16_LE:
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;
115int drmFourccForSingleFd(
const GstVideoInfo *info)
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;
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;
129#ifdef DRM_FORMAT_VYUY
130 case GST_VIDEO_FORMAT_VYUY:
return DRM_FORMAT_VYUY;
133#ifdef DRM_FORMAT_Y210
134 case GST_VIDEO_FORMAT_Y210:
return DRM_FORMAT_Y210;
136#ifdef DRM_FORMAT_Y410
137 case GST_VIDEO_FORMAT_Y410:
return DRM_FORMAT_Y410;
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,
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,
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,
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,
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,
165class FrameTextures final :
public QVideoFrameTextures
168 FrameTextures(QRhi *rhi, QSize size, QVideoFrameFormat::PixelFormat pixelFormat,
169 std::array<GLuint, kMaxPlanes> names,
int count)
174 const auto *desc = QVideoTextureHelper::textureDescription(pixelFormat);
176 qCWarning(GstDmaBufLog) <<
"no QVideoTextureHelper description for format" << pixelFormat;
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),
184 if (_textures[i] && !_textures[i]->createFrom({_names[i], 0})) {
185 qCWarning(GstDmaBufLog) <<
"QRhiTexture::createFrom failed for plane" << i;
186 _textures[i].reset();
191 ~FrameTextures()
override
196 void onFrameEndInvoked()
override
201 QRhiTexture *texture(uint plane)
const override
203 return (
int(plane) < _count) ? _textures[plane].get() :
nullptr;
207 void releaseGLTextures()
209 if (_released || !_rhi || _count == 0)
return;
212 _rhi->makeThreadLocalNativeContextCurrent();
213 if (
auto *ctx = QOpenGLContext::currentContext()) {
214 ctx->functions()->glDeleteTextures(_count, _names.data());
218 QRhi *_rhi =
nullptr;
219 std::array<GLuint, kMaxPlanes> _names{};
221 bool _released =
false;
222 std::unique_ptr<QRhiTexture> _textures[kMaxPlanes];
227GstDmaBufVideoBuffer::GstDmaBufVideoBuffer(GstSample *sample,
228 const GstVideoInfo &videoInfo,
229 const QVideoFrameFormat &format,
230 EGLDisplay eglDisplay)
232 , _eglDisplay(eglDisplay)
236GstDmaBufVideoBuffer::~GstDmaBufVideoBuffer() =
default;
238QAbstractVideoBuffer::MapData GstDmaBufVideoBuffer::map(QVideoFrame::MapMode )
245bool GstDmaBufVideoBuffer::validatePlaneHandles()
const
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;
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) {
272QVideoFrameTexturesUPtr GstDmaBufVideoBuffer::mapTextures(QRhi &rhi, QVideoFrameTexturesUPtr & )
274 Q_ASSERT(rhi.thread()->isCurrentThread());
276 auto fail = []() -> QVideoFrameTexturesUPtr {
277 s_mapFailureCount.fetch_add(1, std::memory_order_relaxed);
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;
301 if (drmModifier == 0 ) {
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);
313 rhi.makeThreadLocalNativeContextCurrent();
318 EGLDisplay eglDpy = eglGetCurrentDisplay();
319 if (eglDpy == EGL_NO_DISPLAY) {
320 eglDpy = _eglDisplay;
323 if (eglDpy == EGL_NO_DISPLAY) {
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";
333 GstBuffer *buffer = gst_sample_get_buffer(_sample);
334 if (!buffer || !gst_is_dmabuf_memory(gst_buffer_peek_memory(buffer, 0))) {
337 const bool hasModifiersExt = queryHasModifiersExt(eglDpy);
338 if (drmModifier != 0) {
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 <<
")";
351 const auto *nativeHandles =
static_cast<const QRhiGles2NativeHandles *
>(rhi.nativeHandles());
352 if (!nativeHandles || !nativeHandles->context) {
353 qCWarning(GstDmaBufLog) <<
"QRhi exposes no GL context";
357 static const auto eglImageTargetTexture2D =
358 reinterpret_cast<PFNGLEGLIMAGETARGETTEXTURE2DOESPROC
>(
359 eglGetProcAddress(
"glEGLImageTargetTexture2DOES"));
360 if (!eglImageTargetTexture2D) {
361 qCWarning(GstDmaBufLog) <<
"glEGLImageTargetTexture2DOES unavailable";
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));
370 const int cachedSingleFourcc = (memCount == 1) ? drmFourccForSingleFd(&_videoInfo) : -1;
371 const bool singleFdPacking = (cachedSingleFourcc != -1);
372 if (memCount != planeCount && !singleFdPacking) {
380 std::array<GLuint, kMaxPlanes> names{};
381 QOpenGLFunctions functions(nativeHandles->context);
382 functions.glGenTextures(planeCount, names.data());
386 const EGLAttrib modLo =
static_cast<EGLAttrib
>(drmModifier & 0xFFFFFFFFu);
387 const EGLAttrib modHi =
static_cast<EGLAttrib
>((drmModifier >> 32) & 0xFFFFFFFFu);
393 if (singleFdPacking) {
394 if (planeCount > kMaxPlanes) {
395 qCWarning(GstDmaBufLog) <<
"single-fd format has" << planeCount
396 <<
"planes (>" << kMaxPlanes <<
"); falling back to per-plane";
399 const int singleFourcc = cachedSingleFourcc;
400 if (singleFourcc == -1) {
403 const int fd0 = gst_dmabuf_memory_get_fd(gst_buffer_peek_memory(buffer, 0));
405 EGLAttrib attribs[3 + kMaxPlanes * 5 + 1];
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) {
418 ATTRIB_PUSH(kPlModLo[p], modLo);
419 ATTRIB_PUSH(kPlModHi[p], modHi);
423 attribs[n++] = EGL_NONE;
424 EGLImage singleImage = eglCreateImage(eglDpy, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT,
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);
431 eglDestroyImage(eglDpy, singleImage);
434#if defined(DRM_FORMAT_Y210) || defined(DRM_FORMAT_Y410)
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";
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";
449 qCWarning(GstDmaBufLog) <<
"single-fd eglCreateImage failed (" << Qt::hex
450 << eglGetError() <<
"); falling back to per-plane";
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);
465 qCWarning(GstDmaBufLog) <<
"no DRM fourcc for format"
466 << gst_video_format_to_string(GST_VIDEO_INFO_FORMAT(&_videoInfo));
472 EGLAttrib attribs[18];
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);
483 ATTRIB_PUSH(kPlModHi[i], modHi);
486 attribs[n++] = EGL_NONE;
487 EGLImage image = eglCreateImage(eglDpy, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT,
489 if (image == EGL_NO_IMAGE_KHR) {
490 qCWarning(GstDmaBufLog) <<
"eglCreateImage failed plane" << i <<
"err"
491 << Qt::hex << eglGetError();
495 functions.glBindTexture(GL_TEXTURE_2D, names[i]);
496 eglImageTargetTexture2D(GL_TEXTURE_2D, image);
497 eglDestroyImage(eglDpy, image);
503 functions.glDeleteTextures(planeCount, names.data());
507 auto frameTextures = std::make_unique<FrameTextures>(&rhi, _format.frameSize(),
508 _format.pixelFormat(), names, planeCount);
512 for (
int i = 0; i < planeCount; ++i) {
513 if (!frameTextures->texture(i)) {
514 qCWarning(GstDmaBufLog) <<
"FrameTextures plane" << i <<
"createFrom failed — dropping frame";
519 return frameTextures;
522quint64 GstDmaBufVideoBuffer::takeMapFailureCount()
524 return s_mapFailureCount.exchange(0, std::memory_order_relaxed);
527quint64 GstDmaBufVideoBuffer::peekMapFailureCount()
529 return s_mapFailureCount.load(std::memory_order_relaxed);
#define QGC_LOGGING_CATEGORY(name, categoryStr)