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 #if defined(QSHADER_MSL) || defined(QSHADER_HLSL)
40 return tc;
41 #else
42 return vec2(tc.x, 1.0 - tc.y);
43 #endif
44 }
45
46 int flip_y_int(int y, int h) {
47 #if defined(QSHADER_MSL) || defined(QSHADER_HLSL)
48 return y;
49 #else
50 return h - 1 - y;
51 #endif
52 }
53
54 // 10-bit unsigned (0..1023) for one source pixel.
55 uvec3 to_yuv10(vec3 rgb) {
56 vec3 yuv = clamp(convert_from_rgb(rgb), 0.0, 1.0);
57 return uvec3(yuv * 1023.0 + 0.5);
58 }
59
60 // Pack a 32-bit value as 4 little-endian bytes into the RGBA8 output.
61 vec4 pack32(uint w) {
62 return vec4(
63 float( w & 0xFFu),
64 float((w >> 8u) & 0xFFu),
65 float((w >> 16u) & 0xFFu),
66 float((w >> 24u) & 0xFFu)) / 255.0;
67 }
68
69 void main() {
70 ivec2 srcSize = textureSize(src_tex, 0);
71 ivec2 outPos = ivec2(gl_FragCoord.xy);
72
73 // Each output row is N v210 words (RGBA8 texels), N == srcWidth/6 * 4.
74 int wordInGroup = outPos.x & 3; // 0..3
75 int groupIdx = outPos.x >> 2; // 6-pixel group index
76 int srcX0 = groupIdx * 6; // first source pixel in group
77 int srcY = flip_y_int(outPos.y, srcSize.y);
78
79 // texelFetch is clamped via sampler/edge handling; for safety on the
80 // last partial group (shouldn't happen since width % 6 == 0) we clamp.
81 int x0 = min(srcX0 , srcSize.x - 1);
82 int x1 = min(srcX0 + 1, srcSize.x - 1);
83 int x2 = min(srcX0 + 2, srcSize.x - 1);
84 int x3 = min(srcX0 + 3, srcSize.x - 1);
85 int x4 = min(srcX0 + 4, srcSize.x - 1);
86 int x5 = min(srcX0 + 5, srcSize.x - 1);
87
88 uint w = 0u;
89 if (wordInGroup == 0) {
90 uvec3 a = to_yuv10(texelFetch(src_tex, ivec2(x0, srcY), 0).rgb);
91 uvec3 b = to_yuv10(texelFetch(src_tex, ivec2(x1, srcY), 0).rgb);
92 uint cb01 = (a.y + b.y) >> 1;
93 uint cr01 = (a.z + b.z) >> 1;
94 w = cb01 | (a.x << 10) | (cr01 << 20);
95 } else if (wordInGroup == 1) {
96 uvec3 b = to_yuv10(texelFetch(src_tex, ivec2(x1, srcY), 0).rgb);
97 uvec3 c = to_yuv10(texelFetch(src_tex, ivec2(x2, srcY), 0).rgb);
98 uvec3 d = to_yuv10(texelFetch(src_tex, ivec2(x3, srcY), 0).rgb);
99 uint cb23 = (c.y + d.y) >> 1;
100 w = b.x | (cb23 << 10) | (c.x << 20);
101 } else if (wordInGroup == 2) {
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 uvec3 e = to_yuv10(texelFetch(src_tex, ivec2(x4, srcY), 0).rgb);
105 uvec3 f = to_yuv10(texelFetch(src_tex, ivec2(x5, srcY), 0).rgb);
106 uint cr23 = (c.z + d.z) >> 1;
107 uint cb45 = (e.y + f.y) >> 1;
108 w = cr23 | (d.x << 10) | (cb45 << 20);
109 } else {
110 uvec3 e = to_yuv10(texelFetch(src_tex, ivec2(x4, srcY), 0).rgb);
111 uvec3 f = to_yuv10(texelFetch(src_tex, ivec2(x5, srcY), 0).rgb);
112 uint cr45 = (e.z + f.z) >> 1;
113 w = e.x | (cr45 << 10) | (f.x << 20);
114 }
115 fragColor = pack32(w);
116 }
117 )_";
118
119 QRhiTexture* m_outTexture{};
120 QRhiTextureRenderTarget* m_renderTarget{};
121 QRhiRenderPassDescriptor* m_rpDesc{};
122 QRhiSampler* m_sampler{};
123 QRhiShaderResourceBindings* m_srb{};
124 QRhiGraphicsPipeline* m_pipeline{};
125 QRhiReadbackResult m_readback{};
126 int m_width{};
127 int m_height{};
128 int m_outW{};
129
130 void init(
131 QRhi& rhi, const RenderState& state, QRhiTexture* inputRGBA, int width,
132 int height, const QString& colorConversion = colorMatrixOut()) override
133 {
134 m_width = width;
135 m_height = height;
136 // Output: one RGBA8 texel per v210 ULWord. 4 words per 6 source pixels.
137 m_outW = (width / 6) * 4;
138
139 m_outTexture = rhi.newTexture(
140 QRhiTexture::RGBA8, QSize{m_outW, height}, 1,
141 QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource);
142 m_outTexture->create();
143
144 m_renderTarget = rhi.newTextureRenderTarget({m_outTexture});
145 m_rpDesc = m_renderTarget->newCompatibleRenderPassDescriptor();
146 m_renderTarget->setRenderPassDescriptor(m_rpDesc);
147 m_renderTarget->create();
148
149 // Nearest sampling - we use texelFetch but a sampler is still required
150 // by the binding model.
151 m_sampler = rhi.newSampler(
152 QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
153 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
154 m_sampler->create();
155
156 m_srb = rhi.newShaderResourceBindings();
157 m_srb->setBindings({
158 QRhiShaderResourceBinding::sampledTexture(
159 3, QRhiShaderResourceBinding::FragmentStage, inputRGBA, m_sampler),
160 });
161 m_srb->create();
162
163 auto [vertS, fragS] = makeShaders(
164 state, QString::fromLatin1(vertex_shader),
165 QString::fromLatin1(frag).arg(colorConversion));
166
167 m_pipeline = rhi.newGraphicsPipeline();
168 m_pipeline->setShaderStages({
169 {QRhiShaderStage::Vertex, vertS},
170 {QRhiShaderStage::Fragment, fragS},
171 });
172 m_pipeline->setVertexInputLayout({});
173 m_pipeline->setShaderResourceBindings(m_srb);
174 m_pipeline->setRenderPassDescriptor(m_rpDesc);
175 m_pipeline->create();
176 }
177
178 void exec(QRhi& rhi, QRhiCommandBuffer& cb) override
179 {
180 cb.beginPass(m_renderTarget, Qt::black, {1.0f, 0});
181 cb.setGraphicsPipeline(m_pipeline);
182 cb.setShaderResources(m_srb);
183 cb.setViewport(QRhiViewport(0, 0, m_outW, m_height));
184 cb.draw(3);
186 auto* readbackBatch = rhi.nextResourceUpdateBatch();
187 readbackBatch->readBackTexture(QRhiReadbackDescription{m_outTexture}, &m_readback);
188 cb.endPass(readbackBatch);
189 }
190
191 int planeCount() const override { return 1; }
192 const QRhiReadbackResult& readback(int) const override { return m_readback; }
193
194 void release() override
195 {
196 delete m_pipeline; m_pipeline = nullptr;
197 delete m_srb; m_srb = nullptr;
198 delete m_sampler; m_sampler = nullptr;
199 delete m_rpDesc; m_rpDesc = nullptr;
200 delete m_renderTarget; m_renderTarget = nullptr;
201 delete m_outTexture; m_outTexture = nullptr;
202 }
203};
204
205} // namespace score::gfx
Graphics rendering pipeline for ossia score.
Definition Filter/PreviewWidget.hpp:12
std::pair< QShader, QShader > makeShaders(const RenderState &v, QString vert, QString frag)
Get a pair of compiled vertex / fragment shaders from GLSL 4.5 sources.
Definition score-plugin-gfx/Gfx/Graph/Utils.cpp:647
Base class for GPU-side video format conversion (RGBA to YUV).
Definition GPUVideoEncoder.hpp:30
static constexpr const char * vertex_shader
Definition GPUVideoEncoder.hpp:63
Cross-backend RGBA -> v210 encoder.
Definition encoders/V210.hpp:30
int planeCount() const override
Number of readback planes (1 for UYVY, 2 for NV12, 3 for I420).
Definition encoders/V210.hpp:185
void release() override
Release all GPU resources.
Definition encoders/V210.hpp:188
void exec(QRhi &rhi, QRhiCommandBuffer &cb) override
Definition encoders/V210.hpp:172
void init(QRhi &rhi, const RenderState &state, QRhiTexture *inputRGBA, int width, int height, const QString &colorConversion=colorMatrixOut()) override
Definition encoders/V210.hpp:124
const QRhiReadbackResult & readback(int) const override
Get the readback result for a given plane. Valid after endOffscreenFrame.
Definition encoders/V210.hpp:186