Loading...
Searching...
No Matches
HWVideoToolbox.hpp
1#pragma once
2#if defined(__APPLE__)
3
4#include <Gfx/Graph/decoders/ColorSpace.hpp>
5#include <Gfx/Graph/decoders/GPUVideoDecoder.hpp>
6#include <Gfx/Graph/decoders/HWVideoToolbox_metal.hpp>
7#include <Gfx/Graph/decoders/NV12.hpp>
8#include <Gfx/Graph/decoders/P010.hpp>
9#include <Video/GpuFormats.hpp>
10
11#include <QtGui/private/qrhi_p.h>
12#include <QtGui/private/qrhimetal_p.h>
13
14extern "C" {
15#include <libavformat/avformat.h>
16#include <libavutil/pixdesc.h>
17#if __has_include(<libavutil/hwcontext_videotoolbox.h>)
18#include <libavutil/hwcontext_videotoolbox.h>
19#define SCORE_HAS_VTB_HWCONTEXT 1
20#endif
21}
22
23#if defined(SCORE_HAS_VTB_HWCONTEXT)
24
25namespace score::gfx
26{
27
28// MTLPixelFormat values (avoid Metal header dependency)
29enum
30{
31 ScoreMetalPixelFormatR8Unorm = 10,
32 ScoreMetalPixelFormatR16Unorm = 20,
33 ScoreMetalPixelFormatRG8Unorm = 30,
34 ScoreMetalPixelFormatRG16Unorm = 60,
35 ScoreMetalPixelFormatRGBA8Unorm = 70,
36 ScoreMetalPixelFormatRGBA16Unorm = 110,
37};
38
42static const constexpr auto ayuv_frag = R"_(#version 450
43
44)_" SCORE_GFX_VIDEO_UNIFORMS R"_(
45
46layout(binding=3) uniform sampler2D ayuv_tex;
47
48layout(location = 0) in vec2 v_texcoord;
49layout(location = 0) out vec4 fragColor;
50
51%2
52
53vec4 processTexture(vec4 tex) {
54 vec4 processed = convert_to_rgb(tex);
55 { %1 }
56 return processed;
57}
58
59void main()
60{
61 vec4 tex = texture(ayuv_tex, v_texcoord);
62 float y = tex.g; // Y is in green channel
63 float u = tex.b; // Cb is in blue channel
64 float v = tex.a; // Cr is in alpha channel
65 float a = tex.r; // Alpha is in red channel
66
67 vec4 rgb = processTexture(vec4(y, u, v, 1.));
68 fragColor = vec4(rgb.rgb, a);
69})_";
70
77struct HWVideoToolboxDecoder : GPUVideoDecoder
78{
79 Video::ImageFormat& decoder;
80 PixelFormatInfo m_fmt;
81 void* m_textureCache{}; // CVMetalTextureCacheRef
82
83 // Retained CVMetalTextureRef handles from the current frame.
84 // The Metal textures obtained via CVMetalTextureGetTexture are only valid
85 // while these refs are alive. Qt's createFrom() does not retain the texture,
86 // so we must keep these alive until the next frame replaces them.
87 void* m_retainedCvTexY{};
88 void* m_retainedCvTexUV{};
89
90 static bool isAvailable(QRhi& rhi)
91 {
92 return rhi.backend() == QRhi::Metal;
93 }
94
95 explicit HWVideoToolboxDecoder(
96 Video::ImageFormat& d, QRhi& rhi, PixelFormatInfo fmt)
97 : decoder{d}
98 , m_fmt{fmt}
99 {
100 auto* nh = static_cast<const QRhiMetalNativeHandles*>(rhi.nativeHandles());
101 if(nh && nh->dev)
102 m_textureCache = createMetalTextureCache(nh->dev);
103 }
104
105 ~HWVideoToolboxDecoder() override
106 {
107 releaseRetainedTextures();
108 if(m_textureCache)
109 {
110 releaseMetalTextureCache(m_textureCache);
111 m_textureCache = nullptr;
112 }
113 }
114
115 void releaseRetainedTextures()
116 {
117 if(m_retainedCvTexY)
118 {
119 releaseMetalTextureRef(m_retainedCvTexY);
120 m_retainedCvTexY = nullptr;
121 }
122 if(m_retainedCvTexUV)
123 {
124 releaseMetalTextureRef(m_retainedCvTexUV);
125 m_retainedCvTexUV = nullptr;
126 }
127 }
128
129 std::pair<QShader, QShader> init(RenderList& r) override
130 {
131 auto& rhi = *r.state.rhi;
132 const int w = decoder.width, h = decoder.height;
133
134 if(m_fmt.hasAlpha)
135 {
136 // Packed AYUV: single RGBA texture.
137 // QRhi doesn't have RGBA16 unorm, but createFrom() replaces the native
138 // texture — Metal reads the actual MTLPixelFormatRGBA16Unorm format.
139 // We use RGBA16F as a size-compatible placeholder (both are 8 bytes/pixel).
140 auto qrhiFmt = m_fmt.is10bit() ? QRhiTexture::RGBA16F : QRhiTexture::RGBA8;
141 auto tex = rhi.newTexture(qrhiFmt, {w, h}, 1, QRhiTexture::Flag{});
142 tex->create();
143 auto sampler = rhi.newSampler(
144 QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
145 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
146 sampler->create();
147 samplers.push_back({sampler, tex});
148
150 r.state, vertexShader(),
151 QString(ayuv_frag).arg("").arg(colorMatrix(decoder)));
152 }
153
154 // Semi-planar (BiPlanar): Y + UV textures.
155 auto texFmt = m_fmt.is10bit() ? QRhiTexture::R16 : QRhiTexture::R8;
156 auto uvFmt = m_fmt.is10bit() ? QRhiTexture::RG16 : QRhiTexture::RG8;
157 const int uvW = w >> m_fmt.log2ChromaW;
158 const int uvH = h >> m_fmt.log2ChromaH;
159
160 {
161 auto tex = rhi.newTexture(texFmt, {w, h}, 1, QRhiTexture::Flag{});
162 tex->create();
163 auto sampler = rhi.newSampler(
164 QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
165 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
166 sampler->create();
167 samplers.push_back({sampler, tex});
168 }
169 {
170 auto tex = rhi.newTexture(uvFmt, {uvW, uvH}, 1, QRhiTexture::Flag{});
171 tex->create();
172 auto sampler = rhi.newSampler(
173 QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
174 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
175 sampler->create();
176 samplers.push_back({sampler, tex});
177 }
178
179 if(m_fmt.is10bit())
180 {
182 r.state, vertexShader(),
183 QString(P010Decoder::frag).arg("").arg(colorMatrix(decoder)));
184 }
185
186 QString frag = NV12Decoder::nv12_filter_prologue;
187 frag += " vec3 yuv = vec3(y, u, v);\n";
188 frag += NV12Decoder::nv12_filter_epilogue;
190 r.state, vertexShader(), frag.arg("").arg(colorMatrix(decoder)));
191 }
192
193 void exec(RenderList& r, QRhiResourceUpdateBatch& res, AVFrame& frame) override
194 {
195 if(!m_textureCache)
196 {
197 failed = true;
198 return;
199 }
200 if(!Video::formatIsHardwareDecoded(static_cast<AVPixelFormat>(frame.format)))
201 {
202 failed = true;
203 return;
204 }
205
206 auto pixbuf = (CVPixelBufferRef)frame.data[3];
207 if(!pixbuf)
208 {
209 failed = true;
210 return;
211 }
212
213 releaseRetainedTextures();
214
215 if(m_fmt.hasAlpha)
216 {
217 execPackedAYUV(pixbuf);
218 return;
219 }
220
221 execSemiPlanar(pixbuf);
222 }
223
224private:
225 void execPackedAYUV(CVPixelBufferRef pixbuf)
226 {
227 // AYUV/AYUV64: single non-planar CVPixelBuffer → one RGBA texture.
228 // planeIndex=0 for non-planar buffers.
229 unsigned mtlFmt = m_fmt.is10bit()
230 ? ScoreMetalPixelFormatRGBA16Unorm
231 : ScoreMetalPixelFormatRGBA8Unorm;
232
233 auto tex = createMetalTextureFromPixelBuffer(
234 m_textureCache, pixbuf, 0, mtlFmt);
235 if(!tex.mtlTexture)
236 {
237 failed = true;
238 return;
239 }
240
241 QSize sz(tex.width, tex.height);
242 if(samplers[0].texture->pixelSize() != sz)
243 samplers[0].texture->setPixelSize(sz);
244 samplers[0].texture->createFrom(
245 QRhiTexture::NativeTexture{quint64(tex.mtlTexture), 0});
246 m_retainedCvTexY = tex.cvMetalTexture; // reuse Y slot for the single texture
247 }
248
249 void execSemiPlanar(CVPixelBufferRef pixbuf)
250 {
251 if(getPixelBufferPlaneCount(pixbuf) < 2)
252 {
253 failed = true;
254 return;
255 }
256
257 unsigned yFmt = m_fmt.is10bit() ? ScoreMetalPixelFormatR16Unorm : ScoreMetalPixelFormatR8Unorm;
258 unsigned uvFmt = m_fmt.is10bit() ? ScoreMetalPixelFormatRG16Unorm : ScoreMetalPixelFormatRG8Unorm;
259
260 // Y plane
261 auto yTex = createMetalTextureFromPixelBuffer(m_textureCache, pixbuf, 0, yFmt);
262 if(!yTex.mtlTexture)
263 {
264 failed = true;
265 return;
266 }
267 {
268 QSize ySize(yTex.width, yTex.height);
269 if(samplers[0].texture->pixelSize() != ySize)
270 samplers[0].texture->setPixelSize(ySize);
271 samplers[0].texture->createFrom(
272 QRhiTexture::NativeTexture{quint64(yTex.mtlTexture), 0});
273 m_retainedCvTexY = yTex.cvMetalTexture;
274 }
275
276 // UV plane
277 auto uvTex = createMetalTextureFromPixelBuffer(m_textureCache, pixbuf, 1, uvFmt);
278 if(!uvTex.mtlTexture)
279 {
280 failed = true;
281 return;
282 }
283 {
284 QSize uvSize(uvTex.width, uvTex.height);
285 if(samplers[1].texture->pixelSize() != uvSize)
286 samplers[1].texture->setPixelSize(uvSize);
287 samplers[1].texture->createFrom(
288 QRhiTexture::NativeTexture{quint64(uvTex.mtlTexture), 0});
289 m_retainedCvTexUV = uvTex.cvMetalTexture;
290 }
291 }
292};
293
294} // namespace score::gfx
295
296#endif // SCORE_HAS_VTB_HWCONTEXT
297#endif // __APPLE__
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