Loading...
Searching...
No Matches
HWVulkan.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_VK 1
21#endif
22}
23
24#if QT_HAS_VULKAN && defined(SCORE_HAS_DRM_HWCONTEXT_VK) && 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
30#include <unistd.h>
31
32// Guard against old Vulkan headers missing DRM format modifier extension
33#if defined(VK_EXT_image_drm_format_modifier) && defined(VK_KHR_external_memory_fd)
34
35namespace score::gfx
36{
37
52struct HWVulkanDecoder : GPUVideoDecoder
53{
54 Video::ImageFormat& decoder;
55 PixelFormatInfo m_fmt;
56 DMABufPlaneImporter m_importer;
57
58 using PlaneImport = DMABufPlaneImporter::PlaneImport;
59
60 static constexpr int NumSlots = 2;
61 struct ImportSlot
62 {
63 PlaneImport planes[2]{};
64 AVFrame* hwRef{};
65 };
66 ImportSlot m_slots[NumSlots]{};
67 int m_slotIdx{0};
68
69 static bool isAvailable(QRhi& rhi)
70 {
71 return DMABufPlaneImporter::isAvailable(rhi);
72 }
73
74 explicit HWVulkanDecoder(Video::ImageFormat& d, QRhi& rhi, PixelFormatInfo fmt)
75 : decoder{d}
76 , m_fmt{fmt}
77 {
78 m_importer.init(rhi);
79 }
80
81 ~HWVulkanDecoder() override
82 {
83 for(auto& slot : m_slots)
84 cleanupSlot(slot);
85 }
86
87 void cleanupSlot(ImportSlot& slot)
88 {
89 for(auto& p : slot.planes)
90 m_importer.cleanupPlane(p);
91 if(slot.hwRef)
92 {
93 av_frame_free(&slot.hwRef);
94 slot.hwRef = nullptr;
95 }
96 }
97
98 // ------------------------------------------------------------------
99 // init -- create placeholder textures and NV12/P010 shaders
100 // ------------------------------------------------------------------
101
102 std::pair<QShader, QShader> init(RenderList& r) override
103 {
104 auto& rhi = *r.state.rhi;
105 const auto w = decoder.width, h = decoder.height;
106
107 if(m_fmt.is10bit())
108 {
109 // P010 planes: R16 (Y) + RG16 (UV)
110 {
111 auto tex = rhi.newTexture(QRhiTexture::R16, {w, h}, 1, QRhiTexture::Flag{});
112 tex->create();
113 auto sampler = rhi.newSampler(
114 QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
115 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
116 sampler->create();
117 samplers.push_back({sampler, tex});
118 }
119 {
120 auto tex
121 = rhi.newTexture(QRhiTexture::RG16, {w / 2, h / 2}, 1, QRhiTexture::Flag{});
122 tex->create();
123 auto sampler = rhi.newSampler(
124 QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
125 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
126 sampler->create();
127 samplers.push_back({sampler, tex});
128 }
130 r.state, vertexShader(),
131 QString(P010Decoder::frag).arg("").arg(colorMatrix(decoder)));
132 }
133 else
134 {
135 // NV12 planes: R8 (Y) + RG8 (UV)
136 {
137 auto tex = rhi.newTexture(QRhiTexture::R8, {w, h}, 1, QRhiTexture::Flag{});
138 tex->create();
139 auto sampler = rhi.newSampler(
140 QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
141 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
142 sampler->create();
143 samplers.push_back({sampler, tex});
144 }
145 {
146 auto tex
147 = rhi.newTexture(QRhiTexture::RG8, {w / 2, h / 2}, 1, QRhiTexture::Flag{});
148 tex->create();
149 auto sampler = rhi.newSampler(
150 QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
151 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
152 sampler->create();
153 samplers.push_back({sampler, tex});
154 }
155
156 QString frag = NV12Decoder::nv12_filter_prologue;
157 frag += " vec3 yuv = vec3(y, u, v);\n";
158 frag += NV12Decoder::nv12_filter_epilogue;
160 r.state, vertexShader(), frag.arg("").arg(colorMatrix(decoder)));
161 }
162 }
163
164 // ------------------------------------------------------------------
165 // exec -- map Vulkan frame to DMA-BUF, import into Qt's Vulkan device
166 // ------------------------------------------------------------------
167
168 void exec(RenderList& r, QRhiResourceUpdateBatch& res, AVFrame& frame) override
169 {
170#if LIBAVUTIL_VERSION_MAJOR >= 57
171 if(!Video::formatIsHardwareDecoded(static_cast<AVPixelFormat>(frame.format)))
172 return;
173
174 // Advance ring-buffer slot (the one we reuse was last active 2 frames ago)
175 auto& slot = m_slots[m_slotIdx];
176 cleanupSlot(slot);
177 m_slotIdx = (m_slotIdx + 1) % NumSlots;
178
179 // Hold a reference to the Vulkan frame to prevent reuse
180 slot.hwRef = av_frame_alloc();
181 if(av_frame_ref(slot.hwRef, &frame) < 0)
182 {
183 av_frame_free(&slot.hwRef);
184 slot.hwRef = nullptr;
185 return;
186 }
187
188 // Map Vulkan frame -> DRM PRIME to obtain DMA-BUF file descriptors
189 AVFrame* drmFrame = av_frame_alloc();
190 drmFrame->format = AV_PIX_FMT_DRM_PRIME;
191
192 int ret = av_hwframe_map(
193 drmFrame, &frame, AV_HWFRAME_MAP_READ | AV_HWFRAME_MAP_DIRECT);
194 if(ret < 0)
195 {
196 qDebug() << "HWVulkanDecoder: av_hwframe_map failed:" << ret;
197 av_frame_free(&drmFrame);
198 return;
199 }
200
201 auto* desc = reinterpret_cast<AVDRMFrameDescriptor*>(drmFrame->data[0]);
202 if(!desc || desc->nb_objects < 1)
203 {
204 av_frame_unref(drmFrame);
205 av_frame_free(&drmFrame);
206 return;
207 }
208
209 // Parse plane info from the DRM descriptor.
210 // NV12/P010: Y plane + UV plane. Drivers may export them as:
211 // (a) 2 separate layers, each with 1 plane
212 // (b) 1 layer with 2 planes
213 struct PlaneInfo
214 {
215 int obj_idx;
216 ptrdiff_t offset;
217 ptrdiff_t pitch;
218 VkFormat format;
219 int w, h;
220 };
221 PlaneInfo planeInfo[2]{};
222 bool planesOk = false;
223 const int w = decoder.width;
224 const int h = decoder.height;
225
226 const VkFormat yFmt = m_fmt.is10bit() ? VK_FORMAT_R16_UNORM : VK_FORMAT_R8_UNORM;
227 const VkFormat uvFmt = m_fmt.is10bit() ? VK_FORMAT_R16G16_UNORM : VK_FORMAT_R8G8_UNORM;
228
229 if(desc->nb_layers >= 2 && desc->layers[0].nb_planes >= 1
230 && desc->layers[1].nb_planes >= 1)
231 {
232 // Case (a): separate layers
233 auto& yP = desc->layers[0].planes[0];
234 auto& uvP = desc->layers[1].planes[0];
235 planeInfo[0] = {yP.object_index, yP.offset, yP.pitch, yFmt, w, h};
236 planeInfo[1] = {uvP.object_index, uvP.offset, uvP.pitch, uvFmt, w / 2, h / 2};
237 planesOk = true;
238 }
239 else if(desc->nb_layers >= 1 && desc->layers[0].nb_planes >= 2)
240 {
241 // Case (b): single layer, multiple planes
242 auto& yP = desc->layers[0].planes[0];
243 auto& uvP = desc->layers[0].planes[1];
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
249 if(!planesOk)
250 {
251 qDebug() << "HWVulkanDecoder: unexpected DRM layout, layers:"
252 << desc->nb_layers;
253 av_frame_unref(drmFrame);
254 av_frame_free(&drmFrame);
255 return;
256 }
257
258 // Import each plane into a VkImage backed by the DMA-BUF memory
259 for(int i = 0; i < 2; ++i)
260 {
261 auto& pi = planeInfo[i];
262 auto& obj = desc->objects[pi.obj_idx];
263 if(!m_importer.importPlane(
264 slot.planes[i], obj.fd, obj.format_modifier, pi.offset, pi.pitch,
265 pi.format, pi.w, pi.h))
266 {
267 qDebug() << "HWVulkanDecoder: importPlane failed, plane" << i;
268 av_frame_unref(drmFrame);
269 av_frame_free(&drmFrame);
270 return;
271 }
272 }
273
274 // DRM mapping can be released now -- fds were dup'd during import,
275 // so Vulkan holds its own reference to the DMA-BUF buffer objects.
276 av_frame_unref(drmFrame);
277 av_frame_free(&drmFrame);
278
279 // Rewrap QRhiTextures with the imported VkImages
280 for(int i = 0; i < 2; ++i)
281 {
282 samplers[i].texture->createFrom(QRhiTexture::NativeTexture{
283 quint64(slot.planes[i].image), VK_IMAGE_LAYOUT_UNDEFINED});
284 }
285#endif // LIBAVUTIL_VERSION_MAJOR >= 57
286 }
287
288};
289
290} // namespace score::gfx
291
292#endif // VK_EXT_image_drm_format_modifier && VK_KHR_external_memory_fd
293#endif // QT_HAS_VULKAN && SCORE_HAS_DRM_HWCONTEXT_VK
294#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