2#include <Gfx/Graph/encoders/GPUVideoEncoder.hpp>
28#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)
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;
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;
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)
54 return vec2(tc.x, 1.0 - tc.y);
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);
65 static constexpr const char* uv_frag = R
"_(#version 450
66 layout(location = 0) in vec2 v_texcoord;
67 layout(location = 0) out vec4 fragColor;
68 layout(binding = 3) uniform sampler2D src_tex;
71 vec2 flip_y(vec2 tc) {
72 // Only OpenGL. The rest of the engine puts its geometry through
73 // renderer.clipSpaceCorrMatrix, which negates Y on Vulkan; this pass draws
74 // a hardcoded triangle in raw NDC and does not, so the correction it needs
75 // is not the same one. Flipping on Vulkan as well handed libav, GStreamer,
76 // NDI and every other consumer an upside-down picture.
77 #if defined(QSHADER_SPIRV) || defined(QSHADER_MSL) || defined(QSHADER_HLSL)
80 return vec2(tc.x, 1.0 - tc.y);
84 vec3 rgb = texture(src_tex, flip_y(v_texcoord)).rgb;
85 vec3 yuv = convert_from_rgb(rgb);
86 fragColor = vec4(pack10hi(yuv.y), pack10hi(yuv.z), 0.0, 1.0);
91 QRhiTexture* m_yTexture{};
92 QRhiTextureRenderTarget* m_yRT{};
93 QRhiRenderPassDescriptor* m_yRP{};
94 QRhiShaderResourceBindings* m_ySRB{};
95 QRhiGraphicsPipeline* m_yPipeline{};
96 QRhiReadbackResult m_yReadback{};
99 QRhiTexture* m_uvTexture{};
100 QRhiTextureRenderTarget* m_uvRT{};
101 QRhiRenderPassDescriptor* m_uvRP{};
102 QRhiShaderResourceBindings* m_uvSRB{};
103 QRhiGraphicsPipeline* m_uvPipeline{};
104 QRhiReadbackResult m_uvReadback{};
106 QRhiSampler* m_sampler{};
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)
117 tex = rhi.newTexture(
119 QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource);
122 rt = rhi.newTextureRenderTarget({tex});
123 rp = rt->newCompatibleRenderPassDescriptor();
124 rt->setRenderPassDescriptor(rp);
127 srb = rhi.newShaderResourceBindings();
129 QRhiShaderResourceBinding::sampledTexture(
130 3, QRhiShaderResourceBinding::FragmentStage, inputRGBA, m_sampler),
136 QString::fromLatin1(frag).arg(colorConversion).arg(pack_fn));
137 pipeline = rhi.newGraphicsPipeline();
138 pipeline->setShaderStages({
139 {QRhiShaderStage::Vertex, vs},
140 {QRhiShaderStage::Fragment, fs},
142 pipeline->setVertexInputLayout({});
143 pipeline->setShaderResourceBindings(srb);
144 pipeline->setRenderPassDescriptor(rp);
149 QRhi& rhi,
const RenderState& state, QRhiTexture* inputRGBA,
int width,
150 int height,
const QString& colorConversion = colorMatrixOut())
override
155 m_sampler = rhi.newSampler(
156 QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
157 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
161 rhi, state, inputRGBA, QRhiTexture::R16, width, height, y_frag,
162 colorConversion, m_yTexture, m_yRT, m_yRP, m_ySRB, m_yPipeline);
164 rhi, state, inputRGBA, QRhiTexture::RG16, width / 2, height / 2, uv_frag,
165 colorConversion, m_uvTexture, m_uvRT, m_uvRP, m_uvSRB, m_uvPipeline);
168 void exec(QRhi& rhi, QRhiCommandBuffer& cb)
override
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));
175 auto* yb = rhi.nextResourceUpdateBatch();
176 yb->readBackTexture(QRhiReadbackDescription{m_yTexture}, &m_yReadback);
179 cb.beginPass(m_uvRT, Qt::black, {0.0f, 0});
180 cb.setGraphicsPipeline(m_uvPipeline);
181 cb.setShaderResources(m_uvSRB);
182 cb.setViewport(QRhiViewport(0, 0, m_width / 2, m_height / 2));
184 auto* uvb = rhi.nextResourceUpdateBatch();
185 uvb->readBackTexture(QRhiReadbackDescription{m_uvTexture}, &m_uvReadback);
191 const QRhiReadbackResult&
readback(
int plane)
const override
193 return plane == 0 ? m_yReadback : m_uvReadback;
209 m_uvPipeline = m_yPipeline =
nullptr;
210 m_uvSRB = m_ySRB =
nullptr;
211 m_uvRP = m_yRP =
nullptr;
212 m_uvRT = m_yRT =
nullptr;
213 m_uvTexture = m_yTexture =
nullptr;
218struct P010Encoder : GPUVideoEncoder
220 static constexpr const char* common = R
"_(
221 int flip_y_int(int y, int h) {
222 // See GPUVideoEncoder::y_flip_glsl: only OpenGL. The rest of the engine
223 // negates Y through renderer.clipSpaceCorrMatrix on Vulkan; this pass
224 // indexes texels directly and does not, so it must not flip there.
225 #if defined(QSHADER_SPIRV) || defined(QSHADER_MSL) || defined(QSHADER_HLSL)
231 // 10-bit component (0=Y,1=U,2=V) shifted into the high 10 bits of 16 bits.
232 uint hi10(vec3 rgb, int c) {
233 vec3 yuv = clamp(convert_from_rgb(rgb), 0.0, 1.0);
234 float v = (c == 0) ? yuv.x : (c == 1 ? yuv.y : yuv.z);
235 return uint(v * 1023.0 + 0.5) << 6u;
237 vec4 pack2(uint a, uint b) {
238 return vec4(float(a & 0xFFu), float((a >> 8u) & 0xFFu),
239 float(b & 0xFFu), float((b >> 8u) & 0xFFu)) / 255.0;
244 static constexpr const char* y_frag = R
"_(#version 450
245 layout(location = 0) in vec2 v_texcoord;
246 layout(location = 0) out vec4 fragColor;
247 layout(binding = 3) uniform sampler2D src_tex;
251 ivec2 sz = textureSize(src_tex, 0);
252 ivec2 o = ivec2(gl_FragCoord.xy);
253 int sy = flip_y_int(o.y, sz.y);
255 uint a = hi10(texelFetch(src_tex, ivec2(min(x0, sz.x - 1), sy), 0).rgb, 0);
256 uint b = hi10(texelFetch(src_tex, ivec2(min(x0 + 1, sz.x - 1), sy), 0).rgb, 0);
257 fragColor = pack2(a, b);
263 static constexpr const char* uv_frag = R
"_(#version 450
264 layout(location = 0) in vec2 v_texcoord;
265 layout(location = 0) out vec4 fragColor;
266 layout(binding = 3) uniform sampler2D src_tex;
269 uint avg(int x, int y0, int y1, ivec2 sz, int c) {
270 int xa = min(x, sz.x - 1), xb = min(x + 1, sz.x - 1);
271 uint s = hi10(texelFetch(src_tex, ivec2(xa, y0), 0).rgb, c)
272 + hi10(texelFetch(src_tex, ivec2(xb, y0), 0).rgb, c)
273 + hi10(texelFetch(src_tex, ivec2(xa, y1), 0).rgb, c)
274 + hi10(texelFetch(src_tex, ivec2(xb, y1), 0).rgb, c);
275 return (s >> 2u) & 0xFFC0u; // keep 10 bits in the high position
278 ivec2 sz = textureSize(src_tex, 0);
279 ivec2 o = ivec2(gl_FragCoord.xy);
281 int y0 = flip_y_int(o.y * 2, sz.y);
282 int y1 = flip_y_int(o.y * 2 + 1, sz.y);
283 fragColor = pack2(avg(x0, y0, y1, sz, 1), avg(x0, y0, y1, sz, 2));
287 struct PlaneResources
289 QRhiTexture* texture{};
290 QRhiTextureRenderTarget* rt{};
291 QRhiRenderPassDescriptor* rp{};
292 QRhiShaderResourceBindings* srb{};
293 QRhiGraphicsPipeline* pipeline{};
294 QRhiReadbackResult readback{};
299 delete pipeline;
delete srb;
delete rp;
delete rt;
delete texture;
300 pipeline =
nullptr; srb =
nullptr; rp =
nullptr; rt =
nullptr; texture =
nullptr;
304 PlaneResources m_y, m_uv;
305 QRhiSampler* m_sampler{};
310 QRhi& rhi,
const RenderState& state, QRhiTexture* inputRGBA,
311 PlaneResources& p,
int packW,
int packH,
const QString& frag)
315 p.texture = rhi.newTexture(
316 QRhiTexture::RGBA8, QSize{packW, packH}, 1,
317 QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource);
319 p.rt = rhi.newTextureRenderTarget({p.texture});
320 p.rp = p.rt->newCompatibleRenderPassDescriptor();
321 p.rt->setRenderPassDescriptor(p.rp);
323 p.srb = rhi.newShaderResourceBindings();
324 p.srb->setBindings({QRhiShaderResourceBinding::sampledTexture(
325 3, QRhiShaderResourceBinding::FragmentStage, inputRGBA, m_sampler)});
328 p.pipeline = rhi.newGraphicsPipeline();
329 p.pipeline->setShaderStages({
330 {QRhiShaderStage::Vertex, vs}, {QRhiShaderStage::Fragment, fs}});
331 p.pipeline->setVertexInputLayout({});
332 p.pipeline->setShaderResourceBindings(p.srb);
333 p.pipeline->setRenderPassDescriptor(p.rp);
334 p.pipeline->create();
337 void execPlane(QRhi& rhi, QRhiCommandBuffer& cb, PlaneResources& p)
339 cb.beginPass(p.rt, Qt::black, {0.0f, 0});
340 cb.setGraphicsPipeline(p.pipeline);
341 cb.setShaderResources(p.srb);
342 cb.setViewport(QRhiViewport(0, 0, p.w, p.h));
344 auto* batch = rhi.nextResourceUpdateBatch();
345 batch->readBackTexture(QRhiReadbackDescription{p.texture}, &p.readback);
350 QRhi& rhi,
const RenderState& state, QRhiTexture* inputRGBA,
int width,
351 int height,
const QString& colorConversion = colorMatrixOut())
override
355 m_sampler = rhi.newSampler(
356 QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
357 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
360 const QString cm = QString::fromLatin1(common);
361 const QString y = QString::fromLatin1(y_frag).arg(colorConversion, cm);
362 const QString uv = QString::fromLatin1(uv_frag).arg(colorConversion, cm);
363 initPlane(rhi, state, inputRGBA, m_y, width / 2, height, y);
364 initPlane(rhi, state, inputRGBA, m_uv, width / 2, height / 2, uv);
367 void exec(QRhi& rhi, QRhiCommandBuffer& cb)
override
369 execPlane(rhi, cb, m_y);
370 execPlane(rhi, cb, m_uv);
375 const QRhiReadbackResult&
readback(
int plane)
const override
377 return plane == 0 ? m_y.readback : m_uv.readback;
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
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:190
int planeCount() const override
Number of readback planes (1 for UYVY, 2 for NV12, 3 for I420).
Definition encoders/P010.hpp:183
void init(QRhi &rhi, const RenderState &state, QRhiTexture *inputRGBA, int width, int height, const QString &colorConversion=colorMatrixOut()) override
Definition encoders/P010.hpp:142
const QRhiReadbackResult & readback(int plane) const override
Get the readback result for a given plane. Valid after endOffscreenFrame.
Definition encoders/P010.hpp:185
void exec(QRhi &rhi, QRhiCommandBuffer &cb) override
Definition encoders/P010.hpp:162
Global state associated to a rendering context.
Definition RenderState.hpp:37