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