Loading...
Searching...
No Matches
P216.hpp
1#pragma once
2#include <Gfx/Graph/encoders/GPUVideoEncoder.hpp>
3
4namespace score::gfx
5{
6
38#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)
40{
41 // %1 = colorMatrixOut() shader defining convert_from_rgb(vec3).
42 // R16 is UNORM: writing v in [0,1] stores round(v * 65535), which is the
43 // 16-bit sample. No packing helper needed, unlike the 10-bit encoders.
44 static constexpr const char* y_frag = R"_(#version 450
45 layout(location = 0) in vec2 v_texcoord;
46 layout(location = 0) out vec4 fragColor;
47 layout(binding = 3) uniform sampler2D src_tex;
48 )_" "%1" R"_(
49 vec2 flip_y(vec2 tc) {
50 // Only OpenGL. The rest of the engine puts its geometry through
51 // renderer.clipSpaceCorrMatrix, which negates Y on Vulkan; this pass draws
52 // a hardcoded triangle in raw NDC and does not, so the correction it needs
53 // is not the same one. Flipping on Vulkan as well handed libav, GStreamer,
54 // NDI and every other consumer an upside-down picture.
55 #if defined(QSHADER_SPIRV) || defined(QSHADER_MSL) || defined(QSHADER_HLSL)
56 return tc;
57 #else
58 return vec2(tc.x, 1.0 - tc.y);
59 #endif
60 }
61 void main() {
62 vec3 rgb = texture(src_tex, flip_y(v_texcoord)).rgb;
63 vec3 yuv = convert_from_rgb(rgb);
64 fragColor = vec4(clamp(yuv.x, 0.0, 1.0), 0.0, 0.0, 1.0);
65 }
66 )_";
67
68 // Rendered at half width, FULL height: 4:2:2 keeps every line's chroma.
69 // Bilinear sampling averages the horizontal pair, and only that pair -- a
70 // vertical average here would be 4:2:0 and would quietly halve the chroma
71 // resolution of a format whose whole point is that it does not.
72 static constexpr const char* uv_frag = R"_(#version 450
73 layout(location = 0) in vec2 v_texcoord;
74 layout(location = 0) out vec4 fragColor;
75 layout(binding = 3) uniform sampler2D src_tex;
76 )_" "%1" R"_(
77 vec2 flip_y(vec2 tc) {
78 #if defined(QSHADER_SPIRV) || defined(QSHADER_MSL) || defined(QSHADER_HLSL)
79 return tc;
80 #else
81 return vec2(tc.x, 1.0 - tc.y);
82 #endif
83 }
84 void main() {
85 vec3 rgb = texture(src_tex, flip_y(v_texcoord)).rgb;
86 vec3 yuv = convert_from_rgb(rgb);
87 fragColor = vec4(clamp(yuv.y, 0.0, 1.0), clamp(yuv.z, 0.0, 1.0), 0.0, 1.0);
88 }
89 )_";
90
91 QRhiTexture* m_yTexture{};
92 QRhiTextureRenderTarget* m_yRT{};
93 QRhiRenderPassDescriptor* m_yRP{};
94 QRhiShaderResourceBindings* m_ySRB{};
95 QRhiGraphicsPipeline* m_yPipeline{};
96 QRhiReadbackResult m_yReadback{};
97
98 QRhiTexture* m_uvTexture{};
99 QRhiTextureRenderTarget* m_uvRT{};
100 QRhiRenderPassDescriptor* m_uvRP{};
101 QRhiShaderResourceBindings* m_uvSRB{};
102 QRhiGraphicsPipeline* m_uvPipeline{};
103 QRhiReadbackResult m_uvReadback{};
104
105 QRhiSampler* m_sampler{};
106 int m_width{};
107 int m_height{};
108 bool m_readbackEnabled{true};
109
110 void setupPlane(
111 QRhi& rhi, const RenderState& state, QRhiTexture* inputRGBA,
112 QRhiTexture::Format fmt, int w, int h, const char* frag,
113 const QString& colorConversion, QRhiTexture*& tex,
114 QRhiTextureRenderTarget*& rt, QRhiRenderPassDescriptor*& rp,
115 QRhiShaderResourceBindings*& srb, QRhiGraphicsPipeline*& pipeline)
116 {
117 tex = rhi.newTexture(
118 fmt, QSize{w, h}, 1,
119 QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource);
120 tex->create();
121
122 rt = rhi.newTextureRenderTarget({tex});
123 rp = rt->newCompatibleRenderPassDescriptor();
124 rt->setRenderPassDescriptor(rp);
125 rt->create();
126
127 srb = rhi.newShaderResourceBindings();
128 srb->setBindings({
129 QRhiShaderResourceBinding::sampledTexture(
130 3, QRhiShaderResourceBinding::FragmentStage, inputRGBA, m_sampler),
131 });
132 srb->create();
133
134 auto [vs, fs] = makeShaders(
135 state, QString::fromLatin1(vertex_shader),
136 QString::fromLatin1(frag).arg(colorConversion));
137 pipeline = rhi.newGraphicsPipeline();
138 pipeline->setShaderStages({
139 {QRhiShaderStage::Vertex, vs},
140 {QRhiShaderStage::Fragment, fs},
141 });
142 pipeline->setVertexInputLayout({});
143 pipeline->setShaderResourceBindings(srb);
144 pipeline->setRenderPassDescriptor(rp);
145 pipeline->create();
146 }
147
148 void init(
149 QRhi& rhi, const RenderState& state, QRhiTexture* inputRGBA, int width,
150 int height, const QString& colorConversion) override
151 {
152 m_width = width;
153 m_height = height;
154
155 m_sampler = rhi.newSampler(
156 QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
157 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
158 m_sampler->create();
159
160 setupPlane(
161 rhi, state, inputRGBA, QRhiTexture::R16, width, height, y_frag,
162 colorConversion, m_yTexture, m_yRT, m_yRP, m_ySRB, m_yPipeline);
163 setupPlane(
164 rhi, state, inputRGBA, QRhiTexture::RG16, width / 2, height, uv_frag,
165 colorConversion, m_uvTexture, m_uvRT, m_uvRP, m_uvSRB, m_uvPipeline);
166 }
167
168 void exec(QRhi& rhi, QRhiCommandBuffer& cb) override
169 {
170 cb.beginPass(m_yRT, Qt::black, {0.0f, 0});
171 cb.setGraphicsPipeline(m_yPipeline);
172 cb.setShaderResources(m_ySRB);
173 cb.setViewport(QRhiViewport(0, 0, m_width, m_height));
174 cb.draw(3);
175 if(m_readbackEnabled)
176 {
177 auto* yb = rhi.nextResourceUpdateBatch();
178 yb->readBackTexture(QRhiReadbackDescription{m_yTexture}, &m_yReadback);
179 cb.endPass(yb);
180 }
181 else
182 {
183 cb.endPass();
184 }
185
186 cb.beginPass(m_uvRT, Qt::black, {0.0f, 0});
187 cb.setGraphicsPipeline(m_uvPipeline);
188 cb.setShaderResources(m_uvSRB);
189 cb.setViewport(QRhiViewport(0, 0, m_width / 2, m_height));
190 cb.draw(3);
191 if(m_readbackEnabled)
192 {
193 auto* uvb = rhi.nextResourceUpdateBatch();
194 uvb->readBackTexture(QRhiReadbackDescription{m_uvTexture}, &m_uvReadback);
195 cb.endPass(uvb);
196 }
197 else
198 {
199 cb.endPass();
200 }
201 }
202
203 int planeCount() const override { return 2; }
204
205 const QRhiReadbackResult& readback(int plane) const override
207 return plane == 0 ? m_yReadback : m_uvReadback;
208 }
209
212 QRhiTexture* planeTexture(int plane) const noexcept
214 return plane == 0 ? m_yTexture : m_uvTexture;
215 }
216
217 void setReadbackEnabled(bool e) noexcept override { m_readbackEnabled = e; }
218
219 void release() override
220 {
221 delete m_uvPipeline;
222 delete m_uvSRB;
223 delete m_uvRP;
224 delete m_uvRT;
225 delete m_uvTexture;
226 delete m_yPipeline;
227 delete m_ySRB;
228 delete m_yRP;
229 delete m_yRT;
230 delete m_yTexture;
231 delete m_sampler;
232 m_uvPipeline = m_yPipeline = nullptr;
233 m_uvSRB = m_ySRB = nullptr;
234 m_uvRP = m_yRP = nullptr;
235 m_uvRT = m_yRT = nullptr;
236 m_uvTexture = m_yTexture = nullptr;
237 m_sampler = nullptr;
238 }
239};
240#else // Qt < 6.10: pack 16-bit samples into RGBA8 for a tight GL readback.
241struct P216Encoder : GPUVideoEncoder
242{
243 static constexpr const char* common = R"_(
244 int flip_y_int(int y, int h) {
245 // See GPUVideoEncoder::y_flip_glsl: only OpenGL. The rest of the engine
246 // negates Y through renderer.clipSpaceCorrMatrix on Vulkan; this pass
247 // indexes texels directly and does not, so it must not flip there.
248 #if defined(QSHADER_SPIRV) || defined(QSHADER_MSL) || defined(QSHADER_HLSL)
249 return y;
250 #else
251 return h - 1 - y;
252 #endif
253 }
254 // Component (0=Y,1=U,2=V) as a full-range 16-bit word.
255 uint s16(vec3 rgb, int c) {
256 vec3 yuv = clamp(convert_from_rgb(rgb), 0.0, 1.0);
257 float v = (c == 0) ? yuv.x : (c == 1 ? yuv.y : yuv.z);
258 return uint(v * 65535.0 + 0.5);
259 }
260 vec4 pack2(uint a, uint b) {
261 return vec4(float(a & 0xFFu), float((a >> 8u) & 0xFFu),
262 float(b & 0xFFu), float((b >> 8u) & 0xFFu)) / 255.0;
263 }
264 )_";
265
266 // %1 = colorMatrixOut(), %2 = common. Y: two full-res luma per texel.
267 static constexpr const char* y_frag = R"_(#version 450
268 layout(location = 0) in vec2 v_texcoord;
269 layout(location = 0) out vec4 fragColor;
270 layout(binding = 3) uniform sampler2D src_tex;
271 )_" "%1" R"_(
272 )_" "%2" R"_(
273 void main() {
274 ivec2 sz = textureSize(src_tex, 0);
275 ivec2 o = ivec2(gl_FragCoord.xy);
276 int sy = flip_y_int(o.y, sz.y);
277 int x0 = o.x * 2;
278 uint a = s16(texelFetch(src_tex, ivec2(min(x0, sz.x - 1), sy), 0).rgb, 0);
279 uint b = s16(texelFetch(src_tex, ivec2(min(x0 + 1, sz.x - 1), sy), 0).rgb, 0);
280 fragColor = pack2(a, b);
281 }
282 )_";
283
284 // %1 = colorMatrixOut(), %2 = common. UV: one chroma site per texel, averaged
285 // over the horizontal pair on THIS line only -- 4:2:2.
286 static constexpr const char* uv_frag = R"_(#version 450
287 layout(location = 0) in vec2 v_texcoord;
288 layout(location = 0) out vec4 fragColor;
289 layout(binding = 3) uniform sampler2D src_tex;
290 )_" "%1" R"_(
291 )_" "%2" R"_(
292 uint avg2(int x, int y, ivec2 sz, int c) {
293 int xa = min(x, sz.x - 1), xb = min(x + 1, sz.x - 1);
294 uint s = s16(texelFetch(src_tex, ivec2(xa, y), 0).rgb, c)
295 + s16(texelFetch(src_tex, ivec2(xb, y), 0).rgb, c);
296 return s >> 1u;
297 }
298 void main() {
299 ivec2 sz = textureSize(src_tex, 0);
300 ivec2 o = ivec2(gl_FragCoord.xy);
301 int x0 = o.x * 2;
302 int sy = flip_y_int(o.y, sz.y);
303 fragColor = pack2(avg2(x0, sy, sz, 1), avg2(x0, sy, sz, 2));
304 }
305 )_";
306
307 struct PlaneResources
308 {
309 QRhiTexture* texture{};
310 QRhiTextureRenderTarget* rt{};
311 QRhiRenderPassDescriptor* rp{};
312 QRhiShaderResourceBindings* srb{};
313 QRhiGraphicsPipeline* pipeline{};
314 QRhiReadbackResult readback{};
315 int w{}, h{};
316
317 void destroy()
318 {
319 delete pipeline; delete srb; delete rp; delete rt; delete texture;
320 pipeline = nullptr; srb = nullptr; rp = nullptr; rt = nullptr; texture = nullptr;
321 }
322 };
323
324 PlaneResources m_y, m_uv;
325 QRhiSampler* m_sampler{};
326 int m_width{};
327 int m_height{};
328 bool m_readbackEnabled{true};
329
330 void initPlane(
331 QRhi& rhi, const RenderState& state, QRhiTexture* inputRGBA,
332 PlaneResources& p, int packW, int packH, const QString& frag)
333 {
334 p.w = packW;
335 p.h = packH;
336 p.texture = rhi.newTexture(
337 QRhiTexture::RGBA8, QSize{packW, packH}, 1,
338 QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource);
339 p.texture->create();
340 p.rt = rhi.newTextureRenderTarget({p.texture});
341 p.rp = p.rt->newCompatibleRenderPassDescriptor();
342 p.rt->setRenderPassDescriptor(p.rp);
343 p.rt->create();
344 p.srb = rhi.newShaderResourceBindings();
345 p.srb->setBindings({QRhiShaderResourceBinding::sampledTexture(
346 3, QRhiShaderResourceBinding::FragmentStage, inputRGBA, m_sampler)});
347 p.srb->create();
348 auto [vs, fs] = makeShaders(state, QString::fromLatin1(vertex_shader), frag);
349 p.pipeline = rhi.newGraphicsPipeline();
350 p.pipeline->setShaderStages({
351 {QRhiShaderStage::Vertex, vs}, {QRhiShaderStage::Fragment, fs}});
352 p.pipeline->setVertexInputLayout({});
353 p.pipeline->setShaderResourceBindings(p.srb);
354 p.pipeline->setRenderPassDescriptor(p.rp);
355 p.pipeline->create();
356 }
357
358 void execPlane(QRhi& rhi, QRhiCommandBuffer& cb, PlaneResources& p)
359 {
360 cb.beginPass(p.rt, Qt::black, {0.0f, 0});
361 cb.setGraphicsPipeline(p.pipeline);
362 cb.setShaderResources(p.srb);
363 cb.setViewport(QRhiViewport(0, 0, p.w, p.h));
364 cb.draw(3);
365 if(m_readbackEnabled)
366 {
367 auto* batch = rhi.nextResourceUpdateBatch();
368 batch->readBackTexture(QRhiReadbackDescription{p.texture}, &p.readback);
369 cb.endPass(batch);
370 }
371 else
372 {
373 cb.endPass();
374 }
375 }
376
377 void init(
378 QRhi& rhi, const RenderState& state, QRhiTexture* inputRGBA, int width,
379 int height, const QString& colorConversion) override
380 {
381 m_width = width;
382 m_height = height;
383 m_sampler = rhi.newSampler(
384 QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
385 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
386 m_sampler->create();
387
388 // Both packed planes are 2*width bytes per row over height rows: the Y
389 // texel carries two luma samples, the UV texel one (Cb, Cr) pair.
390 const QString y = QString::fromLatin1(y_frag)
391 .arg(colorConversion)
392 .arg(QString::fromLatin1(common));
393 const QString uv = QString::fromLatin1(uv_frag)
394 .arg(colorConversion)
395 .arg(QString::fromLatin1(common));
396 initPlane(rhi, state, inputRGBA, m_y, width / 2, height, y);
397 initPlane(rhi, state, inputRGBA, m_uv, width / 2, height, uv);
398 }
399
400 void exec(QRhi& rhi, QRhiCommandBuffer& cb) override
401 {
402 execPlane(rhi, cb, m_y);
403 execPlane(rhi, cb, m_uv);
404 }
405
406 int planeCount() const override { return 2; }
407
408 const QRhiReadbackResult& readback(int plane) const override
409 {
410 return plane == 0 ? m_y.readback : m_uv.readback;
411 }
412
413 QRhiTexture* planeTexture(int plane) const noexcept
414 {
415 return plane == 0 ? m_y.texture : m_uv.texture;
416 }
417
418 void setReadbackEnabled(bool e) noexcept override { m_readbackEnabled = e; }
419
420 void release() override
421 {
422 m_uv.destroy();
423 m_y.destroy();
424 delete m_sampler;
425 m_sampler = nullptr;
426 }
427};
428#endif
429
430} // 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:1303
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
GPU RGBA -> p216 encoder (semi-planar 4:2:2, 16-bit).
Definition P216.hpp:40
const QRhiReadbackResult & readback(int plane) const override
Get the readback result for a given plane. Valid after endOffscreenFrame.
Definition P216.hpp:199
void exec(QRhi &rhi, QRhiCommandBuffer &cb) override
Definition P216.hpp:162
void setReadbackEnabled(bool e) noexcept override
Definition P216.hpp:211
QRhiTexture * planeTexture(int plane) const noexcept
Definition P216.hpp:206
void init(QRhi &rhi, const RenderState &state, QRhiTexture *inputRGBA, int width, int height, const QString &colorConversion) override
Definition P216.hpp:142
void release() override
Release all GPU resources.
Definition P216.hpp:213
int planeCount() const override
Number of readback planes (1 for UYVY, 2 for NV12, 3 for I420).
Definition P216.hpp:197
Global state associated to a rendering context.
Definition RenderState.hpp:37