Loading...
Searching...
No Matches
DmaBufImportCapture.hpp
Go to the documentation of this file.
1#pragma once
2
82#if defined(__linux__)
84#include <Gfx/Graph/interop/DMABufImport.hpp>
85#include <Gfx/Graph/interop/DrmFourcc.hpp>
86#include <Gfx/Graph/interop/EglDmaBufImport.hpp>
88
89#include <QtGui/private/qrhi_p.h>
90
91#include <QDebug>
92
93#include <array>
94#include <atomic>
95#include <cstddef>
96#include <cstdint>
97#include <string>
98#include <vector>
99
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
107#endif
108
109namespace score::gfx::interop
110{
111
113struct DmaBufPlaneLayout
114{
115 std::uint32_t offset{};
116 std::uint32_t pitch{};
117};
118
127enum class DmaBufOrigin
128{
131 ForeignDevice,
134 GpuAllocated,
135};
136
138struct DmaBufSlotDesc
139{
140 int fd{-1};
141 std::uint64_t modifier{};
142 std::uint32_t offset{};
143 std::uint32_t pitch{};
144
154 std::uint32_t planeCount{0};
155 DmaBufPlaneLayout planes[3]{};
156};
157
160struct DmaBufImportFormat
161{
162 bool ok{};
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};
167#endif
168};
169
170inline DmaBufImportFormat dmaBufFormatFor(QRhiTexture::Format f) noexcept
171{
172 // The fourccs live in DrmFourcc.hpp; this table only chooses among them.
173 DmaBufImportFormat r;
174 auto set = [&](std::uint32_t fourcc, std::uint32_t bpp
175#if defined(SCORE_GFX_HAS_VK_DMABUF_IMPORT)
176 ,
177 VkFormat vk
178#endif
179 ) {
180 r.ok = true;
181 r.drmFourcc = fourcc;
182 r.bytesPerPixel = bpp;
183#if defined(SCORE_GFX_HAS_VK_DMABUF_IMPORT)
184 r.vk = vk;
185#endif
186 };
187
188#if defined(SCORE_GFX_HAS_VK_DMABUF_IMPORT)
189#define SCORE_DMABUF_FMT(fourcc, bpp, vk) set(fourcc, bpp, vk)
190#else
191#define SCORE_DMABUF_FMT(fourcc, bpp, vk) set(fourcc, bpp)
192#endif
193
194 switch(f)
195 {
196 case QRhiTexture::RGBA8:
197 SCORE_DMABUF_FMT(DRM_ABGR8888, 4, VK_FORMAT_R8G8B8A8_UNORM);
198 break;
199 case QRhiTexture::BGRA8:
200 SCORE_DMABUF_FMT(DRM_ARGB8888, 4, VK_FORMAT_B8G8R8A8_UNORM);
201 break;
202 case QRhiTexture::R8:
203 SCORE_DMABUF_FMT(DRM_R8, 1, VK_FORMAT_R8_UNORM);
204 break;
205 case QRhiTexture::RG8:
206 SCORE_DMABUF_FMT(DRM_GR88, 2, VK_FORMAT_R8G8_UNORM);
207 break;
208 case QRhiTexture::R16:
209 SCORE_DMABUF_FMT(DRM_R16, 2, VK_FORMAT_R16_UNORM);
210 break;
211 case QRhiTexture::RG16:
212 SCORE_DMABUF_FMT(DRM_GR1616, 4, VK_FORMAT_R16G16_UNORM);
213 break;
214 case QRhiTexture::RGB10A2:
215 SCORE_DMABUF_FMT(
216 DRM_ABGR2101010, 4, VK_FORMAT_A2B10G10R10_UNORM_PACK32);
217 break;
218 default:
219 break;
220 }
221#undef SCORE_DMABUF_FMT
222 return r;
223}
224
226inline bool
227dmaBufExplicitLayout(const DmaBufSlotDesc& slot, std::size_t planes) noexcept
228{
229 return slot.planeCount >= planes;
230}
231
234inline DmaBufPlaneLayout dmaBufPlaneLayout(
235 bool explicitLayout, const DmaBufSlotDesc& slot, std::size_t i,
236 std::uint32_t derivedOffset, std::uint32_t derivedPitch) noexcept
237{
238 if(explicitLayout)
239 return slot.planes[i];
240 return {derivedOffset, derivedPitch};
241}
242
251inline bool dmaBufUsesProducerPitch(
252 std::size_t derivedPlanes, const DmaBufSlotDesc& slot) noexcept
253{
254 return derivedPlanes == 1 && slot.planeCount == 0 && slot.pitch > 0;
255}
256
263inline std::uint32_t dmaBufMinPitch(
264 bool wantsExternal, int width, std::uint32_t bytesPerPixel) noexcept
265{
266 return wantsExternal ? std::uint32_t(width)
267 : std::uint32_t(width) * bytesPerPixel;
268}
269
272inline constexpr std::uint32_t kNvidiaProprietaryDriverId = 4;
273
280inline bool dmaBufVulkanRungRefused(
281 DmaBufOrigin origin, std::uint32_t driverId) noexcept
282{
283 return origin == DmaBufOrigin::ForeignDevice
284 && driverId == kNvidiaProprietaryDriverId;
285}
286
288struct DmaBufExternalPlanes
289{
290 std::uint32_t count{};
291 std::uint32_t offsets[3]{};
292 std::uint32_t pitches[3]{};
293};
294
298inline DmaBufExternalPlanes
299dmaBufExternalPlanes(const DmaBufSlotDesc& slot) noexcept
300{
301 DmaBufExternalPlanes r;
302 r.count = std::min<std::uint32_t>(slot.planeCount, 3);
303 if(r.count == 0)
304 {
305 r.count = 1;
306 r.offsets[0] = slot.offset;
307 r.pitches[0] = slot.pitch;
308 return r;
309 }
310 for(std::uint32_t p = 0; p < r.count; ++p)
311 {
312 r.offsets[p] = slot.offset + slot.planes[p].offset;
313 r.pitches[p] = slot.planes[p].pitch;
314 }
315 return r;
316}
317
318struct DmaBufImportCapture final : VideoCaptureStrategy
319{
321 static constexpr std::size_t kMaxSlots = 32;
322
327 DmaBufImportCapture(
328 const char* tag, std::vector<DmaBufSlotDesc> slots,
329 DmaBufOrigin origin = DmaBufOrigin::ForeignDevice)
330 : m_slots{std::move(slots)}
331 , m_origin{origin}
332 , m_tag{tag}
333 , m_name{m_tag + "-dmabuf"}
334 {
335 }
336
337 ~DmaBufImportCapture() override { release(); }
338
346 void requestExternalImage(std::uint32_t fourcc, int height) noexcept
347 {
348 m_wholeFrameFourcc = fourcc;
349 m_wholeFrameHeight = height;
350 }
351
354 bool usesExternalImage() const noexcept { return m_external; }
355
356 const char* name() const noexcept override { return m_name.c_str(); }
357
358 std::size_t slotCount() const noexcept override { return m_slots.size(); }
359
362 void* slotBuffer(std::size_t) const noexcept override { return nullptr; }
363
364 QRhiTexture* outputTexture() const noexcept override { return m_tex; }
365
366 std::size_t outputPlaneCount() const noexcept override
367 {
368 return m_planeInfo.empty() ? 1 : m_planeInfo.size();
369 }
370
371 QRhiTexture* outputPlane(std::size_t i) const noexcept override
372 {
373 return i < m_planeInfo.size() ? m_planeTex[i] : nullptr;
374 }
375
378 std::uint32_t importPitch(const DmaBufSlotDesc& slot, std::size_t p) const noexcept
379 {
380 if(dmaBufUsesProducerPitch(m_planeInfo.size(), slot))
381 return slot.pitch;
382 return m_planeInfo[p].pitch;
383 }
384
385 bool init(const VideoCaptureStrategyConfig& c) override
386 {
387 cfg = c;
388 if(!cfg.rhi || !cfg.outputTexture)
389 return false;
390 if(m_slots.empty() || m_slots.size() > kMaxSlots)
391 return false;
392
393 // Plane geometry. A planar frame is one contiguous dma-buf holding N planes
394 // back to back, and the decoder's own texture list defines the split: each
395 // plane's byte size is its texture geometry times its texel size, and
396 // offsets accumulate in order. That is the same derivation CpuStagedCapture
397 // uses, so the two rungs cannot disagree about where chroma starts, and
398 // adding a planar decoder needs no change here.
399 m_planeInfo.clear();
400 // A producer that states its plane layout is believed; only one that does
401 // not gets the derivation.
402 const bool explicitLayout
403 = !m_slots.empty() && dmaBufExplicitLayout(m_slots[0], cfg.planes.size());
404 if(cfg.planes.size() > 1)
405 {
406 std::uint32_t off = 0;
407 for(std::size_t i = 0; i < cfg.planes.size(); ++i)
408 {
409 auto* tex = cfg.planes[i];
410 if(!tex)
411 {
412 qDebug() << "DmaBufImportCapture: decoder plane texture is null";
413 return false;
414 }
415 const auto psz = tex->pixelSize();
416 const auto pf = dmaBufFormatFor(tex->format());
417 if(!pf.ok)
418 {
419 qDebug() << "DmaBufImportCapture: no DMA-BUF mapping for plane format"
420 << int(tex->format());
421 return false;
422 }
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)
429 {
430 qDebug() << "DmaBufImportCapture: plane" << i << "pitch" << pitch
431 << "cannot hold" << psz.width() << "px of"
432 << pf.bytesPerPixel << "bytes";
433 return false;
434 }
435 m_planeInfo.push_back(
436 PlaneInfo{tex->format(), pf, planeOff, pitch, psz.width(),
437 psz.height()});
438 off = planeOff + pitch * std::uint32_t(psz.height());
439 }
440 m_planeBytes = off;
441 }
442 else
443 {
444 const auto pf = dmaBufFormatFor(cfg.outputTexture->format());
445 if(!pf.ok)
446 {
447 qDebug() << "DmaBufImportCapture: no DMA-BUF mapping for QRhi format"
448 << int(cfg.outputTexture->format());
449 return false;
450 }
451 const auto sz0 = cfg.outputTexture->pixelSize();
452 m_planeInfo.push_back(
453 PlaneInfo{
454 cfg.outputTexture->format(), pf, 0,
455 std::uint32_t(sz0.width()) * pf.bytesPerPixel, sz0.width(),
456 sz0.height()});
457 m_planeBytes = 0;
458 }
459
460 // Plane 0's geometry still drives the slot-pitch validation below, and for
461 // the single-plane case it is the whole picture.
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;
465
466 // The derivation assumes tightly-packed rows; a producer that stated its
467 // layout has already told us where each plane is and is exempt.
468 if(m_planeInfo.size() > 1 && !explicitLayout)
469 {
470 const auto tight = std::uint32_t(sz.width()) * fmt.bytesPerPixel;
471 for(const auto& s : m_slots)
472 {
473 if(s.pitch != tight)
474 {
475 qDebug() << "DmaBufImportCapture: planar source has a padded pitch"
476 << s.pitch << "(tight" << tight
477 << "); plane offsets cannot be derived, no dma-buf rung";
478 return false;
479 }
480 }
481 }
482 if(sz.width() <= 0 || sz.height() <= 0)
483 return false;
484
485 const bool wantsExternal = m_wholeFrameFourcc != 0;
486 const auto minPitch
487 = dmaBufMinPitch(wantsExternal, sz.width(), fmt.bytesPerPixel);
488 for(const auto& s : m_slots)
489 {
490 if(s.fd < 0 || s.pitch < minPitch)
491 {
492 qDebug() << "DmaBufImportCapture: slot fd" << s.fd << "pitch" << s.pitch
493 << "cannot hold" << sz.width() << "x" << sz.height()
494 << "(need >=" << minPitch << ")";
495 return false;
496 }
497 }
498
499 m_retireDepth = static_cast<std::size_t>(
500 cfg.rhi->resourceLimit(QRhi::FramesInFlight))
501 + 1u;
502 if(m_retireDepth + 2u > m_slots.size())
503 {
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";
507 return false;
508 }
509
510#if defined(SCORE_GFX_HAS_VK_DMABUF_IMPORT)
511 if(DMABufPlaneImporter::isAvailable(*cfg.rhi))
512 {
513 // The NVIDIA proprietary driver imports a V4L2 dma_buf and samples
514 // plausible pixels from it, but the image does not stay bound to the
515 // buffer it was imported from: the sampled content changes several times
516 // faster than frames are acquired, while the identical code
517 // on anv and on Mesa's GL path changes exactly once per frame. There is
518 // no capability bit for that, and a rung that tears is worse than one
519 // that degrades.
520#if defined(VK_DRIVER_ID_NVIDIA_PROPRIETARY)
521 static_assert(
522 std::uint32_t(VK_DRIVER_ID_NVIDIA_PROPRIETARY)
523 == kNvidiaProprietaryDriverId);
524#endif
525 if(dmaBufVulkanRungRefused(
526 m_origin, DMABufPlaneImporter::driverId(*cfg.rhi)))
527 {
528 qDebug() << "DmaBufImportCapture: NVIDIA proprietary Vulkan reads stale "
529 "bytes out of a dma-buf exported by a foreign device; no "
530 "Vulkan rung for"
531 << m_tag.c_str();
532 return false;
533 }
534 // Set before importing: release() keys its cleanup off the backend, and
535 // a refusal halfway through the ring still has images to destroy.
536 m_backend = Backend::Vulkan;
537 m_vk.init(*cfg.rhi);
538 for(std::size_t i = 0; i < m_slots.size(); ++i)
539 {
540 const auto& s = m_slots[i];
541 for(std::size_t p = 0; p < m_planeInfo.size(); ++p)
542 {
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))
547 {
548 qDebug() << "DmaBufImportCapture: Vulkan import refused slot" << i
549 << "plane" << p << "fd" << s.fd << "modifier" << Qt::hex
550 << s.modifier;
551 release();
552 return false;
553 }
554 }
555 }
556 m_name += "/Vulkan";
557 }
558 else
559#endif
560 if(EglDmaBufImporter::isAvailable(*cfg.rhi) && m_egl.init(*cfg.rhi))
561 {
562 m_backend = Backend::OpenGL;
563 // Refusing here is the whole point of the check: eglCreateImage accepts a
564 // modifier the driver only exposes through GL_TEXTURE_EXTERNAL_OES, and
565 // the resulting GL_TEXTURE_2D samples black without raising an error.
566 // A planar frame goes in as ONE external image when the caller asked for
567 // it: drivers refuse the per-plane 2D form (fourcc 'R8 ' is rejected on
568 // both Mesa and Tegra), and an external sampler converts YUV itself.
569 m_external = cfg.planes.size() == 1 && m_wholeFrameFourcc != 0
570 && m_egl.hasExternalImage();
571
572 if(!m_external)
573 {
574 // Per plane: NV12's chroma is a different fourcc from its luma, and a
575 // driver may expose one as a 2D texture and not the other.
576 for(const auto& pi : m_planeInfo)
577 {
578 if(!m_egl.canImportModifier(pi.fmt.drmFourcc, m_slots[0].modifier))
579 {
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";
583 release();
584 return false;
585 }
586 }
587 }
588 // One GL texture per plane: the EGLImage behind each is re-targeted per
589 // frame, but the texture ids themselves are created once here. The
590 // external form needs exactly one, of a different target.
591 const std::size_t texCount = m_external ? 1u : m_planeInfo.size();
592 for(std::size_t p = 0; p < texCount; ++p)
593 {
594 m_glTexPlane[p] = m_external
595 ? EglDmaBufImporter::createExternalTexture()
596 : score::gfx::createLinearClampGlTexture2D();
597 if(m_glTexPlane[p] == 0)
598 {
599 release();
600 return false;
601 }
602 }
603 m_glTex = m_glTexPlane[0];
604 for(std::size_t i = 0; i < m_slots.size(); ++i)
605 {
606 const auto& s = m_slots[i];
607 if(m_external)
608 {
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))
614 {
615 qDebug() << "DmaBufImportCapture: external EGL import refused slot"
616 << i << "fourcc" << Qt::hex << m_wholeFrameFourcc;
617 release();
618 return false;
619 }
620 continue;
621 }
622 for(std::size_t p = 0; p < m_planeInfo.size(); ++p)
623 {
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))
629 {
630 qDebug() << "DmaBufImportCapture: EGL import refused slot" << i
631 << "plane" << p << "fd" << s.fd << "fourcc" << Qt::hex
632 << pi.fmt.drmFourcc << "modifier" << s.modifier;
633 release();
634 return false;
635 }
636 }
637 }
638 if(m_external)
639 m_name += "/NV12-OES";
640 m_name += "/EGL";
641 }
642
643 if(m_backend == Backend::None)
644 return false;
645
646 // QGles2Texture::prepareCreate() derives the GL bind target from the flags
647 // alone, and createFrom() goes through it too -- so an external image whose
648 // QRhiTexture lacks ExternalOES binds as GL_TEXTURE_2D and samples nothing.
649 const auto texFlags
650 = m_external ? QRhiTexture::ExternalOES : QRhiTexture::Flag{};
651 for(std::size_t p = 0; p < m_planeInfo.size(); ++p)
652 {
653 const auto& pi = m_planeInfo[p];
654 m_planeTex[p]
655 = cfg.rhi->newTexture(pi.qfmt, QSize{pi.width, pi.height}, 1, texFlags);
656 if(!m_planeTex[p])
657 {
658 release();
659 return false;
660 }
661 }
662 m_tex = m_planeTex[0];
663 // Bind slot 0 so the textures are complete before the passes are built.
664 if(!bindSlot(0))
665 {
666 release();
667 return false;
668 }
669 return true;
670 }
671
672 void release() override
673 {
674#if defined(SCORE_GFX_HAS_VK_DMABUF_IMPORT)
675 if(m_backend == Backend::Vulkan)
676 for(auto& slot : m_vkSlots)
677 for(auto& p : slot)
678 m_vk.cleanupPlane(p);
679#endif
680 if(m_backend == Backend::OpenGL)
681 {
682 for(auto& slot : m_eglSlots)
683 for(auto& p : slot)
684 m_egl.cleanupPlane(p);
685 for(auto& t : m_glTexPlane)
686 {
687 if(t != 0)
688 {
689 if(auto* ctx = QOpenGLContext::currentContext())
690 if(auto* funcs = ctx->extraFunctions())
691 funcs->glDeleteTextures(1, &t);
692 t = 0;
693 }
694 }
695 m_glTex = 0;
696 }
697 for(auto& t : m_planeTex)
698 {
699 delete t;
700 t = nullptr;
701 }
702 m_tex = nullptr;
703 m_planeInfo.clear();
704 m_backend = Backend::None;
705 m_glTexBound = false;
706 m_name = m_tag + "-dmabuf";
707 m_publisher.reset();
708 m_returns.store(0, std::memory_order_relaxed);
709 m_held = -1;
710 m_acquisitions = 0;
711 m_retireN = 0;
712 }
713
716 bool ingestFrame(std::size_t i) override
717 {
718 if(i >= m_slots.size())
719 return false;
720 const int displaced
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);
724 return true;
725 }
726
729 std::uint32_t takeReturnedSlots() noexcept
730 {
731 return m_returns.exchange(0, std::memory_order_acquire);
732 }
733
734 void acquireForRender() override
735 {
736 const int slot = m_publisher.consume();
737 if(slot < 0 || std::size_t(slot) >= m_slots.size())
738 return;
739
740 ++m_acquisitions;
741 if(m_held >= 0 && m_held != slot && m_retireN < kMaxSlots)
742 m_retire[m_retireN++] = Retired{m_held, m_acquisitions};
743 m_held = slot;
744 bindSlot(std::size_t(slot));
745
746 std::uint32_t freed = 0;
747 std::size_t keep = 0;
748 for(std::size_t i = 0; i < m_retireN; ++i)
749 {
750 if(m_acquisitions - m_retire[i].at >= m_retireDepth)
751 freed |= 1u << unsigned(m_retire[i].slot);
752 else
753 m_retire[keep++] = m_retire[i];
754 }
755 m_retireN = keep;
756 if(freed)
757 m_returns.fetch_or(freed, std::memory_order_release);
758 }
759
760 bool supportsSlotSelection() const noexcept override { return true; }
761
762 bool acquireSlotForRender(
763 std::size_t slot, QRhiResourceUpdateBatch&, QRhiCommandBuffer*) override
764 {
765 if(slot >= m_slots.size())
766 return false;
767 m_held = int(slot);
768 return bindSlot(slot);
769 }
770
771 void releaseAfterRender() override { }
772
773private:
774 enum class Backend
775 {
776 None,
777 Vulkan,
778 OpenGL,
779 };
780
782 bool bindSlot(std::size_t i)
783 {
784 if(!m_tex)
785 return true;
786#if defined(SCORE_GFX_HAS_VK_DMABUF_IMPORT)
787 if(m_backend == Backend::Vulkan)
788 {
789 // Re-issuing createFrom bumps the texture generation, which is what makes
790 // QRhi rewrite the descriptor and emit the layout transition whose cache
791 // invalidation exposes the producer's new bytes.
792 for(std::size_t p = 0; p < m_planeInfo.size(); ++p)
793 {
794 if(!m_planeTex[p]->createFrom(QRhiTexture::NativeTexture{
795 quint64(m_vkSlots[i][p].image), VK_IMAGE_LAYOUT_UNDEFINED}))
796 return false;
797 }
798 return true;
799 }
800#endif
801 if(m_backend == Backend::OpenGL)
802 {
803 if(m_external)
804 {
805 if(!m_egl.bindExternal(m_glTexPlane[0], m_eglSlots[i][0]))
806 return false;
807 if(m_glTexBound)
808 return true;
809 m_glTexBound = m_planeTex[0]->createFrom(
810 QRhiTexture::NativeTexture{quint64(m_glTexPlane[0]), 0});
811 return m_glTexBound;
812 }
813 for(std::size_t p = 0; p < m_planeInfo.size(); ++p)
814 if(!m_egl.bindPlane(m_glTexPlane[p], m_eglSlots[i][p]))
815 return false;
816 // The GL texture ids never change -- only the EGLImages behind them --
817 // and QRhiGles2 resolves the id at draw time, so the wrap is set up once.
818 if(m_glTexBound)
819 return true;
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}))
823 return false;
824 m_glTexBound = true;
825 return true;
826 }
827 return false;
828 }
829
830 struct Retired
831 {
832 int slot{-1};
833 std::uint64_t at{};
834 };
835
836 VideoCaptureStrategyConfig cfg{};
839 struct PlaneInfo
840 {
841 QRhiTexture::Format qfmt{};
842 DmaBufImportFormat fmt{};
843 std::uint32_t offset{};
844 std::uint32_t pitch{};
845 int width{};
846 int height{};
847 };
848
850 static constexpr std::size_t kMaxPlanes = 3;
851
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{};
864 std::string m_tag;
865 std::string m_name;
866 Backend m_backend{Backend::None};
867
868 QRhiTexture* m_tex{};
869
870#if defined(SCORE_GFX_HAS_VK_DMABUF_IMPORT)
871 DMABufPlaneImporter m_vk;
872 std::array<std::array<DMABufPlaneImporter::PlaneImport, kMaxPlanes>, kMaxSlots> m_vkSlots{};
873#endif
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};
878
879 CaptureSlotPublisher m_publisher;
880 std::atomic<std::uint32_t> m_returns{0};
881
882 int m_held{-1};
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};
887};
888
889} // namespace score::gfx::interop
890#endif // __linux__
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
STL namespace.