Loading...
Searching...
No Matches
encoders/YUV422P10.hpp
1#pragma once
2#include <Gfx/Graph/encoders/GPUVideoEncoder.hpp>
3
4namespace score::gfx
5{
6
34#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)
36{
37 // %1 = colorMatrixOut() shader defining convert_from_rgb(vec3).
38 // %2 = the YUV component selector (x / y / z).
39 static constexpr const char* plane_frag = R"_(#version 450
40 layout(location = 0) in vec2 v_texcoord;
41 layout(location = 0) out vec4 fragColor;
42 layout(binding = 3) uniform sampler2D src_tex;
43 )_" "%1" R"_(
44 vec2 flip_y(vec2 tc) {
45 // Only OpenGL. The rest of the engine puts its geometry through
46 // renderer.clipSpaceCorrMatrix, which negates Y on Vulkan; this pass draws
47 // a hardcoded triangle in raw NDC and does not, so the correction it needs
48 // is not the same one. Flipping on Vulkan as well handed libav, GStreamer,
49 // NDI and every other consumer an upside-down picture.
50 #if defined(QSHADER_SPIRV) || defined(QSHADER_MSL) || defined(QSHADER_HLSL)
51 return tc;
52 #else
53 return vec2(tc.x, 1.0 - tc.y);
54 #endif
55 }
56 void main() {
57 vec3 rgb = texture(src_tex, flip_y(v_texcoord)).rgb;
58 vec3 yuv = clamp(convert_from_rgb(rgb), 0.0, 1.0);
59 // 10-bit value packed into the low 10 bits of the R16 word.
60 fragColor = vec4(yuv.%2 * (1023.0 / 65535.0), 0.0, 0.0, 1.0);
61 }
62 )_";
63
64 struct PlaneResources
65 {
66 QRhiTexture* texture{};
67 QRhiTextureRenderTarget* rt{};
68 QRhiRenderPassDescriptor* rp{};
69 QRhiShaderResourceBindings* srb{};
70 QRhiGraphicsPipeline* pipeline{};
71 QRhiReadbackResult readback{};
72
73 void destroy()
74 {
75 delete pipeline;
76 delete srb;
77 delete rp;
78 delete rt;
79 delete texture;
80 pipeline = nullptr;
81 srb = nullptr;
82 rp = nullptr;
83 rt = nullptr;
84 texture = nullptr;
85 }
86 };
87
88 PlaneResources m_planes[3]; // Y, U, V
89 QRhiSampler* m_sampler{};
90 int m_width{};
91 int m_height{};
92
93 void initPlane(
94 QRhi& rhi, const RenderState& state, QRhiTexture* inputRGBA,
95 PlaneResources& plane, int w, int h, const char* component,
96 const QString& colorConversion)
97 {
98 plane.texture = rhi.newTexture(
99 QRhiTexture::R16, QSize{w, h}, 1,
100 QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource);
101 plane.texture->create();
102
103 plane.rt = rhi.newTextureRenderTarget({plane.texture});
104 plane.rp = plane.rt->newCompatibleRenderPassDescriptor();
105 plane.rt->setRenderPassDescriptor(plane.rp);
106 plane.rt->create();
107
108 plane.srb = rhi.newShaderResourceBindings();
109 plane.srb->setBindings({
110 QRhiShaderResourceBinding::sampledTexture(
111 3, QRhiShaderResourceBinding::FragmentStage, inputRGBA, m_sampler),
112 });
113 plane.srb->create();
114
115 auto [vs, fs] = makeShaders(
116 state, QString::fromLatin1(vertex_shader),
117 QString::fromLatin1(plane_frag).arg(colorConversion).arg(component));
118 plane.pipeline = rhi.newGraphicsPipeline();
119 plane.pipeline->setShaderStages({
120 {QRhiShaderStage::Vertex, vs},
121 {QRhiShaderStage::Fragment, fs},
122 });
123 plane.pipeline->setVertexInputLayout({});
124 plane.pipeline->setShaderResourceBindings(plane.srb);
125 plane.pipeline->setRenderPassDescriptor(plane.rp);
126 plane.pipeline->create();
127 }
128
129 void
130 execPlane(QRhi& rhi, QRhiCommandBuffer& cb, PlaneResources& plane, int w, int h)
131 {
132 cb.beginPass(plane.rt, Qt::black, {0.0f, 0});
133 cb.setGraphicsPipeline(plane.pipeline);
134 cb.setShaderResources(plane.srb);
135 cb.setViewport(QRhiViewport(0, 0, w, h));
136 cb.draw(3);
137
138 auto* batch = rhi.nextResourceUpdateBatch();
139 batch->readBackTexture(QRhiReadbackDescription{plane.texture}, &plane.readback);
140 cb.endPass(batch);
141 }
142
143 void init(
144 QRhi& rhi, const RenderState& state, QRhiTexture* inputRGBA, int width,
145 int height, const QString& colorConversion) override
146 {
147 m_width = width;
148 m_height = height;
149
150 m_sampler = rhi.newSampler(
151 QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
152 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
153 m_sampler->create();
154
155 // 4:2:2 -> U/V are half-width, full-height.
156 initPlane(rhi, state, inputRGBA, m_planes[0], width, height, "x", colorConversion);
157 initPlane(rhi, state, inputRGBA, m_planes[1], width / 2, height, "y", colorConversion);
158 initPlane(rhi, state, inputRGBA, m_planes[2], width / 2, height, "z", colorConversion);
159 }
160
161 void exec(QRhi& rhi, QRhiCommandBuffer& cb) override
162 {
163 execPlane(rhi, cb, m_planes[0], m_width, m_height);
164 execPlane(rhi, cb, m_planes[1], m_width / 2, m_height);
165 execPlane(rhi, cb, m_planes[2], m_width / 2, m_height);
166 }
168 int planeCount() const override { return 3; }
169
170 const QRhiReadbackResult& readback(int plane) const override
171 {
172 return m_planes[plane].readback;
173 }
174
175 void release() override
176 {
177 for(auto& p : m_planes)
178 p.destroy();
179 delete m_sampler;
180 m_sampler = nullptr;
181 }
182};
183#else // Qt < 6.10: pack 16-bit samples into RGBA8 for a tight GL readback.
184struct YUV422P10Encoder : GPUVideoEncoder
185{
186 // Helpers shared by all plane shaders (injected as %2).
187 static constexpr const char* common = R"_(
188 int flip_y_int(int y, int h) {
189 // See GPUVideoEncoder::y_flip_glsl: only OpenGL. The rest of the engine
190 // negates Y through renderer.clipSpaceCorrMatrix on Vulkan; this pass
191 // indexes texels directly and does not, so it must not flip there.
192 #if defined(QSHADER_SPIRV) || defined(QSHADER_MSL) || defined(QSHADER_HLSL)
193 return y;
194 #else
195 return h - 1 - y;
196 #endif
197 }
198 uint comp10(vec3 rgb, int c) {
199 vec3 yuv = clamp(convert_from_rgb(rgb), 0.0, 1.0);
200 float v = (c == 0) ? yuv.x : (c == 1 ? yuv.y : yuv.z);
201 return uint(v * 1023.0 + 0.5);
202 }
203 // Two 16-bit values -> 4 little-endian bytes in an RGBA8 texel.
204 vec4 pack2(uint a, uint b) {
205 return vec4(float(a & 0xFFu), float((a >> 8u) & 0xFFu),
206 float(b & 0xFFu), float((b >> 8u) & 0xFFu)) / 255.0;
207 }
208 )_";
209
210 // %1 = colorMatrixOut(), %2 = common. Y plane: 2 full-res luma per texel.
211 static constexpr const char* y_frag = R"_(#version 450
212 layout(location = 0) in vec2 v_texcoord;
213 layout(location = 0) out vec4 fragColor;
214 layout(binding = 3) uniform sampler2D src_tex;
215 )_" "%1" R"_(
216 )_" "%2" R"_(
217 void main() {
218 ivec2 sz = textureSize(src_tex, 0);
219 ivec2 o = ivec2(gl_FragCoord.xy);
220 int sy = flip_y_int(o.y, sz.y);
221 int x0 = o.x * 2;
222 uint a = comp10(texelFetch(src_tex, ivec2(min(x0, sz.x - 1), sy), 0).rgb, 0);
223 uint b = comp10(texelFetch(src_tex, ivec2(min(x0 + 1, sz.x - 1), sy), 0).rgb, 0);
224 fragColor = pack2(a, b);
225 }
226 )_";
227
228 // %1 = colorMatrixOut(), %2 = common, %3 = component (1=U, 2=V).
229 // U/V plane: 2 chroma per texel, each = average of 2 adjacent pixels.
230 static constexpr const char* uv_frag = R"_(#version 450
231 layout(location = 0) in vec2 v_texcoord;
232 layout(location = 0) out vec4 fragColor;
233 layout(binding = 3) uniform sampler2D src_tex;
234 )_" "%1" R"_(
235 )_" "%2" R"_(
236 uint chroma(int x, int sy, ivec2 sz) {
237 uint a = comp10(texelFetch(src_tex, ivec2(min(x, sz.x - 1), sy), 0).rgb, %3);
238 uint b = comp10(texelFetch(src_tex, ivec2(min(x + 1, sz.x - 1), sy), 0).rgb, %3);
239 return (a + b) >> 1u;
240 }
241 void main() {
242 ivec2 sz = textureSize(src_tex, 0);
243 ivec2 o = ivec2(gl_FragCoord.xy);
244 int sy = flip_y_int(o.y, sz.y);
245 int x0 = o.x * 4;
246 fragColor = pack2(chroma(x0, sy, sz), chroma(x0 + 2, sy, sz));
247 }
248 )_";
249
250 struct PlaneResources
251 {
252 QRhiTexture* texture{};
253 QRhiTextureRenderTarget* rt{};
254 QRhiRenderPassDescriptor* rp{};
255 QRhiShaderResourceBindings* srb{};
256 QRhiGraphicsPipeline* pipeline{};
257 QRhiReadbackResult readback{};
258 int w{}, h{};
259
260 void destroy()
261 {
262 delete pipeline; delete srb; delete rp; delete rt; delete texture;
263 pipeline = nullptr; srb = nullptr; rp = nullptr; rt = nullptr; texture = nullptr;
264 }
265 };
266
267 PlaneResources m_planes[3]; // Y, U, V (packed RGBA8 targets)
268 QRhiSampler* m_sampler{};
269 int m_width{};
270 int m_height{};
271
272 void initPlane(
273 QRhi& rhi, const RenderState& state, QRhiTexture* inputRGBA,
274 PlaneResources& plane, int packW, int packH, const QString& frag)
275 {
276 plane.w = packW;
277 plane.h = packH;
278 plane.texture = rhi.newTexture(
279 QRhiTexture::RGBA8, QSize{packW, packH}, 1,
280 QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource);
281 plane.texture->create();
282
283 plane.rt = rhi.newTextureRenderTarget({plane.texture});
284 plane.rp = plane.rt->newCompatibleRenderPassDescriptor();
285 plane.rt->setRenderPassDescriptor(plane.rp);
286 plane.rt->create();
287
288 plane.srb = rhi.newShaderResourceBindings();
289 plane.srb->setBindings({
290 QRhiShaderResourceBinding::sampledTexture(
291 3, QRhiShaderResourceBinding::FragmentStage, inputRGBA, m_sampler),
292 });
293 plane.srb->create();
294
295 auto [vs, fs] = makeShaders(
296 state, QString::fromLatin1(vertex_shader), frag);
297 plane.pipeline = rhi.newGraphicsPipeline();
298 plane.pipeline->setShaderStages({
299 {QRhiShaderStage::Vertex, vs}, {QRhiShaderStage::Fragment, fs}});
300 plane.pipeline->setVertexInputLayout({});
301 plane.pipeline->setShaderResourceBindings(plane.srb);
302 plane.pipeline->setRenderPassDescriptor(plane.rp);
303 plane.pipeline->create();
304 }
305
306 void execPlane(QRhi& rhi, QRhiCommandBuffer& cb, PlaneResources& p)
307 {
308 cb.beginPass(p.rt, Qt::black, {0.0f, 0});
309 cb.setGraphicsPipeline(p.pipeline);
310 cb.setShaderResources(p.srb);
311 cb.setViewport(QRhiViewport(0, 0, p.w, p.h));
312 cb.draw(3);
313 auto* batch = rhi.nextResourceUpdateBatch();
314 batch->readBackTexture(QRhiReadbackDescription{p.texture}, &p.readback);
315 cb.endPass(batch);
316 }
317
318 void init(
319 QRhi& rhi, const RenderState& state, QRhiTexture* inputRGBA, int width,
320 int height, const QString& colorConversion) override
321 {
322 m_width = width;
323 m_height = height;
324 m_sampler = rhi.newSampler(
325 QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
326 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
327 m_sampler->create();
328
329 const QString cm = QString::fromLatin1(common);
330 const QString y = QString::fromLatin1(y_frag).arg(colorConversion, cm);
331 const QString u
332 = QString::fromLatin1(uv_frag).arg(colorConversion, cm, QStringLiteral("1"));
333 const QString v
334 = QString::fromLatin1(uv_frag).arg(colorConversion, cm, QStringLiteral("2"));
335
336 // RGBA8 pack widths: Y=w/2 (2 luma/texel), U/V=w/4 (2 chroma/texel).
337 initPlane(rhi, state, inputRGBA, m_planes[0], width / 2, height, y);
338 initPlane(rhi, state, inputRGBA, m_planes[1], width / 4, height, u);
339 initPlane(rhi, state, inputRGBA, m_planes[2], width / 4, height, v);
340 }
341
342 void exec(QRhi& rhi, QRhiCommandBuffer& cb) override
343 {
344 execPlane(rhi, cb, m_planes[0]);
345 execPlane(rhi, cb, m_planes[1]);
346 execPlane(rhi, cb, m_planes[2]);
347 }
348
349 int planeCount() const override { return 3; }
350
351 const QRhiReadbackResult& readback(int plane) const override
352 {
353 return m_planes[plane].readback;
354 }
355
356 void release() override
357 {
358 for(auto& p : m_planes)
359 p.destroy();
360 delete m_sampler;
361 m_sampler = nullptr;
362 }
363};
364#endif
365
366} // namespace score::gfx
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
Base class for GPU-side video format conversion (RGBA to YUV).
Definition GPUVideoEncoder.hpp:30
static constexpr const char * vertex_shader
Definition GPUVideoEncoder.hpp:74
Global state associated to a rendering context.
Definition RenderState.hpp:37
Definition encoders/YUV422P10.hpp:62
GPU RGBA -> yuv422p10le encoder (planar 4:2:2, 10-bit).
Definition encoders/YUV422P10.hpp:36
int planeCount() const override
Number of readback planes (1 for UYVY, 2 for NV12, 3 for I420).
Definition encoders/YUV422P10.hpp:165
void release() override
Release all GPU resources.
Definition encoders/YUV422P10.hpp:172
const QRhiReadbackResult & readback(int plane) const override
Get the readback result for a given plane. Valid after endOffscreenFrame.
Definition encoders/YUV422P10.hpp:167
void init(QRhi &rhi, const RenderState &state, QRhiTexture *inputRGBA, int width, int height, const QString &colorConversion) override
Definition encoders/YUV422P10.hpp:140
void exec(QRhi &rhi, QRhiCommandBuffer &cb) override
Definition encoders/YUV422P10.hpp:158