84#include <Gfx/Graph/interop/DMABufImport.hpp>
85#include <Gfx/Graph/interop/DrmFourcc.hpp>
86#include <Gfx/Graph/interop/EglDmaBufImport.hpp>
89#include <QtGui/private/qrhi_p.h>
103#if QT_HAS_VULKAN && defined(VK_EXT_image_drm_format_modifier) \
104 && defined(VK_KHR_external_memory_fd) \
105 && QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
106#define SCORE_GFX_HAS_VK_DMABUF_IMPORT 1
109namespace score::gfx::interop
113struct DmaBufPlaneLayout
115 std::uint32_t offset{};
116 std::uint32_t pitch{};
127enum class DmaBufOrigin
141 std::uint64_t modifier{};
142 std::uint32_t offset{};
143 std::uint32_t pitch{};
154 std::uint32_t planeCount{0};
155 DmaBufPlaneLayout planes[3]{};
160struct DmaBufImportFormat
163 std::uint32_t drmFourcc{};
164 std::uint32_t bytesPerPixel{};
165#if defined(SCORE_GFX_HAS_VK_DMABUF_IMPORT)
166 VkFormat vk{VK_FORMAT_UNDEFINED};
170inline DmaBufImportFormat dmaBufFormatFor(QRhiTexture::Format f)
noexcept
173 DmaBufImportFormat r;
174 auto set = [&](std::uint32_t fourcc, std::uint32_t bpp
175#if defined(SCORE_GFX_HAS_VK_DMABUF_IMPORT)
181 r.drmFourcc = fourcc;
182 r.bytesPerPixel = bpp;
183#if defined(SCORE_GFX_HAS_VK_DMABUF_IMPORT)
188#if defined(SCORE_GFX_HAS_VK_DMABUF_IMPORT)
189#define SCORE_DMABUF_FMT(fourcc, bpp, vk) set(fourcc, bpp, vk)
191#define SCORE_DMABUF_FMT(fourcc, bpp, vk) set(fourcc, bpp)
196 case QRhiTexture::RGBA8:
197 SCORE_DMABUF_FMT(DRM_ABGR8888, 4, VK_FORMAT_R8G8B8A8_UNORM);
199 case QRhiTexture::BGRA8:
200 SCORE_DMABUF_FMT(DRM_ARGB8888, 4, VK_FORMAT_B8G8R8A8_UNORM);
202 case QRhiTexture::R8:
203 SCORE_DMABUF_FMT(DRM_R8, 1, VK_FORMAT_R8_UNORM);
205 case QRhiTexture::RG8:
206 SCORE_DMABUF_FMT(DRM_GR88, 2, VK_FORMAT_R8G8_UNORM);
208 case QRhiTexture::R16:
209 SCORE_DMABUF_FMT(DRM_R16, 2, VK_FORMAT_R16_UNORM);
211 case QRhiTexture::RG16:
212 SCORE_DMABUF_FMT(DRM_GR1616, 4, VK_FORMAT_R16G16_UNORM);
214 case QRhiTexture::RGB10A2:
216 DRM_ABGR2101010, 4, VK_FORMAT_A2B10G10R10_UNORM_PACK32);
221#undef SCORE_DMABUF_FMT
227dmaBufExplicitLayout(
const DmaBufSlotDesc& slot, std::size_t planes)
noexcept
229 return slot.planeCount >= planes;
234inline DmaBufPlaneLayout dmaBufPlaneLayout(
235 bool explicitLayout,
const DmaBufSlotDesc& slot, std::size_t i,
236 std::uint32_t derivedOffset, std::uint32_t derivedPitch)
noexcept
239 return slot.planes[i];
240 return {derivedOffset, derivedPitch};
251inline bool dmaBufUsesProducerPitch(
252 std::size_t derivedPlanes,
const DmaBufSlotDesc& slot)
noexcept
254 return derivedPlanes == 1 && slot.planeCount == 0 && slot.pitch > 0;
263inline std::uint32_t dmaBufMinPitch(
264 bool wantsExternal,
int width, std::uint32_t bytesPerPixel)
noexcept
266 return wantsExternal ? std::uint32_t(width)
267 :
std::uint32_t(width) * bytesPerPixel;
272inline constexpr std::uint32_t kNvidiaProprietaryDriverId = 4;
280inline bool dmaBufVulkanRungRefused(
281 DmaBufOrigin origin, std::uint32_t driverId)
noexcept
283 return origin == DmaBufOrigin::ForeignDevice
284 && driverId == kNvidiaProprietaryDriverId;
288struct DmaBufExternalPlanes
290 std::uint32_t count{};
291 std::uint32_t offsets[3]{};
292 std::uint32_t pitches[3]{};
298inline DmaBufExternalPlanes
299dmaBufExternalPlanes(
const DmaBufSlotDesc& slot)
noexcept
301 DmaBufExternalPlanes r;
302 r.count = std::min<std::uint32_t>(slot.planeCount, 3);
306 r.offsets[0] = slot.offset;
307 r.pitches[0] = slot.pitch;
310 for(std::uint32_t p = 0; p < r.count; ++p)
312 r.offsets[p] = slot.offset + slot.planes[p].offset;
313 r.pitches[p] = slot.planes[p].pitch;
318struct DmaBufImportCapture final : VideoCaptureStrategy
321 static constexpr std::size_t kMaxSlots = 32;
328 const char* tag, std::vector<DmaBufSlotDesc> slots,
329 DmaBufOrigin origin = DmaBufOrigin::ForeignDevice)
330 : m_slots{
std::move(slots)}
333 , m_name{m_tag +
"-dmabuf"}
337 ~DmaBufImportCapture()
override { release(); }
346 void requestExternalImage(std::uint32_t fourcc,
int height)
noexcept
348 m_wholeFrameFourcc = fourcc;
349 m_wholeFrameHeight = height;
354 bool usesExternalImage() const noexcept {
return m_external; }
356 const char* name() const noexcept
override {
return m_name.c_str(); }
358 std::size_t slotCount() const noexcept
override {
return m_slots.size(); }
362 void* slotBuffer(std::size_t)
const noexcept override {
return nullptr; }
364 QRhiTexture* outputTexture() const noexcept
override {
return m_tex; }
366 std::size_t outputPlaneCount() const noexcept
override
368 return m_planeInfo.empty() ? 1 : m_planeInfo.size();
371 QRhiTexture* outputPlane(std::size_t i)
const noexcept override
373 return i < m_planeInfo.size() ? m_planeTex[i] :
nullptr;
378 std::uint32_t importPitch(
const DmaBufSlotDesc& slot, std::size_t p)
const noexcept
380 if(dmaBufUsesProducerPitch(m_planeInfo.size(), slot))
382 return m_planeInfo[p].pitch;
385 bool init(
const VideoCaptureStrategyConfig& c)
override
388 if(!cfg.rhi || !cfg.outputTexture)
390 if(m_slots.empty() || m_slots.size() > kMaxSlots)
402 const bool explicitLayout
403 = !m_slots.empty() && dmaBufExplicitLayout(m_slots[0], cfg.planes.size());
404 if(cfg.planes.size() > 1)
406 std::uint32_t off = 0;
407 for(std::size_t i = 0; i < cfg.planes.size(); ++i)
409 auto* tex = cfg.planes[i];
412 qDebug() <<
"DmaBufImportCapture: decoder plane texture is null";
415 const auto psz = tex->pixelSize();
416 const auto pf = dmaBufFormatFor(tex->format());
419 qDebug() <<
"DmaBufImportCapture: no DMA-BUF mapping for plane format"
420 << int(tex->format());
423 const auto derivedPitch = std::uint32_t(psz.width()) * pf.bytesPerPixel;
424 const auto lay = dmaBufPlaneLayout(
425 explicitLayout, m_slots[0], i, off, derivedPitch);
426 const auto pitch = lay.pitch;
427 const auto planeOff = lay.offset;
428 if(pitch < derivedPitch)
430 qDebug() <<
"DmaBufImportCapture: plane" << i <<
"pitch" << pitch
431 <<
"cannot hold" << psz.width() <<
"px of"
432 << pf.bytesPerPixel <<
"bytes";
435 m_planeInfo.push_back(
436 PlaneInfo{tex->format(), pf, planeOff, pitch, psz.width(),
438 off = planeOff + pitch * std::uint32_t(psz.height());
444 const auto pf = dmaBufFormatFor(cfg.outputTexture->format());
447 qDebug() <<
"DmaBufImportCapture: no DMA-BUF mapping for QRhi format"
448 << int(cfg.outputTexture->format());
451 const auto sz0 = cfg.outputTexture->pixelSize();
452 m_planeInfo.push_back(
454 cfg.outputTexture->format(), pf, 0,
455 std::uint32_t(sz0.width()) * pf.bytesPerPixel, sz0.width(),
462 const auto sz = QSize{m_planeInfo[0].width, m_planeInfo[0].height};
463 const auto qfmt = m_planeInfo[0].qfmt;
464 const auto fmt = m_planeInfo[0].fmt;
468 if(m_planeInfo.size() > 1 && !explicitLayout)
470 const auto tight = std::uint32_t(sz.width()) * fmt.bytesPerPixel;
471 for(
const auto& s : m_slots)
475 qDebug() <<
"DmaBufImportCapture: planar source has a padded pitch"
476 << s.pitch <<
"(tight" << tight
477 <<
"); plane offsets cannot be derived, no dma-buf rung";
482 if(sz.width() <= 0 || sz.height() <= 0)
485 const bool wantsExternal = m_wholeFrameFourcc != 0;
487 = dmaBufMinPitch(wantsExternal, sz.width(), fmt.bytesPerPixel);
488 for(
const auto& s : m_slots)
490 if(s.fd < 0 || s.pitch < minPitch)
492 qDebug() <<
"DmaBufImportCapture: slot fd" << s.fd <<
"pitch" << s.pitch
493 <<
"cannot hold" << sz.width() <<
"x" << sz.height()
494 <<
"(need >=" << minPitch <<
")";
499 m_retireDepth =
static_cast<std::size_t
>(
500 cfg.rhi->resourceLimit(QRhi::FramesInFlight))
502 if(m_retireDepth + 2u > m_slots.size())
504 qDebug() <<
"DmaBufImportCapture: producer ring of" << m_slots.size()
505 <<
"is too shallow for" << m_retireDepth
506 <<
"frames in flight plus the device's own queue";
510#if defined(SCORE_GFX_HAS_VK_DMABUF_IMPORT)
511 if(DMABufPlaneImporter::isAvailable(*cfg.rhi))
520#if defined(VK_DRIVER_ID_NVIDIA_PROPRIETARY)
522 std::uint32_t(VK_DRIVER_ID_NVIDIA_PROPRIETARY)
523 == kNvidiaProprietaryDriverId);
525 if(dmaBufVulkanRungRefused(
526 m_origin, DMABufPlaneImporter::driverId(*cfg.rhi)))
528 qDebug() <<
"DmaBufImportCapture: NVIDIA proprietary Vulkan reads stale "
529 "bytes out of a dma-buf exported by a foreign device; no "
536 m_backend = Backend::Vulkan;
538 for(std::size_t i = 0; i < m_slots.size(); ++i)
540 const auto& s = m_slots[i];
541 for(std::size_t p = 0; p < m_planeInfo.size(); ++p)
543 const auto& pi = m_planeInfo[p];
544 if(!m_vk.importPlane(
545 m_vkSlots[i][p], s.fd, s.modifier, s.offset + pi.offset,
546 importPitch(s, p), pi.fmt.vk, pi.width, pi.height))
548 qDebug() <<
"DmaBufImportCapture: Vulkan import refused slot" << i
549 <<
"plane" << p <<
"fd" << s.fd <<
"modifier" << Qt::hex
560 if(EglDmaBufImporter::isAvailable(*cfg.rhi) && m_egl.init(*cfg.rhi))
562 m_backend = Backend::OpenGL;
569 m_external = cfg.planes.size() == 1 && m_wholeFrameFourcc != 0
570 && m_egl.hasExternalImage();
576 for(
const auto& pi : m_planeInfo)
578 if(!m_egl.canImportModifier(pi.fmt.drmFourcc, m_slots[0].modifier))
580 qDebug() <<
"DmaBufImportCapture: driver cannot sample fourcc"
581 << Qt::hex << pi.fmt.drmFourcc <<
"modifier"
582 << m_slots[0].modifier <<
"as a 2D texture; no EGL rung";
591 const std::size_t texCount = m_external ? 1u : m_planeInfo.size();
592 for(std::size_t p = 0; p < texCount; ++p)
594 m_glTexPlane[p] = m_external
595 ? EglDmaBufImporter::createExternalTexture()
596 :
score::gfx::createLinearClampGlTexture2D();
597 if(m_glTexPlane[p] == 0)
603 m_glTex = m_glTexPlane[0];
604 for(std::size_t i = 0; i < m_slots.size(); ++i)
606 const auto& s = m_slots[i];
609 const auto ext = dmaBufExternalPlanes(s);
610 if(!m_egl.importExternal(
611 m_eglSlots[i][0], m_glTexPlane[0], s.fd, s.modifier,
612 ext.offsets, ext.pitches, ext.count, m_wholeFrameFourcc,
613 m_planeInfo[0].width, m_wholeFrameHeight))
615 qDebug() <<
"DmaBufImportCapture: external EGL import refused slot"
616 << i <<
"fourcc" << Qt::hex << m_wholeFrameFourcc;
622 for(std::size_t p = 0; p < m_planeInfo.size(); ++p)
624 const auto& pi = m_planeInfo[p];
625 if(!m_egl.importPlane(
626 m_eglSlots[i][p], m_glTexPlane[p], s.fd, s.modifier,
627 s.offset + pi.offset, importPitch(s, p), pi.fmt.drmFourcc,
628 pi.width, pi.height))
630 qDebug() <<
"DmaBufImportCapture: EGL import refused slot" << i
631 <<
"plane" << p <<
"fd" << s.fd <<
"fourcc" << Qt::hex
632 << pi.fmt.drmFourcc <<
"modifier" << s.modifier;
639 m_name +=
"/NV12-OES";
643 if(m_backend == Backend::None)
650 = m_external ? QRhiTexture::ExternalOES : QRhiTexture::Flag{};
651 for(std::size_t p = 0; p < m_planeInfo.size(); ++p)
653 const auto& pi = m_planeInfo[p];
655 = cfg.rhi->newTexture(pi.qfmt, QSize{pi.width, pi.height}, 1, texFlags);
662 m_tex = m_planeTex[0];
672 void release()
override
674#if defined(SCORE_GFX_HAS_VK_DMABUF_IMPORT)
675 if(m_backend == Backend::Vulkan)
676 for(
auto& slot : m_vkSlots)
678 m_vk.cleanupPlane(p);
680 if(m_backend == Backend::OpenGL)
682 for(
auto& slot : m_eglSlots)
684 m_egl.cleanupPlane(p);
685 for(
auto& t : m_glTexPlane)
689 if(
auto* ctx = QOpenGLContext::currentContext())
690 if(
auto* funcs = ctx->extraFunctions())
691 funcs->glDeleteTextures(1, &t);
697 for(
auto& t : m_planeTex)
704 m_backend = Backend::None;
705 m_glTexBound =
false;
706 m_name = m_tag +
"-dmabuf";
708 m_returns.store(0, std::memory_order_relaxed);
716 bool ingestFrame(std::size_t i)
override
718 if(i >= m_slots.size())
721 = m_publisher.pending.exchange(
int(i), std::memory_order_acq_rel);
722 if(displaced >= 0 && displaced !=
int(i))
723 m_returns.fetch_or(1u <<
unsigned(displaced), std::memory_order_release);
729 std::uint32_t takeReturnedSlots() noexcept
731 return m_returns.exchange(0, std::memory_order_acquire);
734 void acquireForRender()
override
736 const int slot = m_publisher.consume();
737 if(slot < 0 || std::size_t(slot) >= m_slots.size())
741 if(m_held >= 0 && m_held != slot && m_retireN < kMaxSlots)
742 m_retire[m_retireN++] = Retired{m_held, m_acquisitions};
744 bindSlot(std::size_t(slot));
746 std::uint32_t freed = 0;
747 std::size_t keep = 0;
748 for(std::size_t i = 0; i < m_retireN; ++i)
750 if(m_acquisitions - m_retire[i].at >= m_retireDepth)
751 freed |= 1u << unsigned(m_retire[i].slot);
753 m_retire[keep++] = m_retire[i];
757 m_returns.fetch_or(freed, std::memory_order_release);
760 bool supportsSlotSelection() const noexcept
override {
return true; }
762 bool acquireSlotForRender(
763 std::size_t slot, QRhiResourceUpdateBatch&, QRhiCommandBuffer*)
override
765 if(slot >= m_slots.size())
768 return bindSlot(slot);
771 void releaseAfterRender()
override { }
782 bool bindSlot(std::size_t i)
786#if defined(SCORE_GFX_HAS_VK_DMABUF_IMPORT)
787 if(m_backend == Backend::Vulkan)
792 for(std::size_t p = 0; p < m_planeInfo.size(); ++p)
794 if(!m_planeTex[p]->createFrom(QRhiTexture::NativeTexture{
795 quint64(m_vkSlots[i][p].image), VK_IMAGE_LAYOUT_UNDEFINED}))
801 if(m_backend == Backend::OpenGL)
805 if(!m_egl.bindExternal(m_glTexPlane[0], m_eglSlots[i][0]))
809 m_glTexBound = m_planeTex[0]->createFrom(
810 QRhiTexture::NativeTexture{quint64(m_glTexPlane[0]), 0});
813 for(std::size_t p = 0; p < m_planeInfo.size(); ++p)
814 if(!m_egl.bindPlane(m_glTexPlane[p], m_eglSlots[i][p]))
820 for(std::size_t p = 0; p < m_planeInfo.size(); ++p)
821 if(!m_planeTex[p]->createFrom(
822 QRhiTexture::NativeTexture{quint64(m_glTexPlane[p]), 0}))
836 VideoCaptureStrategyConfig cfg{};
841 QRhiTexture::Format qfmt{};
842 DmaBufImportFormat fmt{};
843 std::uint32_t offset{};
844 std::uint32_t pitch{};
850 static constexpr std::size_t kMaxPlanes = 3;
852 std::vector<DmaBufSlotDesc> m_slots;
853 DmaBufOrigin m_origin{DmaBufOrigin::ForeignDevice};
854 std::vector<PlaneInfo> m_planeInfo;
858 std::uint32_t m_wholeFrameFourcc{};
859 int m_wholeFrameHeight{};
860 bool m_external{
false};
861 std::uint32_t m_planeBytes{};
862 std::array<QRhiTexture*, kMaxPlanes> m_planeTex{};
863 std::array<std::uint32_t, kMaxPlanes> m_glTexPlane{};
866 Backend m_backend{Backend::None};
868 QRhiTexture* m_tex{};
870#if defined(SCORE_GFX_HAS_VK_DMABUF_IMPORT)
871 DMABufPlaneImporter m_vk;
872 std::array<std::array<DMABufPlaneImporter::PlaneImport, kMaxPlanes>, kMaxSlots> m_vkSlots{};
874 EglDmaBufImporter m_egl;
875 std::array<std::array<EglDmaBufImporter::PlaneImport, kMaxPlanes>, kMaxSlots> m_eglSlots{};
876 unsigned int m_glTex{0};
877 bool m_glTexBound{
false};
879 CaptureSlotPublisher m_publisher;
880 std::atomic<std::uint32_t> m_returns{0};
883 std::uint64_t m_acquisitions{0};
884 std::size_t m_retireDepth{3};
885 std::array<Retired, kMaxSlots> m_retire{};
886 std::size_t m_retireN{0};
Backend-neutral plumbing shared by GPU-direct video CAPTURE strategies (no graphics-API headers).
Vendor-neutral interface for GPU-direct video CAPTURE strategies.
Base toolkit upon which the software is built.
Definition Application.cpp:117