15#if defined(QGC_HAS_GST_DMABUF_GPU_PATH)
17#include <QtCore/QByteArray>
18#include <QtCore/QLoggingCategory>
19#include <QtCore/QMutex>
20#include <QtCore/QScopeGuard>
21#include <QtCore/QSize>
22#include <QtGui/QGuiApplication>
23#include <QtGui/QOpenGLContext>
24#include <QtGui/QOpenGLFunctions>
25#include <QtGui/qopenglcontext_platform.h>
26#include <gst/allocators/gstdmabuf.h>
27#include <gst/video/video.h>
28#include <private/qvideotexturehelper_p.h>
32#if GST_CHECK_VERSION(1, 24, 0)
33#include <gst/video/video-info-dma.h>
37#include <GLES2/gl2ext.h>
41#include <drm_fourcc.h>
49#if __has_include(<linux/dma-buf.h>)
50#include <linux/dma-buf.h>
52#ifdef DMA_BUF_IOCTL_EXPORT_SYNC_FILE
53#define QGC_HAS_DMABUF_EXPORT_SYNC 1
57#ifndef EGL_ANDROID_native_fence_sync
58#define EGL_SYNC_NATIVE_FENCE_ANDROID 0x3144
59#define EGL_SYNC_NATIVE_FENCE_FD_ANDROID 0x3145
60#define EGL_NO_NATIVE_FENCE_FD_ANDROID -1
69std::atomic<bool> s_loggedBadBackend{
false};
72std::atomic<bool> s_permanentlyDisabled{
false};
74void maybePermanentlyDisable(EGLDisplay eglDpy,
int plane,
const char* pathTag)
76 if (!s_permanentlyDisabled.exchange(
true, std::memory_order_relaxed)) {
79 qCWarning(GstDmaBufLog) <<
"eglCreateImage returned EGL_BAD_MATCH on" << pathTag <<
"plane" << plane
80 <<
"— disabling DMABuf zero-copy for this process"
81 <<
"(EGL_VENDOR=" << eglQueryString(eglDpy, EGL_VENDOR)
82 <<
"EGL_VERSION=" << eglQueryString(eglDpy, EGL_VERSION) <<
")";
87std::atomic<EGLDisplay> s_cachedDisplay{EGL_NO_DISPLAY};
88std::atomic<bool> s_loggedSingleFdDisabled{
false};
89std::atomic<bool> s_loggedFenceTimeout{
false};
92std::atomic<bool> s_bindValidated{
false};
93std::atomic<bool> s_loggedBindFailed{
false};
95constexpr const char* kModifiersExt =
"EGL_EXT_image_dma_buf_import_modifiers";
97constexpr const char* kFenceSyncExt =
"EGL_KHR_fence_sync";
99constexpr const char* kNativeFenceExt =
"EGL_ANDROID_native_fence_sync";
103bool imageCacheEnabled() noexcept
108bool texStorageImportAllowed(
bool contextIsOpenGles,
bool hasTexStorageExt, guint64 drmModifier)
noexcept
110 return drmModifier == 0 && !contextIsOpenGles && hasTexStorageExt;
113bool directGlImportAllowed(
bool hasModifiersExt, guint64 drmModifier)
noexcept
115 Q_UNUSED(hasModifiersExt);
116 return drmModifier == 0;
122 guint64 modifier = 0;
127 std::array<gsize, kMaxPlanes> offsets{};
128 std::array<int, kMaxPlanes> strides{};
130 bool operator==(
const DmaImageKey& o)
const noexcept
132 return fd == o.fd && modifier == o.modifier && width == o.width && height == o.height && fourcc == o.fourcc &&
133 planeCount == o.planeCount && offsets == o.offsets && strides == o.strides;
138void hashCombine(std::size_t& h,
const T& value)
noexcept
140 h ^= std::hash<T>{}(value) + 0x9e3779b97f4a7c15ULL + (h << 6) + (h >> 2);
143struct DmaImageKeyHash
145 std::size_t operator()(
const DmaImageKey& k)
const noexcept
147 std::size_t h = std::hash<int>{}(k.fd);
148 hashCombine(h, k.modifier);
149 hashCombine(h, k.width);
150 hashCombine(h, k.height);
151 hashCombine(h, k.fourcc);
152 hashCombine(h, k.planeCount);
153 for (
int p = 0; p < k.planeCount; ++p) {
154 hashCombine(h, k.offsets[p]);
155 hashCombine(h, k.strides[p]);
163 EGLDisplay display = EGL_NO_DISPLAY;
164 EGLImage image = EGL_NO_IMAGE_KHR;
165 GstMemory* mem =
nullptr;
168constexpr int kImageCacheCapacity = 8;
170QMutex s_dmaCacheMutex;
172std::vector<std::pair<EGLDisplay, EGLImage>> s_orphanedImages;
174void onSourceMemoryFinalized(gpointer userData, GstMiniObject* finalizedMem);
177void drainOrphanedImagesLocked()
179 for (
const auto& [display, image] : s_orphanedImages) {
180 if (image != EGL_NO_IMAGE_KHR && display != EGL_NO_DISPLAY) {
181 eglDestroyImage(display, image);
184 s_orphanedImages.clear();
190void destroyCachedDmaImage(
const DmaImageKey&, DmaCachedImage& entry)
192 const bool onRenderThread = QOpenGLContext::currentContext() !=
nullptr;
193 if (entry.image != EGL_NO_IMAGE_KHR && entry.display != EGL_NO_DISPLAY) {
194 if (onRenderThread) {
195 eglDestroyImage(entry.display, entry.image);
197 s_orphanedImages.emplace_back(entry.display, entry.image);
200 if (entry.mem && onRenderThread) {
201 gst_mini_object_weak_unref(GST_MINI_OBJECT(entry.mem), onSourceMemoryFinalized,
nullptr);
206 destroyCachedDmaImage};
210void onSourceMemoryFinalized(gpointer userData, GstMiniObject* finalizedMem)
212 const QMutexLocker lock(&s_dmaCacheMutex);
213 s_dmaImageCache.eraseIf([&](
const DmaImageKey&,
const DmaCachedImage& e) {
214 return e.mem ==
reinterpret_cast<GstMemory*
>(finalizedMem);
220void clearDmaImageCacheLocked(EGLDisplay eglDpy)
222 s_dmaImageCache.clear();
223 drainOrphanedImagesLocked();
227EGLImage cachedDmaImageLocked(
const DmaImageKey& key, EGLDisplay eglDpy)
229 if (DmaCachedImage* entry = s_dmaImageCache.find(key);
230 entry && entry->display == eglDpy && entry->image != EGL_NO_IMAGE_KHR) {
233 return EGL_NO_IMAGE_KHR;
236void insertDmaImageLocked(
const DmaImageKey& key, EGLDisplay eglDpy, EGLImage image, GstMemory* mem)
239 gst_mini_object_weak_ref(GST_MINI_OBJECT(mem), onSourceMemoryFinalized,
nullptr);
241 s_dmaImageCache.insert(key, DmaCachedImage{eglDpy, image, mem});
244std::atomic<bool> s_dmaCacheResetPending{
false};
246bool singleFdImportEnabled() noexcept
252static constexpr EGLint kPlFd[4] = {
253 EGL_DMA_BUF_PLANE0_FD_EXT,
254 EGL_DMA_BUF_PLANE1_FD_EXT,
255 EGL_DMA_BUF_PLANE2_FD_EXT,
256 EGL_DMA_BUF_PLANE3_FD_EXT,
258static constexpr EGLint kPlOffset[4] = {
259 EGL_DMA_BUF_PLANE0_OFFSET_EXT,
260 EGL_DMA_BUF_PLANE1_OFFSET_EXT,
261 EGL_DMA_BUF_PLANE2_OFFSET_EXT,
262 EGL_DMA_BUF_PLANE3_OFFSET_EXT,
264static constexpr EGLint kPlPitch[4] = {
265 EGL_DMA_BUF_PLANE0_PITCH_EXT,
266 EGL_DMA_BUF_PLANE1_PITCH_EXT,
267 EGL_DMA_BUF_PLANE2_PITCH_EXT,
268 EGL_DMA_BUF_PLANE3_PITCH_EXT,
270static constexpr EGLint kPlModLo[4] = {
271 EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT,
272 EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT,
273 EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT,
274 EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT,
276static constexpr EGLint kPlModHi[4] = {
277 EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT,
278 EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT,
279 EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT,
280 EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT,
286 FrameTextures(QRhi* rhi, QSize size, QVideoFrameFormat::PixelFormat pixelFormat,
287 std::array<GLuint, kMaxPlanes> names,
int count)
291 ~FrameTextures()
override
303 void releaseGLTextures()
305 if (_released || !_rhi || _count == 0)
309 _rhi->makeThreadLocalNativeContextCurrent();
310 if (QOpenGLContext* ctx = QOpenGLContext::currentContext()) {
311 ctx->functions()->glDeleteTextures(
int(_count), _names.data());
316 bool _released =
false;
321QVideoFrameFormat applyEglfsFormatQuirk(
const QVideoFrameFormat& format)
323 static const bool isEglfsQPA = QGuiApplication::platformName() == QLatin1String(
"eglfs");
326 const auto fmt =
format.pixelFormat();
327 if (fmt != QVideoFrameFormat::Format_UYVY && fmt != QVideoFrameFormat::Format_YUYV) {
330 QVideoFrameFormat spoofed(
format.frameSize(), QVideoFrameFormat::Format_RGBA8888);
331 spoofed.setStreamFrameRate(
format.streamFrameRate());
332 spoofed.setColorRange(
format.colorRange());
333 spoofed.setColorSpace(
format.colorSpace());
334 spoofed.setColorTransfer(
format.colorTransfer());
335 spoofed.setViewport(
format.viewport());
339std::atomic<bool> s_loggedExplicitFence{
false};
344bool tryExplicitFenceWait(EGLDisplay eglDpy,
int dmaFd)
346#if defined(QGC_HAS_DMABUF_EXPORT_SYNC)
347 if (eglDpy == EGL_NO_DISPLAY || dmaFd < 0) {
350 if (!GstEglHelpers::displaySupportsExtension(eglDpy, kNativeFenceExt)) {
353 static const auto createSyncKHR =
reinterpret_cast<PFNEGLCREATESYNCKHRPROC
>(eglGetProcAddress(
"eglCreateSyncKHR"));
354 static const auto waitSyncKHR =
reinterpret_cast<PFNEGLWAITSYNCKHRPROC
>(eglGetProcAddress(
"eglWaitSyncKHR"));
355 static const auto destroySyncKHR =
356 reinterpret_cast<PFNEGLDESTROYSYNCKHRPROC
>(eglGetProcAddress(
"eglDestroySyncKHR"));
357 if (!createSyncKHR || !waitSyncKHR || !destroySyncKHR) {
361 dma_buf_export_sync_file req = {};
362 req.flags = DMA_BUF_SYNC_READ;
364 if (ioctl(dmaFd, DMA_BUF_IOCTL_EXPORT_SYNC_FILE, &req) != 0 || req.fd < 0) {
370 const EGLint attribs[] = {EGL_SYNC_NATIVE_FENCE_FD_ANDROID, req.fd, EGL_NONE};
371 EGLSyncKHR sync = createSyncKHR(eglDpy, EGL_SYNC_NATIVE_FENCE_ANDROID, attribs);
372 if (sync == EGL_NO_SYNC_KHR) {
376 const EGLint waited = waitSyncKHR(eglDpy, sync, 0);
377 destroySyncKHR(eglDpy, sync);
378 if (waited != EGL_TRUE) {
382 "DMABuf: using producer dma-buf fence for GPU-side sync (mmap barrier skipped)");
395GstDmaBufVideoBuffer::GstDmaBufVideoBuffer(GstSample* sample,
const GstVideoInfo& videoInfo,
396 const QVideoFrameFormat& format, EGLDisplay eglDisplay)
398 _eglDisplay(eglDisplay)
400#if GST_CHECK_VERSION(1, 24, 0)
401 if (GstCaps* caps = gst_sample_get_caps(sample); caps && gst_video_is_dma_drm_caps(caps)) {
402 GstVideoInfoDmaDrm drmInfo = {};
403 gst_video_info_dma_drm_init(&drmInfo);
406 if (gst_video_info_dma_drm_from_caps(&drmInfo, caps) && drmInfo.drm_modifier != DRM_FORMAT_MOD_INVALID) {
407 _drmModifier = drmInfo.drm_modifier;
413void GstDmaBufVideoBuffer::resetCachedState() noexcept
415 s_cachedDisplay.store(EGL_NO_DISPLAY, std::memory_order_release);
416 s_permanentlyDisabled.store(
false, std::memory_order_release);
417 s_loggedSingleFdDisabled.store(
false, std::memory_order_release);
418#if defined(QGC_HAS_GST_VULKAN_GPU_PATH)
419 GstDmaBufVulkan::resetLoggedState();
421 s_loggedBadBackend.store(
false, std::memory_order_release);
422 s_loggedFenceTimeout.store(
false, std::memory_order_release);
423 s_bindValidated.store(
false, std::memory_order_release);
424 s_loggedBindFailed.store(
false, std::memory_order_release);
425 if (QOpenGLContext::currentContext()) {
428 const QMutexLocker lock(&s_dmaCacheMutex);
429 clearDmaImageCacheLocked(EGL_NO_DISPLAY);
430 s_dmaCacheResetPending.store(
false, std::memory_order_release);
434 s_dmaCacheResetPending.store(
true, std::memory_order_release);
439struct DmaBufCacheResetRegistrar
441 DmaBufCacheResetRegistrar()
443 GstContextBridgeRegistry::registerCacheReset(&GstDmaBufVideoBuffer::resetCachedState);
447const DmaBufCacheResetRegistrar s_dmaBufCacheResetRegistrar;
450#ifdef QGC_GST_BUILD_TESTING
451bool GstDmaBufVideoBuffer::singleFdImportEnabledForTest() noexcept
455 const QByteArray v = qgetenv(
"QGC_GST_DMABUF_SINGLE_EGLIMAGE").trimmed().toLower();
459 return v !=
"0" && v !=
"false" && v !=
"off" && v !=
"no";
462bool GstDmaBufVideoBuffer::texStorageImportAllowedForTest(
bool contextIsOpenGles,
bool hasTexStorageExt,
463 guint64 drmModifier)
noexcept
465 return texStorageImportAllowed(contextIsOpenGles, hasTexStorageExt, drmModifier);
468bool GstDmaBufVideoBuffer::directGlImportAllowedForTest(
bool hasModifiersExt, guint64 drmModifier)
noexcept
470 return directGlImportAllowed(hasModifiersExt, drmModifier);
474bool GstDmaBufVideoBuffer::validatePlaneHandles()
const
477 if (s_permanentlyDisabled.load(std::memory_order_relaxed)) {
480 const bool planesOk = validatePlanes([](GstMemory* mem) {
481 if (!mem || !gst_is_dmabuf_memory(mem))
483 return gst_dmabuf_memory_get_fd(mem) >= 0;
489#if GST_CHECK_VERSION(1, 24, 0)
491 if (_drmModifier != 0 &&
492 (_eglDisplay == EGL_NO_DISPLAY || !GstEglHelpers::displaySupportsExtension(_eglDisplay, kModifiersExt))) {
499QVideoFrameTexturesUPtr GstDmaBufVideoBuffer::mapTextures(QRhi& rhi, QVideoFrameTexturesUPtr& )
503 if (!rhi.thread()->isCurrentThread()) {
512 if (s_permanentlyDisabled.load(std::memory_order_relaxed)) {
517 const guint64 drmModifier = _drmModifier;
523 const bool needLinearFence = (drmModifier == 0 && !skipLinearFence);
525#if defined(QGC_HAS_GST_VULKAN_GPU_PATH)
526 if (rhi.backend() == QRhi::Vulkan) {
527 return importVulkan(rhi);
530 if (rhi.backend() != QRhi::OpenGLES2) {
532 "QRhi backend is not OpenGLES2/Vulkan; zero-copy DMABuf path unsupported");
538 rhi.makeThreadLocalNativeContextCurrent();
543 if (Q_UNLIKELY(GstDmaBufLog().isDebugEnabled())) {
544 const GLenum glErr = glGetError();
545 const EGLint eglErr = eglGetError();
546 if (glErr != GL_NO_ERROR || eglErr != EGL_SUCCESS) {
547 qCDebug(GstDmaBufLog) <<
"mapTextures entry, latent glError=" << Qt::hex << glErr <<
"eglError=" << eglErr;
551 const auto* nativeHandles =
static_cast<const QRhiGles2NativeHandles*
>(rhi.nativeHandles());
552 if (!nativeHandles || !nativeHandles->context) {
553 qCWarning(GstDmaBufLog) <<
"QRhi exposes no GL context";
559 EGLDisplay eglDpy = s_cachedDisplay.load(std::memory_order_acquire);
560 if (eglDpy == EGL_NO_DISPLAY) {
561 eglDpy = GstEglHelpers::resolveEglDisplay(nativeHandles->context);
562 if (eglDpy == EGL_NO_DISPLAY)
563 eglDpy = _eglDisplay;
564 if (eglDpy == EGL_NO_DISPLAY) {
568 EGLDisplay expected = EGL_NO_DISPLAY;
569 s_cachedDisplay.compare_exchange_strong(expected, eglDpy, std::memory_order_release);
573 if (imageCacheEnabled()) {
574 const bool resetPending = s_dmaCacheResetPending.exchange(
false, std::memory_order_acq_rel);
575 const QMutexLocker lock(&s_dmaCacheMutex);
576 if (!s_orphanedImages.empty()) {
577 drainOrphanedImagesLocked();
580 clearDmaImageCacheLocked(eglDpy);
586 if (needLinearFence) {
590 if (GstBuffer* fenceBuf = gst_sample_get_buffer(_sample)) {
591 if (GstMemory* m0 = gst_buffer_peek_memory(fenceBuf, 0); m0 && gst_is_dmabuf_memory(m0)) {
592 producerFd = gst_dmabuf_memory_get_fd(m0);
595 const bool producerFenced = tryExplicitFenceWait(eglDpy, producerFd);
596 if (!producerFenced) {
597 static std::pair<EGLDisplay, bool> fenceExtProbe{EGL_NO_DISPLAY,
false};
598 if (fenceExtProbe.first != eglDpy) {
599 fenceExtProbe = {eglDpy, GstEglHelpers::displaySupportsExtension(eglDpy, kFenceSyncExt)};
601 const bool hasFenceExt = fenceExtProbe.second;
602 static const auto createSyncKHR =
603 reinterpret_cast<PFNEGLCREATESYNCKHRPROC
>(eglGetProcAddress(
"eglCreateSyncKHR"));
604 static const auto clientWaitSyncKHR =
605 reinterpret_cast<PFNEGLCLIENTWAITSYNCKHRPROC
>(eglGetProcAddress(
"eglClientWaitSyncKHR"));
606 static const auto destroySyncKHR =
607 reinterpret_cast<PFNEGLDESTROYSYNCKHRPROC
>(eglGetProcAddress(
"eglDestroySyncKHR"));
609 if (hasFenceExt && createSyncKHR && clientWaitSyncKHR && destroySyncKHR) {
610 if (EGLSyncKHR sync = createSyncKHR(eglDpy, EGL_SYNC_FENCE_KHR,
nullptr); sync != EGL_NO_SYNC_KHR) {
613 const EGLint waitResult = clientWaitSyncKHR(eglDpy, sync, EGL_SYNC_FLUSH_COMMANDS_BIT_KHR,
614 static_cast<EGLTimeKHR
>(100000000));
615 destroySyncKHR(eglDpy, sync);
617 if (waitResult == EGL_CONDITION_SATISFIED_KHR) {
624 GstDmaBufLog, s_loggedFenceTimeout,
625 "EGL fence wait did not satisfy within 100 ms (GPU stall?) — using mmap barrier");
632 if (GstBuffer* syncBuf = gst_sample_get_buffer(_sample)) {
633 GstVideoFrame syncFrame;
634 if (gst_video_frame_map(&syncFrame, &_videoInfo, syncBuf, GST_MAP_READ)) {
635 gst_video_frame_unmap(&syncFrame);
644 GstBuffer* buffer = gst_sample_get_buffer(_sample);
645 if (!buffer || !gst_is_dmabuf_memory(gst_buffer_peek_memory(buffer, 0))) {
648 const bool hasModifiersExt = GstEglHelpers::displaySupportsExtension(eglDpy, kModifiersExt);
649 if (!directGlImportAllowed(hasModifiersExt, drmModifier)) {
652 static std::atomic<bool> warnedModifierRejected{
false};
653 if (!warnedModifierRejected.exchange(
true, std::memory_order_relaxed)) {
654 qCWarning(GstDmaBufLog)
655 <<
"DMABuf modifier" << Qt::hex << drmModifier
656 <<
"rejected for direct GL import"
657 << (hasModifiersExt ?
"(EGL modifier import present but disabled for safety)"
658 :
"(EGL_EXT_image_dma_buf_import_modifiers absent)")
659 <<
"— falling back to CPU";
666 static const auto eglImageTargetTexture2D =
667 reinterpret_cast<PFNGLEGLIMAGETARGETTEXTURE2DOESPROC
>(eglGetProcAddress(
"glEGLImageTargetTexture2DOES"));
668 if (!eglImageTargetTexture2D) {
669 qCWarning(GstDmaBufLog) <<
"glEGLImageTargetTexture2DOES unavailable";
677 PFNGLEGLIMAGETARGETTEXSTORAGEEXTPROC eglImageTargetTexStorage =
nullptr;
678 if (
const QOpenGLContext* ctx = QOpenGLContext::currentContext()) {
679 const bool useTexStorage =
680 texStorageImportAllowed(ctx->isOpenGLES(),
681 ctx->hasExtension(QByteArrayLiteral(
"GL_EXT_EGL_image_storage")), drmModifier);
682 eglImageTargetTexStorage = useTexStorage
683 ?
reinterpret_cast<PFNGLEGLIMAGETARGETTEXSTORAGEEXTPROC
>(
684 eglGetProcAddress(
"glEGLImageTargetTexStorageEXT"))
687 const auto bindEglImage = [&](GLenum target, EGLImage img) {
688 if (eglImageTargetTexStorage)
689 eglImageTargetTexStorage(target, img,
nullptr);
691 eglImageTargetTexture2D(target, img);
695 const bool validateBind = !s_bindValidated.load(std::memory_order_relaxed);
700 GstVideoMeta* vmeta = gst_buffer_get_video_meta(buffer);
701 const int planeCount = std::clamp(
int(GST_VIDEO_INFO_N_PLANES(&_videoInfo)), 1, kMaxPlanes);
702 const int memCount = int(gst_buffer_n_memory(buffer));
704 const int cachedSingleFourcc = (memCount == 1) ? GstHw::drmFourccForSingleFd(_videoInfo) : -1;
705 bool singleFdPacking = (cachedSingleFourcc != -1);
706 if (singleFdPacking && !singleFdImportEnabled()) {
707 if (!s_loggedSingleFdDisabled.exchange(
true, std::memory_order_relaxed)) {
708 qCInfo(GstDmaBufLog) <<
"Single-EGLImage DMABuf import disabled by"
709 <<
"QGC_GST_DMABUF_SINGLE_EGLIMAGE; using per-plane import"
710 <<
"when the buffer layout permits, otherwise CPU fallback";
712 singleFdPacking =
false;
714 if (memCount != planeCount && !singleFdPacking) {
721 _format.frameSize())) {
727 std::array<GLuint, kMaxPlanes> names{};
728 QOpenGLFunctions functions(nativeHandles->context);
729 functions.glGenTextures(planeCount, names.data());
732 const EGLAttrib modLo =
static_cast<EGLAttrib
>(drmModifier & 0xFFFFFFFFu);
733 const EGLAttrib modHi =
static_cast<EGLAttrib
>((drmModifier >> 32) & 0xFFFFFFFFu);
739 if (singleFdPacking) {
740 if (planeCount > kMaxPlanes) {
741 qCWarning(GstDmaBufLog) <<
"single-fd format has" << planeCount <<
"planes (>" <<
kMaxPlanes
742 <<
"); falling back to per-plane";
745 const int singleFourcc = cachedSingleFourcc;
746 if (singleFourcc == -1) {
749 const int fd0 = gst_dmabuf_memory_get_fd(gst_buffer_peek_memory(buffer, 0));
751 EGLAttrib attribs[2 * (3 +
kMaxPlanes * 5) + 1];
753 bool attribOverflow =
false;
754 auto attribPush = [&](EGLAttrib k, EGLAttrib v) {
755 if (n >=
int(std::size(attribs)) - 1) {
756 attribOverflow =
true;
762 attribPush(EGL_WIDTH, GST_VIDEO_INFO_WIDTH(&_videoInfo));
763 attribPush(EGL_HEIGHT, GST_VIDEO_INFO_HEIGHT(&_videoInfo));
764 attribPush(EGL_LINUX_DRM_FOURCC_EXT, singleFourcc);
765 std::array<gsize, kMaxPlanes> planeOffsets{};
766 std::array<int, kMaxPlanes> planeStrides{};
767 for (
int p = 0; p < planeCount; ++p) {
768 const auto off = vmeta ? vmeta->offset[p] : GST_VIDEO_INFO_PLANE_OFFSET(&_videoInfo, p);
769 const auto pitch = vmeta ? vmeta->stride[p] : GST_VIDEO_INFO_PLANE_STRIDE(&_videoInfo, p);
771 static_cast<quint64
>(off) >
static_cast<quint64
>((std::numeric_limits<EGLAttrib>::max)())) {
772 qCWarning(GstDmaBufLog) <<
"implausible plane stride/offset (stride=" << pitch <<
"offset=" << off
773 <<
"); CPU fallback";
777 planeOffsets[p] = off;
778 planeStrides[p] = pitch;
779 attribPush(kPlFd[p], fd0);
780 attribPush(kPlOffset[p],
static_cast<EGLAttrib
>(off));
781 attribPush(kPlPitch[p],
static_cast<EGLAttrib
>(pitch));
782 if (hasModifiersExt) {
783 attribPush(kPlModLo[p], modLo);
784 attribPush(kPlModHi[p], modHi);
787 if (attribOverflow) {
788 qCWarning(GstDmaBufLog) <<
"single-fd EGL attrib list overflow; falling back to per-plane";
791 attribs[n++] = EGL_NONE;
792 const bool cacheOn = imageCacheEnabled();
793 const DmaImageKey key{fd0,
795 GST_VIDEO_INFO_WIDTH(&_videoInfo),
796 GST_VIDEO_INFO_HEIGHT(&_videoInfo),
801 EGLImage singleImage = EGL_NO_IMAGE_KHR;
802 bool singleImageCached =
false;
804 const QMutexLocker lock(&s_dmaCacheMutex);
805 singleImage = cachedDmaImageLocked(key, eglDpy);
806 singleImageCached = (singleImage != EGL_NO_IMAGE_KHR);
808 if (singleImage == EGL_NO_IMAGE_KHR) {
809 singleImage = eglCreateImage(eglDpy, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT,
nullptr, attribs);
813 }
else if (cacheOn) {
816 if (singleImage != EGL_NO_IMAGE_KHR) {
817 bool bindFailed =
false;
818 for (
int p = 0; p < planeCount; ++p) {
819 functions.glBindTexture(GL_TEXTURE_2D, names[p]);
820 bindEglImage(GL_TEXTURE_2D, singleImage);
822 if (Q_UNLIKELY(validateBind || GstDmaBufLog().isDebugEnabled())) {
823 if (
const GLenum glErr = functions.glGetError(); glErr != GL_NO_ERROR) {
826 "single-fd EGLImage bind failed plane"
827 << p <<
"glError=" << Qt::hex << glErr <<
"eglError=" << eglGetError()
828 <<
"— CPU fallback");
832 qCDebug(GstDmaBufLog) <<
"single-fd eglImageTargetTexture2D plane" << p <<
"glError=" << Qt::hex
833 << glErr <<
"eglError=" << eglGetError();
839 if (cacheOn && singleImageCached) {
840 const QMutexLocker lock(&s_dmaCacheMutex);
841 s_dmaImageCache.eraseIf([&](
const DmaImageKey& k,
const DmaCachedImage&) {
return k == key; });
843 eglDestroyImage(eglDpy, singleImage);
848 if (cacheOn && !singleImageCached) {
849 const QMutexLocker lock(&s_dmaCacheMutex);
850 insertDmaImageLocked(key, eglDpy, singleImage, gst_buffer_peek_memory(buffer, 0));
851 }
else if (!cacheOn) {
852 eglDestroyImage(eglDpy, singleImage);
856#if defined(DRM_FORMAT_Y210) || defined(DRM_FORMAT_Y410)
859 static std::atomic<bool> s_warnedY210{
false};
860 static std::atomic<bool> s_warnedY410{
false};
861#if defined(DRM_FORMAT_Y210)
862 if (singleFourcc == DRM_FORMAT_Y210 && !s_warnedY210.exchange(
true, std::memory_order_relaxed))
863 qCWarning(GstDmaBufLog) <<
"EGL DMABuf import of Y210 unsupported on this driver; frames will be black";
865#if defined(DRM_FORMAT_Y410)
866 if (singleFourcc == DRM_FORMAT_Y410 && !s_warnedY410.exchange(
true, std::memory_order_relaxed))
867 qCWarning(GstDmaBufLog) <<
"EGL DMABuf import of Y410 unsupported on this driver; frames will be black";
871 const EGLint singleFdErr = eglGetError();
872 if (drmModifier != 0) {
875 qCWarning(GstDmaBufLog) <<
"single-fd tiled eglCreateImage failed (" << Qt::hex << singleFdErr
876 <<
") — no per-plane fallback for tiled";
877 if (singleFdErr == EGL_BAD_MATCH) {
878 maybePermanentlyDisable(eglDpy, 0,
"single-fd-tiled");
883 qCWarning(GstDmaBufLog) <<
"single-fd eglCreateImage failed (" << Qt::hex << singleFdErr
884 <<
"); falling back to per-plane";
885 if (singleFdErr == EGL_BAD_MATCH) {
888 maybePermanentlyDisable(eglDpy, 0,
"single-fd");
893 for (
int i = 0; i < planeCount && ok; ++i) {
894 const int memIdx = singleFdPacking ? 0 : i;
895 const int fd = gst_dmabuf_memory_get_fd(gst_buffer_peek_memory(buffer, memIdx));
898 singleFdPacking ? (vmeta ? vmeta->offset[i] : GST_VIDEO_INFO_PLANE_OFFSET(&_videoInfo, i)) : 0;
899 const auto stride = vmeta ? vmeta->stride[i] : GST_VIDEO_INFO_PLANE_STRIDE(&_videoInfo, i);
901 static_cast<quint64
>(offset) >
static_cast<quint64
>((std::numeric_limits<EGLAttrib>::max)())) {
902 qCWarning(GstDmaBufLog) <<
"implausible plane stride/offset (stride=" << stride <<
"offset=" << offset
903 <<
"); CPU fallback";
907 const int planeWidth = GST_VIDEO_INFO_COMP_WIDTH(&_videoInfo, i);
908 const int planeHeight = GST_VIDEO_INFO_COMP_HEIGHT(&_videoInfo, i);
909 const int fourcc = GstHw::drmFourccForPlane(_videoInfo, i);
911 qCWarning(GstDmaBufLog) <<
"no DRM fourcc for format"
912 << gst_video_format_to_string(GST_VIDEO_INFO_FORMAT(&_videoInfo));
918 EGLAttrib attribs[18];
920 bool attribOverflow =
false;
921 auto attribPush = [&](EGLAttrib k, EGLAttrib v) {
922 if (n >=
int(std::size(attribs)) - 1) {
923 attribOverflow =
true;
929 attribPush(EGL_WIDTH, planeWidth);
930 attribPush(EGL_HEIGHT, planeHeight);
931 attribPush(EGL_LINUX_DRM_FOURCC_EXT, fourcc);
932 attribPush(kPlFd[0], fd);
933 attribPush(kPlOffset[0],
static_cast<EGLAttrib
>(offset));
934 attribPush(kPlPitch[0],
static_cast<EGLAttrib
>(stride));
935 if (hasModifiersExt) {
938 attribPush(kPlModLo[0], modLo);
939 attribPush(kPlModHi[0], modHi);
941 if (attribOverflow) {
942 qCWarning(GstDmaBufLog) <<
"per-plane EGL attrib list overflow; CPU fallback";
946 attribs[n++] = EGL_NONE;
947 const bool cacheOn = imageCacheEnabled();
949 std::array<gsize, kMaxPlanes> planeOffsets{};
950 std::array<int, kMaxPlanes> planeStrides{};
951 planeOffsets[0] = offset;
952 planeStrides[0] = stride;
953 const DmaImageKey key{fd, drmModifier, planeWidth, planeHeight, fourcc, 1, planeOffsets, planeStrides};
954 EGLImage image = EGL_NO_IMAGE_KHR;
955 bool imageCached =
false;
957 const QMutexLocker lock(&s_dmaCacheMutex);
958 image = cachedDmaImageLocked(key, eglDpy);
959 imageCached = (image != EGL_NO_IMAGE_KHR);
961 if (image == EGL_NO_IMAGE_KHR) {
962 image = eglCreateImage(eglDpy, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT,
nullptr, attribs);
966 }
else if (cacheOn) {
969 if (image == EGL_NO_IMAGE_KHR) {
970 const EGLint eglErr = eglGetError();
971 qCWarning(GstDmaBufLog) <<
"eglCreateImage failed plane" << i <<
"err" << Qt::hex << eglErr;
972 if (eglErr == EGL_BAD_MATCH) {
973 maybePermanentlyDisable(eglDpy, i,
"per-plane");
978 functions.glBindTexture(GL_TEXTURE_2D, names[i]);
979 bindEglImage(GL_TEXTURE_2D, image);
980 if (Q_UNLIKELY(validateBind || GstDmaBufLog().isDebugEnabled())) {
981 if (
const GLenum glErr = functions.glGetError(); glErr != GL_NO_ERROR) {
984 "per-plane EGLImage bind failed plane"
985 << i <<
"glError=" << Qt::hex << glErr <<
"eglError=" << eglGetError()
986 <<
"— CPU fallback");
988 if (cacheOn && imageCached) {
989 const QMutexLocker lock(&s_dmaCacheMutex);
990 s_dmaImageCache.eraseIf([&](
const DmaImageKey& k,
const DmaCachedImage&) {
return k == key; });
992 eglDestroyImage(eglDpy, image);
997 qCDebug(GstDmaBufLog) <<
"per-plane eglImageTargetTexture2D plane" << i <<
"glError=" << Qt::hex
998 << glErr <<
"eglError=" << eglGetError();
1001 if (cacheOn && !imageCached) {
1002 const QMutexLocker lock(&s_dmaCacheMutex);
1003 insertDmaImageLocked(key, eglDpy, image, gst_buffer_peek_memory(buffer, memIdx));
1004 }
else if (!cacheOn) {
1005 eglDestroyImage(eglDpy, image);
1011 if (ok && validateBind) {
1012 s_bindValidated.store(
true, std::memory_order_relaxed);
1015 functions.glDeleteTextures(planeCount, names.data());
1019 auto frameTextures =
1020 std::make_unique<FrameTextures>(&rhi, _format.frameSize(), _format.pixelFormat(), names, planeCount);
1023 for (
int i = 0; i < planeCount; ++i) {
1024 if (!frameTextures->texture(i)) {
1025 qCWarning(GstDmaBufLog) <<
"FrameTextures plane" << i <<
"createFrom failed — dropping frame";
1030 frameTextures->setSourceSample(takeSample());
1031 return frameTextures;
#define QGC_HW_WARN_ONCE(LOGCAT, FLAG,...)
Logs once via qCWarning(LOGCAT) the first time FLAG flips true; subsequent trips are silent.
#define QGC_LOGGING_CATEGORY(name, categoryStr)
Shared base for GL-texture-backed QVideoFrameTextures wrappers (GLMemory and DMABuf-via-EGLImage).
void onFrameEndInvoked() override
RAII timer: records mapTextures() wall time into the path's EWMA on scope exit.
Common base for GStreamer-backed QHwVideoBuffer subclasses.
void recordSyncWait(HwVideoBufferPath path, bool gpuSide) noexcept
GL fence sync wait; split CPU-blocking vs GPU-side.
void recordFallbackReason(HwVideoBufferPath attemptedPath, HwFallbackReason reason) noexcept
Per-(path,reason) fallback accounting; lets a bug report show why a path demoted to CPU.
void recordImageCacheHit(HwVideoBufferPath path) noexcept
Native image/texture cache hit/miss accounting.
void recordImageCacheMiss(HwVideoBufferPath path) noexcept
void recordExplicitFenceWait(HwVideoBufferPath path) noexcept
DMABuf imported the producer's dma-buf/native fence and did a GPU-side wait (skipped the mmap barrier...
void recordFenceTimeout(HwVideoBufferPath path) noexcept
DMABuf EGL fence wait timed out (GPU stall) and fell through to the mmap barrier.
void recordMmapBarrierHit(HwVideoBufferPath path) noexcept
DMABuf mmap CPU-side completion barrier taken (no usable fence ext).
constexpr int kMaxPlanes
Matches GST_VIDEO_MAX_PLANES (gst-video pins it at 4); single source of truth for every per-platform ...
const HwBufferEnvConfig & hwBufferEnvConfig() noexcept
Lazily parses + logs the toggle config on first call; thread-safe via static-init guarantees.
bool dmaBufSingleEglImage