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