Loading...
Searching...
No Matches
HWVAAPI.hpp
1#pragma once
2
3#if defined(__linux__)
4#include <Gfx/Graph/decoders/ColorSpace.hpp>
5#include <Gfx/Graph/decoders/DMABufImport.hpp>
6#include <Gfx/Graph/decoders/GPUVideoDecoder.hpp>
7#include <Gfx/Graph/decoders/NV12.hpp>
8#include <Gfx/Graph/decoders/P010.hpp>
9#include <Video/GpuFormats.hpp>
10#include <score/gfx/Vulkan.hpp>
11
12extern "C" {
13#include <libavformat/avformat.h>
14#include <libavutil/pixdesc.h>
15#if __has_include(<libavutil/hwcontext.h>)
16#include <libavutil/hwcontext.h>
17#endif
18#if __has_include(<libavutil/hwcontext_drm.h>)
19#include <libavutil/hwcontext_drm.h>
20#define SCORE_HAS_DRM_HWCONTEXT 1
21#endif
22}
23
24#if QT_HAS_VULKAN && defined(SCORE_HAS_DRM_HWCONTEXT) && QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
25#include <QtGui/private/qrhivulkan_p.h>
26#include <qvulkanfunctions.h>
27
28#include <vulkan/vulkan.h>
29#include <vulkan/vulkan.hpp>
30
31#include <unistd.h>
32
33// Guard against old Vulkan headers missing DRM format modifier extension
34#if defined(VK_EXT_image_drm_format_modifier) && defined(VK_KHR_external_memory_fd)
35
36namespace score::gfx
37{
38
53struct HWVaapiVulkanDecoder : GPUVideoDecoder
54{
55 Video::ImageFormat& decoder;
56 PixelFormatInfo m_fmt;
57 DMABufPlaneImporter m_importer;
58
59 using PlaneImport = DMABufPlaneImporter::PlaneImport;
60
61 // Ring buffer: slot N was last used 2 frames ago -> GPU is done with it.
62 static constexpr int NumSlots = 2;
63 struct ImportSlot
64 {
65 PlaneImport planes[2]{};
66 AVFrame* vaapiRef{}; // prevents VAAPI surface reuse while GPU reads
67 };
68 ImportSlot m_slots[NumSlots]{};
69 int m_slotIdx{0};
70
71 // ------------------------------------------------------------------
72
73 static bool isAvailable(QRhi& rhi)
74 {
75 return DMABufPlaneImporter::isAvailable(rhi);
76 }
77
78 explicit HWVaapiVulkanDecoder(
79 Video::ImageFormat& d, QRhi& rhi, PixelFormatInfo fmt)
80 : decoder{d}
81 , m_fmt{fmt}
82 {
83 m_importer.init(rhi);
84 }
85
86 ~HWVaapiVulkanDecoder() override
87 {
88 for(auto& slot : m_slots)
89 cleanupSlot(slot);
90 }
91
92 void cleanupSlot(ImportSlot& slot)
93 {
94 for(auto& p : slot.planes)
95 m_importer.cleanupPlane(p);
96 if(slot.vaapiRef)
97 {
98 av_frame_free(&slot.vaapiRef);
99 slot.vaapiRef = nullptr;
100 }
101 }
102
103 // ------------------------------------------------------------------
104 // init — create placeholder textures and NV12/P010 shaders
105 // ------------------------------------------------------------------
106
107 std::pair<QShader, QShader> init(RenderList& r) override
108 {
109 auto& rhi = *r.state.rhi;
110 const auto w = decoder.width, h = decoder.height;
111
112 if(m_fmt.is10bit())
113 {
114 // P010 planes: R16 (Y) + RG16 (UV)
115 {
116 auto tex = rhi.newTexture(QRhiTexture::R16, {w, h}, 1, QRhiTexture::Flag{});
117 tex->create();
118 auto sampler = rhi.newSampler(
119 QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
120 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
121 sampler->create();
122 samplers.push_back({sampler, tex});
123 }
124 {
125 auto tex
126 = rhi.newTexture(QRhiTexture::RG16, {w / 2, h / 2}, 1, QRhiTexture::Flag{});
127 tex->create();
128 auto sampler = rhi.newSampler(
129 QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
130 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
131 sampler->create();
132 samplers.push_back({sampler, tex});
133 }
135 r.state, vertexShader(),
136 QString(P010Decoder::frag).arg("").arg(colorMatrix(decoder)));
137 }
138 else
139 {
140 // NV12 planes: R8 (Y) + RG8 (UV)
141 {
142 auto tex = rhi.newTexture(QRhiTexture::R8, {w, h}, 1, QRhiTexture::Flag{});
143 tex->create();
144 auto sampler = rhi.newSampler(
145 QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
146 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
147 sampler->create();
148 samplers.push_back({sampler, tex});
149 }
150 {
151 auto tex
152 = rhi.newTexture(QRhiTexture::RG8, {w / 2, h / 2}, 1, QRhiTexture::Flag{});
153 tex->create();
154 auto sampler = rhi.newSampler(
155 QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
156 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
157 sampler->create();
158 samplers.push_back({sampler, tex});
159 }
160
161 QString frag = NV12Decoder::nv12_filter_prologue;
162 frag += " vec3 yuv = vec3(y, u, v);\n";
163 frag += NV12Decoder::nv12_filter_epilogue;
165 r.state, vertexShader(), frag.arg("").arg(colorMatrix(decoder)));
166 }
167 }
168
169 // ------------------------------------------------------------------
170 // exec — import DMA-BUF per-plane into Vulkan, rewrap QRhiTextures
171 // ------------------------------------------------------------------
172
173 void exec(RenderList& r, QRhiResourceUpdateBatch& res, AVFrame& frame) override
174 {
175#if LIBAVUTIL_VERSION_MAJOR >= 57
176 if(!Video::formatIsHardwareDecoded(static_cast<AVPixelFormat>(frame.format)))
177 {
178 // Already a software frame — shouldn't happen with this decoder
179 return;
180 }
181
182 // Advance ring-buffer slot (the one we reuse was last active 2 frames ago)
183 auto& slot = m_slots[m_slotIdx];
184 cleanupSlot(slot);
185 m_slotIdx = (m_slotIdx + 1) % NumSlots;
186
187 // Hold a reference to the VAAPI surface to prevent content reuse
188 slot.vaapiRef = av_frame_alloc();
189 if(av_frame_ref(slot.vaapiRef, &frame) < 0)
190 {
191 av_frame_free(&slot.vaapiRef);
192 slot.vaapiRef = nullptr;
193 return;
194 }
195
196 // Map VAAPI → DRM PRIME to obtain DMA-BUF file descriptors
197 AVFrame* drmFrame = av_frame_alloc();
198 drmFrame->format = AV_PIX_FMT_DRM_PRIME;
199
200 int ret = av_hwframe_map(drmFrame, &frame,
201 AV_HWFRAME_MAP_READ | AV_HWFRAME_MAP_DIRECT);
202 if(ret < 0)
203 {
204 qDebug() << "HWVaapiVulkanDecoder: av_hwframe_map failed:" << ret;
205 av_frame_free(&drmFrame);
206 return;
207 }
208
209 auto* desc = reinterpret_cast<AVDRMFrameDescriptor*>(drmFrame->data[0]);
210 if(!desc || desc->nb_objects < 1)
211 {
212 av_frame_unref(drmFrame);
213 av_frame_free(&drmFrame);
214 return;
215 }
216
217 // Parse plane info from the DRM descriptor.
218 // NV12/P010: Y plane + UV plane. Drivers may export them as:
219 // (a) 2 separate layers, each with 1 plane
220 // (b) 1 layer with 2 planes
221 struct PlaneInfo
222 {
223 int obj_idx;
224 ptrdiff_t offset;
225 ptrdiff_t pitch;
226 VkFormat format;
227 int w, h;
228 };
229 PlaneInfo planeInfo[2]{};
230 bool planesOk = false;
231 const int w = decoder.width;
232 const int h = decoder.height;
233
234 const VkFormat yFmt = m_fmt.is10bit() ? VK_FORMAT_R16_UNORM : VK_FORMAT_R8_UNORM;
235 const VkFormat uvFmt = m_fmt.is10bit() ? VK_FORMAT_R16G16_UNORM : VK_FORMAT_R8G8_UNORM;
236
237 if(desc->nb_layers >= 2
238 && desc->layers[0].nb_planes >= 1
239 && desc->layers[1].nb_planes >= 1)
240 {
241 // Case (a): separate layers
242 auto& yP = desc->layers[0].planes[0];
243 auto& uvP = desc->layers[1].planes[0];
244 planeInfo[0] = {yP.object_index, yP.offset, yP.pitch, yFmt, w, h};
245 planeInfo[1] = {uvP.object_index, uvP.offset, uvP.pitch, uvFmt, w / 2, h / 2};
246 planesOk = true;
247 }
248 else if(desc->nb_layers >= 1 && desc->layers[0].nb_planes >= 2)
249 {
250 // Case (b): single layer, multiple planes
251 auto& yP = desc->layers[0].planes[0];
252 auto& uvP = desc->layers[0].planes[1];
253 planeInfo[0] = {yP.object_index, yP.offset, yP.pitch, yFmt, w, h};
254 planeInfo[1] = {uvP.object_index, uvP.offset, uvP.pitch, uvFmt, w / 2, h / 2};
255 planesOk = true;
256 }
257
258 if(!planesOk)
259 {
260 qDebug() << "HWVaapiVulkanDecoder: unexpected DRM layout, layers:"
261 << desc->nb_layers;
262 av_frame_unref(drmFrame);
263 av_frame_free(&drmFrame);
264 return;
265 }
266
267 // Import each plane into a VkImage backed by the DMA-BUF memory
268 for(int i = 0; i < 2; ++i)
269 {
270 auto& pi = planeInfo[i];
271 auto& obj = desc->objects[pi.obj_idx];
272 if(!m_importer.importPlane(slot.planes[i], obj.fd, obj.format_modifier,
273 pi.offset, pi.pitch, pi.format, pi.w, pi.h))
274 {
275 qDebug() << "HWVaapiVulkanDecoder: importPlane failed, plane" << i;
276 av_frame_unref(drmFrame);
277 av_frame_free(&drmFrame);
278 return;
279 }
280 }
281
282 // DRM mapping can be released now — fds were dup'd during import,
283 // so Vulkan holds its own reference to the DMA-BUF buffer objects.
284 av_frame_unref(drmFrame);
285 av_frame_free(&drmFrame);
286
287 // Rewrap QRhiTextures with the imported VkImages
288 for(int i = 0; i < 2; ++i)
289 {
290 samplers[i].texture->createFrom(
291 QRhiTexture::NativeTexture{
292 quint64(slot.planes[i].image),
293 VK_IMAGE_LAYOUT_UNDEFINED});
294 }
295#endif // LIBAVUTIL_VERSION_MAJOR >= 57
296 }
297
298};
299
300} // namespace score::gfx
301
302#endif // VK_EXT_image_drm_format_modifier && VK_KHR_external_memory_fd
303#endif // QT_HAS_VULKAN && SCORE_HAS_DRM_HWCONTEXT
304#endif // __linux__
Graphics rendering pipeline for ossia score.
Definition Filter/PreviewWidget.hpp:12
std::pair< QShader, QShader > makeShaders(const RenderState &v, QString vert, QString frag)
Get a pair of compiled vertex / fragment shaders from GLSL 4.5 sources.
Definition score-plugin-gfx/Gfx/Graph/Utils.cpp:395
Definition VideoInterface.hpp:26