Loading...
Searching...
No Matches
encoders/V210.hpp
1#pragma once
2#include <Gfx/Graph/encoders/GPUVideoEncoder.hpp>
3
4namespace score::gfx
5{
6
30{
31 // %1 = colorMatrixOut() shader defining convert_from_rgb(vec3)
32 static constexpr const char* frag = R"_(#version 450
33 layout(location = 0) in vec2 v_texcoord;
34 layout(location = 0) out vec4 fragColor;
35 layout(binding = 3) uniform sampler2D src_tex;
36 )_" "%1" R"_(
37
38 vec2 flip_y_uv(vec2 tc) {
39 // Only OpenGL. The rest of the engine puts its geometry through
40 // renderer.clipSpaceCorrMatrix, which negates Y on Vulkan; this pass draws
41 // a hardcoded triangle in raw NDC and does not, so the correction it needs
42 // is not the same one. Flipping on Vulkan as well handed libav, GStreamer,
43 // NDI and every other consumer an upside-down picture.
44 #if defined(QSHADER_SPIRV) || defined(QSHADER_MSL) || defined(QSHADER_HLSL)
45 return tc;
46 #else
47 return vec2(tc.x, 1.0 - tc.y);
48 #endif
49 }
50
51 // 10-bit unsigned (0..1023) for one source pixel.
52 uvec3 to_yuv10(vec3 rgb) {
53 vec3 yuv = clamp(convert_from_rgb(rgb), 0.0, 1.0);
54 return uvec3(yuv * 1023.0 + 0.5);
55 }
56
57 // Pack a 32-bit value as 4 little-endian bytes into the RGBA8 output.
58 vec4 pack32(uint w) {
59 return vec4(
60 float( w & 0xFFu),
61 float((w >> 8u) & 0xFFu),
62 float((w >> 16u) & 0xFFu),
63 float((w >> 24u) & 0xFFu)) / 255.0;
64 }
65
66 void main() {
67 ivec2 srcSize = textureSize(src_tex, 0);
68
69 // Output word index (X): gl_FragCoord.x is exact and never flipped.
70 int outX = int(gl_FragCoord.x);
71
72 // Source row (Y): derive from the interpolated texcoord, exactly like
73 // UYVYEncoder. gl_FragCoord.y's origin differs after HLSL/MSL cross-
74 // compilation (it flipped the v210 output vertically on D3D11), whereas
75 // flip_y_uv(v_texcoord) is correct on GL, Vulkan and D3D alike.
76 int srcY = clamp(
77 int(flip_y_uv(v_texcoord).y * float(srcSize.y)), 0, srcSize.y - 1);
78
79 // Each output row is N v210 words (RGBA8 texels), N == srcWidth/6 * 4.
80 int wordInGroup = outX & 3; // 0..3
81 int groupIdx = outX >> 2; // 6-pixel group index
82 int srcX0 = groupIdx * 6; // first source pixel in group
83
84 // texelFetch is clamped via sampler/edge handling; for safety on the
85 // last partial group (shouldn't happen since width % 6 == 0) we clamp.
86 int x0 = min(srcX0 , srcSize.x - 1);
87 int x1 = min(srcX0 + 1, srcSize.x - 1);
88 int x2 = min(srcX0 + 2, srcSize.x - 1);
89 int x3 = min(srcX0 + 3, srcSize.x - 1);
90 int x4 = min(srcX0 + 4, srcSize.x - 1);
91 int x5 = min(srcX0 + 5, srcSize.x - 1);
92
93 uint w = 0u;
94 if (wordInGroup == 0) {
95 uvec3 a = to_yuv10(texelFetch(src_tex, ivec2(x0, srcY), 0).rgb);
96 uvec3 b = to_yuv10(texelFetch(src_tex, ivec2(x1, srcY), 0).rgb);
97 uint cb01 = (a.y + b.y) >> 1;
98 uint cr01 = (a.z + b.z) >> 1;
99 w = cb01 | (a.x << 10) | (cr01 << 20);
100 } else if (wordInGroup == 1) {
101 uvec3 b = to_yuv10(texelFetch(src_tex, ivec2(x1, srcY), 0).rgb);
102 uvec3 c = to_yuv10(texelFetch(src_tex, ivec2(x2, srcY), 0).rgb);
103 uvec3 d = to_yuv10(texelFetch(src_tex, ivec2(x3, srcY), 0).rgb);
104 uint cb23 = (c.y + d.y) >> 1;
105 w = b.x | (cb23 << 10) | (c.x << 20);
106 } else if (wordInGroup == 2) {
107 uvec3 c = to_yuv10(texelFetch(src_tex, ivec2(x2, srcY), 0).rgb);
108 uvec3 d = to_yuv10(texelFetch(src_tex, ivec2(x3, srcY), 0).rgb);
109 uvec3 e = to_yuv10(texelFetch(src_tex, ivec2(x4, srcY), 0).rgb);
110 uvec3 f = to_yuv10(texelFetch(src_tex, ivec2(x5, srcY), 0).rgb);
111 uint cr23 = (c.z + d.z) >> 1;
112 uint cb45 = (e.y + f.y) >> 1;
113 w = cr23 | (d.x << 10) | (cb45 << 20);
114 } else {
115 uvec3 e = to_yuv10(texelFetch(src_tex, ivec2(x4, srcY), 0).rgb);
116 uvec3 f = to_yuv10(texelFetch(src_tex, ivec2(x5, srcY), 0).rgb);
117 uint cr45 = (e.z + f.z) >> 1;
118 w = e.x | (cr45 << 10) | (f.x << 20);
119 }
120 fragColor = pack32(w);
121 }
122 )_";
123
124 QRhiTexture* m_outTexture{};
125 QRhiTextureRenderTarget* m_renderTarget{};
126 QRhiRenderPassDescriptor* m_rpDesc{};
127 QRhiSampler* m_sampler{};
128 QRhiShaderResourceBindings* m_srb{};
129 QRhiGraphicsPipeline* m_pipeline{};
130 QRhiReadbackResult m_readback{};
131 int m_width{};
132 int m_height{};
133 int m_outW{};
134 bool m_readbackEnabled{true};
135
136 void init(
137 QRhi& rhi, const RenderState& state, QRhiTexture* inputRGBA, int width,
138 int height, const QString& colorConversion) override
139 {
140 m_width = width;
141 m_height = height;
142 // Output: one RGBA8 texel per v210 ULWord, on the PADDED wire row
143 // (((width+47)/48)*128 bytes — SMPTE/DeckLink/AJA row stride). (width/6)*4
144 // would truncate the tail group at widths not divisible by 6 (1280,
145 // 2048-DCI) and make the readback row differ from the framestore row,
146 // forcing a re-stride copy; padded, they are byte-identical.
147 m_outW = ((width + 47) / 48) * 32;
148
149 m_outTexture = rhi.newTexture(
150 QRhiTexture::RGBA8, QSize{m_outW, height}, 1,
151 QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource);
152 m_outTexture->create();
153
154 m_renderTarget = rhi.newTextureRenderTarget({m_outTexture});
155 m_rpDesc = m_renderTarget->newCompatibleRenderPassDescriptor();
156 m_renderTarget->setRenderPassDescriptor(m_rpDesc);
157 m_renderTarget->create();
158
159 // Nearest sampling - we use texelFetch but a sampler is still required
160 // by the binding model.
161 m_sampler = rhi.newSampler(
162 QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
163 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
164 m_sampler->create();
165
166 m_srb = rhi.newShaderResourceBindings();
167 m_srb->setBindings({
168 QRhiShaderResourceBinding::sampledTexture(
169 3, QRhiShaderResourceBinding::FragmentStage, inputRGBA, m_sampler),
170 });
171 m_srb->create();
172
173 auto [vertS, fragS] = makeShaders(
174 state, QString::fromLatin1(vertex_shader),
175 QString::fromLatin1(frag).arg(colorConversion));
176
177 m_pipeline = rhi.newGraphicsPipeline();
178 m_pipeline->setShaderStages({
179 {QRhiShaderStage::Vertex, vertS},
180 {QRhiShaderStage::Fragment, fragS},
181 });
182 m_pipeline->setVertexInputLayout({});
183 m_pipeline->setShaderResourceBindings(m_srb);
184 m_pipeline->setRenderPassDescriptor(m_rpDesc);
185 m_pipeline->create();
186 }
187
188 void exec(QRhi& rhi, QRhiCommandBuffer& cb) override
189 {
190 cb.beginPass(m_renderTarget, Qt::black, {0.0f, 0});
191 cb.setGraphicsPipeline(m_pipeline);
192 cb.setShaderResources(m_srb);
193 cb.setViewport(QRhiViewport(0, 0, m_outW, m_height));
194 cb.draw(3);
195
196 if(m_readbackEnabled)
197 {
198 auto* readbackBatch = rhi.nextResourceUpdateBatch();
199 readbackBatch->readBackTexture(QRhiReadbackDescription{m_outTexture}, &m_readback);
200 cb.endPass(readbackBatch);
201 }
202 else
203 {
204 cb.endPass();
208 int planeCount() const override { return 1; }
209 const QRhiReadbackResult& readback(int) const override { return m_readback; }
210 QRhiTexture* outputTexture() const noexcept override { return m_outTexture; }
211 void setReadbackEnabled(bool e) noexcept override { m_readbackEnabled = e; }
212
213 void release() override
214 {
215 delete m_pipeline; m_pipeline = nullptr;
216 delete m_srb; m_srb = nullptr;
217 delete m_sampler; m_sampler = nullptr;
218 delete m_rpDesc; m_rpDesc = nullptr;
219 delete m_renderTarget; m_renderTarget = nullptr;
220 delete m_outTexture; m_outTexture = nullptr;
221 }
222};
223
224} // 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
Cross-backend RGBA -> v210 encoder.
Definition encoders/V210.hpp:30
void init(QRhi &rhi, const RenderState &state, QRhiTexture *inputRGBA, int width, int height, const QString &colorConversion) override
Definition encoders/V210.hpp:133
int planeCount() const override
Number of readback planes (1 for UYVY, 2 for NV12, 3 for I420).
Definition encoders/V210.hpp:205
void release() override
Release all GPU resources.
Definition encoders/V210.hpp:210
void exec(QRhi &rhi, QRhiCommandBuffer &cb) override
Definition encoders/V210.hpp:185
void setReadbackEnabled(bool e) noexcept override
Definition encoders/V210.hpp:208
const QRhiReadbackResult & readback(int) const override
Get the readback result for a given plane. Valid after endOffscreenFrame.
Definition encoders/V210.hpp:206
QRhiTexture * outputTexture() const noexcept override
Definition encoders/V210.hpp:207