Loading...
Searching...
No Matches
DRMPrime.hpp
1#pragma once
2
3// SPDX-License-Identifier: GPL-3.0-or-later
4//
5// Zero-copy DMA-BUF to QRhi decoder for AVFrames in the AV_PIX_FMT_DRM_PRIME
6// transport convention.
7//
8// Backend dispatch, chosen at init from the live QRhi backend:
9// Vulkan DMABufPlaneImporter (VK_KHR_external_memory_fd +
10// VK_EXT_image_drm_format_modifier)
11// OpenGLES2 EglDmaBufImporter (EGL_EXT_image_dma_buf_import_modifiers +
12// GL_OES_EGL_image); EGL-backed contexts only, GLX falls back
13// otherwise no-op shader (black); the producer-side memcpy in
14// PipewireInputDevice covers those
15//
16// Format dispatch, from format.hwaccel_sw_format:
17// packed RGB (BGRA8 / RGBA8 / RGB10A2 / RGBA16F) one sampler, RGBA
18// pass-through; the producer-side fourcc maps to the matching
19// native format
20// NV12 / P010 2 samplers, Y as R8/R16 and
21// UV as RG8/RG16, the standard YUV->RGB shader from NV12.hpp
22// I420 / YUV420P 3 samplers, all R8, the
23// YUV420.hpp shader
24//
25// Imports are cached by the inode of the dma-buf, so a producer rotating a
26// fixed pool -- which is what pipewire negotiates -- imports each buffer once
27// instead of once a frame. Planes that cannot be cached fall back to a 2-slot
28// ring, so the GPU is never handed a slot it has not finished sampling.
29
30#if defined(__linux__)
31#include <Gfx/Graph/decoders/ColorSpace.hpp>
32#include <Gfx/Graph/decoders/GPUVideoDecoder.hpp>
33#include <Gfx/Graph/interop/DrmFourcc.hpp>
34#include <Gfx/Graph/decoders/NV12.hpp>
35#include <Gfx/Graph/decoders/P010.hpp>
36#include <Gfx/Graph/decoders/YUV420.hpp>
37#include <Gfx/Graph/interop/DMABufImport.hpp>
38#include <Gfx/Graph/interop/EglDmaBufImport.hpp>
39
40#include <Video/VideoInterface.hpp>
41
42#include <score/gfx/Vulkan.hpp>
43
44#include <sys/stat.h>
45
46#include <optional>
47#include <unordered_map>
48
49extern "C" {
50#include <libavformat/avformat.h>
51#if __has_include(<libavutil/hwcontext_drm.h>)
52#include <libavutil/hwcontext_drm.h>
53#define SCORE_DRMPRIME_HAS_HWCONTEXT_DRM 1
54#endif
55}
56
57#if defined(SCORE_DRMPRIME_HAS_HWCONTEXT_DRM) \
58 && QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
59
60#if QT_HAS_VULKAN
61#include <QtGui/private/qrhivulkan_p.h>
62#include <vulkan/vulkan.h>
63#endif
64
65namespace score::gfx
66{
67
71struct DRMPrimeDecoder : GPUVideoDecoder
72{
73 Video::ImageFormat& m_decoder;
74
75 enum class Backend
76 {
77 None,
78 Vulkan,
79 OpenGL,
80 };
81 Backend m_backend{Backend::None};
82
83 // SW format family — determines shader + plane count.
84 enum class Family
85 {
86 PackedRGB, // 1 plane: BGRA/RGBA/RGB10A2/RGBA16F
87 NV12, // 2 planes: R8 Y + RG8 UV (or R16/RG16 for P010)
88 I420, // 3 planes: R8 Y + R8 U + R8 V
89 };
90 Family m_family{Family::PackedRGB};
91 bool m_is_10bit{false}; // P010 vs NV12
92
105 struct DmaBufImportKey
106 {
107 unsigned long long ino{};
108 unsigned long long dev{};
109 uint64_t modifier{};
110 long long offset{};
111 long long pitch{};
112 int w{}, h{};
113 uint32_t fmt{};
114
115 friend bool operator==(const DmaBufImportKey&, const DmaBufImportKey&)
116 = default;
117 };
118 struct DmaBufImportKeyHash
119 {
120 std::size_t operator()(const DmaBufImportKey& k) const noexcept
121 {
122 std::size_t h = std::hash<unsigned long long>{}(k.ino);
123 auto mix = [&h](std::size_t v) { h ^= v + 0x9e3779b9 + (h << 6) + (h >> 2); };
124 mix(std::hash<unsigned long long>{}(k.dev));
125 mix(std::hash<uint64_t>{}(k.modifier));
126 mix(std::hash<long long>{}(k.offset));
127 mix(std::hash<long long>{}(k.pitch));
128 mix(std::hash<int>{}(k.w));
129 mix(std::hash<int>{}(k.h));
130 mix(std::hash<uint32_t>{}(k.fmt));
131 return h;
132 }
133 };
134
137 static std::optional<DmaBufImportKey> importKey(
138 int fd, uint64_t modifier, long long offset, long long pitch, int w, int h,
139 uint32_t fmt) noexcept
140 {
141 struct ::stat st
142 {
143 };
144 if(fd < 0 || ::fstat(fd, &st) != 0)
145 return std::nullopt;
146
147 DmaBufImportKey key{};
148 key.ino = st.st_ino;
149 key.dev = st.st_dev;
150 key.modifier = modifier;
151 key.offset = offset;
152 key.pitch = pitch;
153 key.w = w;
154 key.h = h;
155 key.fmt = fmt;
156 return key;
157 }
158
162 static constexpr std::size_t kMaxImports = 32;
163
166 static constexpr int kNumScratchSlots = 2;
167
168#if QT_HAS_VULKAN && defined(VK_EXT_image_drm_format_modifier) \
169 && defined(VK_KHR_external_memory_fd)
170 DMABufPlaneImporter m_vk_importer;
171
172 std::unordered_map<DmaBufImportKey, DMABufPlaneImporter::PlaneImport,
173 DmaBufImportKeyHash>
174 m_vk_imports;
175 struct VkScratch
176 {
177 DMABufPlaneImporter::PlaneImport planes[3]{};
178 };
179 VkScratch m_vk_scratch[kNumScratchSlots]{};
180 int m_vk_scratchIdx{0};
181 VkFormat m_vk_plane_fmt[3]{}; // per-plane VkFormat
182#endif
183
184 EglDmaBufImporter m_gl_importer;
185 std::unordered_map<DmaBufImportKey, EglDmaBufImporter::PlaneImport,
186 DmaBufImportKeyHash>
187 m_gl_imports;
188 struct GlScratch
189 {
190 EglDmaBufImporter::PlaneImport planes[3]{};
191 };
192 GlScratch m_gl_scratch[kNumScratchSlots]{};
193 int m_gl_scratchIdx{0};
194 unsigned int m_gl_textures[3]{0, 0, 0}; // persistent GL ids per plane
195 uint32_t m_gl_plane_fourcc[3]{}; // per-plane DRM fourcc
196
198 static bool isAvailable(QRhi& rhi) noexcept
199 {
200#if QT_HAS_VULKAN && defined(VK_EXT_image_drm_format_modifier) \
201 && defined(VK_KHR_external_memory_fd)
202 if(DMABufPlaneImporter::isAvailable(rhi))
203 return true;
204#endif
205 if(EglDmaBufImporter::isAvailable(rhi))
206 return true;
207 return false;
208 }
209
210 explicit DRMPrimeDecoder(Video::ImageFormat& d)
211 : m_decoder{d}
212 {
213 // Determine format family from hwaccel_sw_format. Falls back to
214 // PackedRGB if not set; the descriptor inspection at exec time
215 // can still detect mismatch.
216 switch(d.hwaccel_sw_format)
217 {
218 case AV_PIX_FMT_NV12:
219 m_family = Family::NV12;
220 m_is_10bit = false;
221 break;
222 case AV_PIX_FMT_P010LE:
223 m_family = Family::NV12;
224 m_is_10bit = true;
225 break;
226 case AV_PIX_FMT_YUV420P:
227 m_family = Family::I420;
228 m_is_10bit = false;
229 break;
230 default:
231 m_family = Family::PackedRGB;
232 break;
233 }
234 }
235
242 void releaseImports() noexcept
243 {
244#if QT_HAS_VULKAN && defined(VK_EXT_image_drm_format_modifier) \
245 && defined(VK_KHR_external_memory_fd)
246 if(m_backend == Backend::Vulkan)
247 {
248 const bool scratch_held = std::any_of(
249 std::begin(m_vk_scratch), std::end(m_vk_scratch), [](const VkScratch& s) {
250 return std::any_of(
251 std::begin(s.planes), std::end(s.planes),
252 [](const DMABufPlaneImporter::PlaneImport& p) {
253 return p.image != VK_NULL_HANDLE;
254 });
255 });
256 // Nothing to free means nothing to wait for. The destructor calls this
257 // after release() already emptied it, and by then the device is being
258 // torn down: waiting on it there is not just wasted, it is a call the
259 // validation layer rejects.
260 if(m_vk_imports.empty() && !scratch_held)
261 return;
262
263 // The GPU may still be reading the frames that sampled these.
264 m_vk_importer.waitIdle();
265 for(auto& [k, pl] : m_vk_imports)
266 m_vk_importer.cleanupPlane(pl);
267 m_vk_imports.clear();
268 for(auto& slot : m_vk_scratch)
269 for(auto& p : slot.planes)
270 m_vk_importer.cleanupPlane(p);
271 }
272#endif
273 if(m_backend == Backend::OpenGL)
274 {
275 for(auto& [k, pl] : m_gl_imports)
276 m_gl_importer.cleanupPlane(pl);
277 m_gl_imports.clear();
278 for(auto& slot : m_gl_scratch)
279 for(auto& p : slot.planes)
280 m_gl_importer.cleanupPlane(p);
281 }
282 }
283
284 void release(RenderList& r) override
285 {
286 // The base only deleteLater()s the sampler textures, leaving their image
287 // views alive past this call. Drop them and let the RHI run its deferred
288 // releases first.
289 for(auto& s : samplers)
290 if(s.texture)
291 s.texture->destroy();
292 if(r.state.rhi)
293 r.state.rhi->finish();
294
295 releaseImports();
296 GPUVideoDecoder::release(r);
297 }
298
299 ~DRMPrimeDecoder() override
300 {
301 // Normally already done by release(); this covers a decoder that is
302 // dropped without one.
303 releaseImports();
304 if(m_backend == Backend::OpenGL)
305 {
306 if(auto* ctx = QOpenGLContext::currentContext())
307 {
308 if(auto* funcs = ctx->extraFunctions())
309 {
310 for(unsigned int& tex : m_gl_textures)
311 if(tex != 0)
312 funcs->glDeleteTextures(1, &tex);
313 }
314 }
315 for(unsigned int& tex : m_gl_textures)
316 tex = 0;
317 }
318 }
319
320 static constexpr auto packed_frag = R"_(#version 450
321
322)_" SCORE_GFX_VIDEO_UNIFORMS R"_(
323
324 layout(binding=3) uniform sampler2D y_tex;
325
326 layout(location = 0) in vec2 v_texcoord;
327 layout(location = 0) out vec4 fragColor;
328
329 vec4 processTexture(vec4 tex) {
330 vec4 processed = tex;
331 { %1 }
332 return processed;
333 }
334
335 void main () {
336 fragColor = processTexture(texture(y_tex, v_texcoord));
337 })_";
338
340 int planeCount() const noexcept
341 {
342 switch(m_family)
343 {
344 case Family::PackedRGB: return 1;
345 case Family::NV12: return 2;
346 case Family::I420: return 3;
347 }
348 return 1;
349 }
350
351 std::pair<QShader, QShader> init(RenderList& r) override
352 {
353 auto& rhi = *r.state.rhi;
354
355 // -- Pick backend ----------------------------------------------
356#if QT_HAS_VULKAN && defined(VK_EXT_image_drm_format_modifier) \
357 && defined(VK_KHR_external_memory_fd)
358 if(DMABufPlaneImporter::isAvailable(rhi))
359 {
360 m_vk_importer.init(rhi);
361 m_backend = Backend::Vulkan;
362 }
363 else
364#endif
365 if(EglDmaBufImporter::isAvailable(rhi))
366 {
367 if(m_gl_importer.init(rhi))
368 m_backend = Backend::OpenGL;
369 }
370
371 if(m_backend == Backend::None)
372 {
374 r.state, vertexShader(), EmptyDecoder::hashtag_no_filter);
375 }
376
377 // -- Create per-plane sampler/texture pairs --------------------
378 const auto w = m_decoder.width, h = m_decoder.height;
379
380 auto makeSamplerTexture = [&](QRhiTexture::Format fmt, QSize size) {
381 auto* tex = rhi.newTexture(fmt, size, 1, QRhiTexture::Flag{});
382 tex->create();
383 auto* sampler = rhi.newSampler(
384 QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
385 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
386 sampler->create();
387 samplers.push_back({sampler, tex});
388 };
389
390 switch(m_family)
391 {
392 case Family::PackedRGB:
393 // Placeholder until the first frame's DRM fourcc gives the real
394 // channel order; exec() fixes the format before adopting the image.
395 // createFrom keeps the texture's format, and Qt builds the image view
396 // from it, so a BGRA8 view over an R8G8B8A8 image swaps red and blue.
397 makeSamplerTexture(QRhiTexture::BGRA8, QSize{w, h});
398#if QT_HAS_VULKAN && defined(VK_EXT_image_drm_format_modifier) \
399 && defined(VK_KHR_external_memory_fd)
400 m_vk_plane_fmt[0] = VK_FORMAT_UNDEFINED; // chosen at first exec
401#endif
402 break;
403
404 case Family::NV12:
405 if(m_is_10bit)
406 {
407 // P010: R16 (Y) + RG16 (UV at half res).
408 makeSamplerTexture(QRhiTexture::R16, QSize{w, h});
409 makeSamplerTexture(QRhiTexture::RG16, QSize{w / 2, h / 2});
410#if QT_HAS_VULKAN && defined(VK_EXT_image_drm_format_modifier) \
411 && defined(VK_KHR_external_memory_fd)
412 m_vk_plane_fmt[0] = VK_FORMAT_R16_UNORM;
413 m_vk_plane_fmt[1] = VK_FORMAT_R16G16_UNORM;
414#endif
415 // DRM_FORMAT_GR1616 is fourcc_code('G','R','3','2'); 'GR16' is not a
416 // fourcc any kernel knows, and importing the chroma plane with it
417 // fails or lands on driver-specific behaviour.
418 m_gl_plane_fourcc[0] = score::gfx::interop::DRM_R16;
419 m_gl_plane_fourcc[1] = score::gfx::interop::DRM_GR1616;
420 }
421 else
422 {
423 // NV12: R8 (Y) + RG8 (UV at half res).
424 makeSamplerTexture(QRhiTexture::R8, QSize{w, h});
425 makeSamplerTexture(QRhiTexture::RG8, QSize{w / 2, h / 2});
426#if QT_HAS_VULKAN && defined(VK_EXT_image_drm_format_modifier) \
427 && defined(VK_KHR_external_memory_fd)
428 m_vk_plane_fmt[0] = VK_FORMAT_R8_UNORM;
429 m_vk_plane_fmt[1] = VK_FORMAT_R8G8_UNORM;
430#endif
431 m_gl_plane_fourcc[0] = 0x20203852u; // DRM_FORMAT_R8 'R8 '
432 m_gl_plane_fourcc[1] = 0x38385247u; // DRM_FORMAT_GR88 'GR88'
433 }
434 break;
435
436 case Family::I420:
437 // Three R8 planes: Y full-res, U/V at half-res.
438 makeSamplerTexture(QRhiTexture::R8, QSize{w, h});
439 makeSamplerTexture(QRhiTexture::R8, QSize{w / 2, h / 2});
440 makeSamplerTexture(QRhiTexture::R8, QSize{w / 2, h / 2});
441#if QT_HAS_VULKAN && defined(VK_EXT_image_drm_format_modifier) \
442 && defined(VK_KHR_external_memory_fd)
443 m_vk_plane_fmt[0] = VK_FORMAT_R8_UNORM;
444 m_vk_plane_fmt[1] = VK_FORMAT_R8_UNORM;
445 m_vk_plane_fmt[2] = VK_FORMAT_R8_UNORM;
446#endif
447 m_gl_plane_fourcc[0] = 0x20203852u;
448 m_gl_plane_fourcc[1] = 0x20203852u;
449 m_gl_plane_fourcc[2] = 0x20203852u;
450 break;
451 }
452
453 // For the OpenGL path: pre-create persistent GL texture ids and
454 // wire each QRhiTexture to wrap one. The EGL importer re-targets
455 // their storage each frame via glEGLImageTargetTexture2DOES.
456 if(m_backend == Backend::OpenGL)
457 {
458 const int n = planeCount();
459 for(int i = 0; i < n; ++i)
460 {
461 m_gl_textures[i] = score::gfx::createLinearClampGlTexture2D();
462 if(m_gl_textures[i])
463 samplers[i].texture->createFrom(
464 QRhiTexture::NativeTexture{quint64(m_gl_textures[i]), 0});
465 }
466 }
467
468 // -- Build the right shader for the family --------------------
469 switch(m_family)
470 {
471 case Family::PackedRGB:
473 r.state, vertexShader(), QString(packed_frag).arg(""));
474
475 case Family::NV12:
476 {
477 // Reuse NV12Decoder's filter epilogue. Builds
478 // y from t0.r, uv from t1.rg, yuv → rgb conversion.
479 QString frag = NV12Decoder::nv12_filter_prologue;
480 if(m_is_10bit)
481 frag += " vec3 yuv = vec3(y * 64.0, u * 64.0, v * 64.0);\n";
482 else
483 frag += " vec3 yuv = vec3(y, u, v);\n";
484 frag += NV12Decoder::nv12_filter_epilogue;
486 r.state, vertexShader(), frag.arg("").arg(colorMatrix(m_decoder)));
487 }
488
489 case Family::I420:
491 r.state, vertexShader(),
492 QString(YUV420Decoder::frag).arg("").arg(colorMatrix(m_decoder)));
493 }
495 r.state, vertexShader(), EmptyDecoder::hashtag_no_filter);
496 }
497
498#if QT_HAS_VULKAN && defined(VK_EXT_image_drm_format_modifier) \
499 && defined(VK_KHR_external_memory_fd)
502 static QRhiTexture::Format qrhiPackedFmtFromDrmFourcc(uint32_t fourcc) noexcept
503 {
504 switch(fourcc)
505 {
506 case 0x34325241: // ARGB8888
507 case 0x34325258: // XRGB8888
508 return QRhiTexture::BGRA8;
509 case 0x34324241: // ABGR8888
510 case 0x34324258: // XBGR8888
511 return QRhiTexture::RGBA8;
512 case 0x30334241: // ABGR2101010
513 case 0x30334258: // XBGR2101010
514 return QRhiTexture::RGB10A2;
515 case 0x48344241: // ABGR16161616F
516 case 0x48344258: // XBGR16161616F
517 return QRhiTexture::RGBA16F;
518 // ARGB2101010 has no QRhi equivalent: RGB10A2 is A2B10G10R10.
519 default: return QRhiTexture::UnknownFormat;
520 }
521 }
522
524 static VkFormat vkPackedFmtFromDrmFourcc(uint32_t fourcc) noexcept
525 {
526 switch(fourcc)
527 {
528 case 0x34325241: return VK_FORMAT_B8G8R8A8_UNORM; // ARGB8888
529 case 0x34325258: return VK_FORMAT_B8G8R8A8_UNORM; // XRGB8888
530 case 0x34324241: return VK_FORMAT_R8G8B8A8_UNORM; // ABGR8888
531 case 0x34324258: return VK_FORMAT_R8G8B8A8_UNORM; // XBGR8888
532 case 0x30335241: return VK_FORMAT_A2R10G10B10_UNORM_PACK32;
533 case 0x30335258: return VK_FORMAT_A2R10G10B10_UNORM_PACK32;
534 case 0x30334241: return VK_FORMAT_A2B10G10R10_UNORM_PACK32;
535 case 0x30334258: return VK_FORMAT_A2B10G10R10_UNORM_PACK32;
536 case 0x48344241: return VK_FORMAT_R16G16B16A16_SFLOAT;
537 case 0x48344258: return VK_FORMAT_R16G16B16A16_SFLOAT;
538 default: return VK_FORMAT_UNDEFINED;
539 }
540 }
541#endif
542
543 void exec(
544 RenderList&, QRhiResourceUpdateBatch& /*res*/, AVFrame& frame) override
545 {
546 if(m_backend == Backend::None || frame.format != AV_PIX_FMT_DRM_PRIME)
547 return;
548
549 const auto* desc
550 = reinterpret_cast<const AVDRMFrameDescriptor*>(frame.data[0]);
551 if(!desc || desc->nb_objects < 1 || desc->nb_layers < 1)
552 return;
553
554 // Resolve per-plane (object_index, offset, pitch, width, height).
555 struct PlaneRef
556 {
557 int obj_idx;
558 ptrdiff_t offset;
559 ptrdiff_t pitch;
560 int w, h;
561 };
562 PlaneRef p[3]{};
563 const int np = planeCount();
564
565 // Two layouts: single-layer-multi-plane (typical) or
566 // multi-layer-single-plane (split-buffer producer).
567 if(desc->nb_layers == 1 && desc->layers[0].nb_planes >= np)
568 {
569 const auto& layer = desc->layers[0];
570 for(int i = 0; i < np; ++i)
571 {
572 const auto& plane = layer.planes[i];
573 p[i].obj_idx = plane.object_index;
574 p[i].offset = plane.offset;
575 p[i].pitch = plane.pitch;
576 }
577 }
578 else if(desc->nb_layers >= np)
579 {
580 for(int i = 0; i < np; ++i)
581 {
582 const auto& plane = desc->layers[i].planes[0];
583 p[i].obj_idx = plane.object_index;
584 p[i].offset = plane.offset;
585 p[i].pitch = plane.pitch;
586 }
587 }
588 else
589 {
590 qDebug() << "DRMPrimeDecoder: unexpected DRM layout — layers"
591 << desc->nb_layers << "but need" << np << "planes";
592 return;
593 }
594
595 // Per-plane dimensions: Y plane full size; chroma planes half-res
596 // for NV12/I420 (semi or 4:2:0).
597 p[0].w = m_decoder.width;
598 p[0].h = m_decoder.height;
599 if(m_family == Family::NV12)
600 {
601 p[1].w = m_decoder.width / 2;
602 p[1].h = m_decoder.height / 2;
603 }
604 else if(m_family == Family::I420)
605 {
606 p[1].w = p[2].w = m_decoder.width / 2;
607 p[1].h = p[2].h = m_decoder.height / 2;
608 }
609
610#if QT_HAS_VULKAN && defined(VK_EXT_image_drm_format_modifier) \
611 && defined(VK_KHR_external_memory_fd)
612 if(m_backend == Backend::Vulkan)
613 {
614 // For packed RGB, derive the VkFormat from the descriptor's
615 // fourcc on first exec.
616 if(m_family == Family::PackedRGB
617 && m_vk_plane_fmt[0] == VK_FORMAT_UNDEFINED)
618 {
619 m_vk_plane_fmt[0] = vkPackedFmtFromDrmFourcc(desc->layers[0].format);
620 if(const auto qfmt = qrhiPackedFmtFromDrmFourcc(desc->layers[0].format);
621 qfmt != QRhiTexture::UnknownFormat && !samplers.empty()
622 && samplers[0].texture)
623 {
624 // Before the createFrom below, which is what builds the view.
625 samplers[0].texture->setFormat(qfmt);
626 }
627 if(m_vk_plane_fmt[0] == VK_FORMAT_UNDEFINED)
628 {
629 qDebug() << "DRMPrimeDecoder: unsupported packed DRM fourcc" << Qt::hex
630 << desc->layers[0].format;
631 return;
632 }
633 }
634
635 for(int i = 0; i < np; ++i)
636 {
637 const auto& obj = desc->objects[p[i].obj_idx];
638 const auto key = importKey(
639 obj.fd, obj.format_modifier, p[i].offset, p[i].pitch, p[i].w, p[i].h,
640 uint32_t(m_vk_plane_fmt[i]));
641
642 if(key)
643 {
644 if(auto it = m_vk_imports.find(*key); it != m_vk_imports.end())
645 {
646 samplers[i].texture->createFrom(QRhiTexture::NativeTexture{
647 quint64(it->second.image), VK_IMAGE_LAYOUT_GENERAL});
648 continue;
649 }
650 }
651
652 DMABufPlaneImporter::PlaneImport imported{};
653 if(!m_vk_importer.importPlane(
654 imported, obj.fd, obj.format_modifier, p[i].offset, p[i].pitch,
655 m_vk_plane_fmt[i], p[i].w, p[i].h))
656 {
657 qDebug() << "DRMPrimeDecoder: Vulkan importPlane failed for plane" << i
658 << "fd" << obj.fd;
659 return;
660 }
661
662 if(key && m_vk_imports.size() < kMaxImports)
663 {
664 m_vk_imports.emplace(*key, imported);
665 }
666 else
667 {
668 // Uncacheable or cache full: park it in the rotation, where the
669 // slot two frames old is the one destroyed.
670 auto& slot = m_vk_scratch[m_vk_scratchIdx];
671 m_vk_importer.cleanupPlane(slot.planes[i]);
672 slot.planes[i] = imported;
673 if(i == np - 1)
674 m_vk_scratchIdx = (m_vk_scratchIdx + 1) % kNumScratchSlots;
675 }
676
677 samplers[i].texture->createFrom(QRhiTexture::NativeTexture{
678 quint64(imported.image), VK_IMAGE_LAYOUT_GENERAL});
679 }
680 hasFrame = true;
681 return;
682 }
683#endif
684
685 if(m_backend == Backend::OpenGL)
686 {
687 // For packed RGB on GL, the EGL importer uses the descriptor's
688 // fourcc directly (EGL handles format mapping).
689 if(m_family == Family::PackedRGB)
690 m_gl_plane_fourcc[0] = desc->layers[0].format;
691
692 for(int i = 0; i < np; ++i)
693 {
694 const auto& obj = desc->objects[p[i].obj_idx];
695 const auto key = importKey(
696 obj.fd, obj.format_modifier, p[i].offset, p[i].pitch, p[i].w, p[i].h,
697 m_gl_plane_fourcc[i]);
698
699 if(key)
700 {
701 if(auto it = m_gl_imports.find(*key); it != m_gl_imports.end())
702 {
703 // The EGLImage is still good but the texture points at the last
704 // frame's buffer, so re-bind it. A pointer swap, not an import.
705 if(!m_gl_importer.bindPlane(m_gl_textures[i], it->second))
706 {
707 qDebug() << "DRMPrimeDecoder: EGL bindPlane failed for plane" << i;
708 return;
709 }
710 continue;
711 }
712 }
713
714 EglDmaBufImporter::PlaneImport imported{};
715 if(!m_gl_importer.importPlane(
716 imported, m_gl_textures[i], obj.fd, obj.format_modifier,
717 p[i].offset, p[i].pitch, m_gl_plane_fourcc[i], p[i].w, p[i].h))
718 {
719 qDebug() << "DRMPrimeDecoder: EGL importPlane failed for plane" << i
720 << "fd" << obj.fd << "fourcc" << Qt::hex
721 << m_gl_plane_fourcc[i];
722 return;
723 }
724
725 if(key && m_gl_imports.size() < kMaxImports)
726 {
727 m_gl_imports.emplace(*key, imported);
728 }
729 else
730 {
731 auto& slot = m_gl_scratch[m_gl_scratchIdx];
732 m_gl_importer.cleanupPlane(slot.planes[i]);
733 slot.planes[i] = imported;
734 if(i == np - 1)
735 m_gl_scratchIdx = (m_gl_scratchIdx + 1) % kNumScratchSlots;
736 }
737 }
738 hasFrame = true;
739 return;
740 }
741 }
742};
743
744} // namespace score::gfx
745
746#endif // SCORE_DRMPRIME_HAS_HWCONTEXT_DRM && Qt 6.6+
747#endif // __linux__
Graphics rendering pipeline for ossia score.
Definition Filter/PreviewWidget.hpp:11
std::pair< QShader, QShader > makeShaders(const RenderState &v, QString vert, QString frag, int multiViewCount)
Get a pair of compiled vertex / fragment shaders from GLSL 4.5 sources.
Definition score-plugin-gfx/Gfx/Graph/Utils.cpp:1238
Definition VideoInterface.hpp:26