Loading...
Searching...
No Matches
PackedRGB.hpp
1#pragma once
2#include <Gfx/Graph/encoders/GPUVideoEncoder.hpp>
3
4namespace score::gfx
5{
6
34{
35 static constexpr const char* frag_template = R"_(#version 450
36 layout(location = 0) in vec2 v_texcoord;
37 layout(location = 0) out vec4 fragColor;
38 layout(binding = 3) uniform sampler2D src_tex;
39
40 vec2 flip_y_uv(vec2 tc) {
41 // Only OpenGL. The rest of the engine puts its geometry through
42 // renderer.clipSpaceCorrMatrix, which negates Y on Vulkan; this pass draws
43 // a hardcoded triangle in raw NDC and does not, so the correction it needs
44 // is not the same one. Flipping on Vulkan as well handed libav, GStreamer,
45 // NDI and every other consumer an upside-down picture.
46 #if defined(QSHADER_SPIRV) || defined(QSHADER_MSL) || defined(QSHADER_HLSL)
47 return tc;
48 #else
49 return vec2(tc.x, 1.0 - tc.y);
50 #endif
51 }
52
53 const uint MAXV = %1u;
54 const uint ALPHA = %2u;
55 ivec2 g_srcSize;
56
57 // Source pixel components quantised to [0..MAXV], .a == ALPHA.
58 uvec4 rgb_at(int x, int srcY) {
59 x = clamp(x, 0, g_srcSize.x - 1);
60 vec3 c = clamp(texelFetch(src_tex, ivec2(x, srcY), 0).rgb, 0.0, 1.0);
61 uvec3 v = uvec3(c * float(MAXV) + 0.5);
62 return uvec4(v, ALPHA);
63 }
64
65 uint rowByte(int b, int srcY) {
66%3
67 }
68
69 void main() {
70 g_srcSize = textureSize(src_tex, 0);
71 int srcY = clamp(
72 int(flip_y_uv(v_texcoord).y * float(g_srcSize.y)), 0, g_srcSize.y - 1);
73 int o = int(gl_FragCoord.x) * 4;
74 fragColor = vec4(
75 float(rowByte(o, srcY)),
76 float(rowByte(o + 1, srcY)),
77 float(rowByte(o + 2, srcY)),
78 float(rowByte(o + 3, srcY))) / 255.0;
79 }
80 )_";
81
82 int m_bytesPerGroup{4};
83 int m_pixelsPerGroup{1};
84 int m_maxVal{1023};
85 int m_alphaVal{0};
86 QString m_rowByteBody;
87
89 int bytesPerGroup, int pixelsPerGroup, int maxVal, int alphaVal,
90 QString rowByteBody)
91 : m_bytesPerGroup{bytesPerGroup}
92 , m_pixelsPerGroup{pixelsPerGroup}
93 , m_maxVal{maxVal}
94 , m_alphaVal{alphaVal}
95 , m_rowByteBody{std::move(rowByteBody)}
96 {
97 }
98
99 QRhiTexture* m_outTexture{};
100 QRhiTextureRenderTarget* m_renderTarget{};
101 QRhiRenderPassDescriptor* m_rpDesc{};
102 QRhiSampler* m_sampler{};
103 QRhiShaderResourceBindings* m_srb{};
104 QRhiGraphicsPipeline* m_pipeline{};
105 QRhiReadbackResult m_readback{};
106 int m_width{};
107 int m_height{};
108 int m_outW{};
109 bool m_readbackEnabled{true};
110
111 void init(
112 QRhi& rhi, const RenderState& state, QRhiTexture* inputRGBA, int width,
113 int height, const QString& /*colorConversion*/ = colorMatrixOut()) override
114 {
115 m_width = width;
116 m_height = height;
117 m_outW = (width * m_bytesPerGroup) / (m_pixelsPerGroup * 4);
118
119 m_outTexture = rhi.newTexture(
120 QRhiTexture::RGBA8, QSize{m_outW, height}, 1,
121 QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource);
122 m_outTexture->create();
123
124 m_renderTarget = rhi.newTextureRenderTarget({m_outTexture});
125 m_rpDesc = m_renderTarget->newCompatibleRenderPassDescriptor();
126 m_renderTarget->setRenderPassDescriptor(m_rpDesc);
127 m_renderTarget->create();
128
129 m_sampler = rhi.newSampler(
130 QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
131 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
132 m_sampler->create();
133
134 m_srb = rhi.newShaderResourceBindings();
135 m_srb->setBindings({
136 QRhiShaderResourceBinding::sampledTexture(
137 3, QRhiShaderResourceBinding::FragmentStage, inputRGBA, m_sampler),
138 });
139 m_srb->create();
140
141 const QString fragSrc = QString::fromLatin1(frag_template)
142 .arg(m_maxVal)
143 .arg(m_alphaVal)
144 .arg(m_rowByteBody);
145 auto [vertS, fragS] = makeShaders(
146 state, QString::fromLatin1(vertex_shader), fragSrc);
147
148 m_pipeline = rhi.newGraphicsPipeline();
149 m_pipeline->setShaderStages({
150 {QRhiShaderStage::Vertex, vertS},
151 {QRhiShaderStage::Fragment, fragS},
152 });
153 m_pipeline->setVertexInputLayout({});
154 m_pipeline->setShaderResourceBindings(m_srb);
155 m_pipeline->setRenderPassDescriptor(m_rpDesc);
156 m_pipeline->create();
157 }
158
159 void exec(QRhi& rhi, QRhiCommandBuffer& cb) override
160 {
161 cb.beginPass(m_renderTarget, Qt::black, {0.0f, 0});
162 cb.setGraphicsPipeline(m_pipeline);
163 cb.setShaderResources(m_srb);
164 cb.setViewport(QRhiViewport(0, 0, m_outW, m_height));
165 cb.draw(3);
166
167 if(m_readbackEnabled)
168 {
169 auto* readbackBatch = rhi.nextResourceUpdateBatch();
170 readbackBatch->readBackTexture(QRhiReadbackDescription{m_outTexture}, &m_readback);
171 cb.endPass(readbackBatch);
172 }
173 else
174 {
175 cb.endPass();
179 int planeCount() const override { return 1; }
180 const QRhiReadbackResult& readback(int) const override { return m_readback; }
181 QRhiTexture* outputTexture() const noexcept override { return m_outTexture; }
182 void setReadbackEnabled(bool e) noexcept override { m_readbackEnabled = e; }
183
184 void release() override
185 {
186 delete m_pipeline; m_pipeline = nullptr;
187 delete m_srb; m_srb = nullptr;
188 delete m_sampler; m_sampler = nullptr;
189 delete m_rpDesc; m_rpDesc = nullptr;
190 delete m_renderTarget; m_renderTarget = nullptr;
191 delete m_outTexture; m_outTexture = nullptr;
192 }
193
194 // ---- Per-format factories (bit layouts verified against the SDK's
195 // PackRGB10Bit* / Convert16BitARGBTo* / ConvertLine_8bitABGR_* ) ----
196
197 // NTV2_FBF_10BIT_RGB: word = (B<<20)|(G<<10)|R, 4 bytes LE, 1 px/word.
198 static std::unique_ptr<PackedRGBEncoder> rgb10()
199 {
200 return std::make_unique<PackedRGBEncoder>(4, 1, 1023, 0, R"_(
201 int px = b >> 2; int k = b & 3;
202 uvec4 c = rgb_at(px, srcY);
203 uint w = c.r | (c.g << 10) | (c.b << 20);
204 return (w >> uint(8 * k)) & 0xFFu;
205 )_");
206 }
207
208 // DeckLink r210 / AV_CODEC_ID_R210: word = (R<<20)|(G<<10)|B, stored
209 // big-endian, 1 px/word. R in the high bits, opposite channel order and
210 // opposite endianness to AJA's NTV2_FBF_10BIT_RGB above.
211 static std::unique_ptr<PackedRGBEncoder> r210be()
212 {
213 return std::make_unique<PackedRGBEncoder>(4, 1, 1023, 0, R"_(
214 int px = b >> 2; int k = b & 3;
215 uvec4 c = rgb_at(px, srcY);
216 uint w = (c.r << 20) | (c.g << 10) | c.b;
217 return (w >> uint(8 * (3 - k))) & 0xFFu;
218 )_");
219 }
220
221 // NTV2_FBF_10BIT_DPX (big-endian): value=(R<<22)|(G<<12)|(B<<2), stored BE.
222 static std::unique_ptr<PackedRGBEncoder> dpx10be()
223 {
224 return std::make_unique<PackedRGBEncoder>(4, 1, 1023, 0, R"_(
225 int px = b >> 2; int k = b & 3;
226 uvec4 c = rgb_at(px, srcY);
227 uint w = (c.r << 22) | (c.g << 12) | (c.b << 2);
228 return (w >> uint(8 * (3 - k))) & 0xFFu;
229 )_");
230 }
231
232 // NTV2_FBF_10BIT_DPX_LE: same value, little-endian.
233 static std::unique_ptr<PackedRGBEncoder> dpx10le()
234 {
235 return std::make_unique<PackedRGBEncoder>(4, 1, 1023, 0, R"_(
236 int px = b >> 2; int k = b & 3;
237 uvec4 c = rgb_at(px, srcY);
238 uint w = (c.r << 22) | (c.g << 12) | (c.b << 2);
239 return (w >> uint(8 * k)) & 0xFFu;
240 )_");
241 }
242
243 // NTV2_FBF_24BIT_RGB: bytes [R,G,B], 3 B/px.
244 static std::unique_ptr<PackedRGBEncoder> rgb24()
245 {
246 return std::make_unique<PackedRGBEncoder>(3, 1, 255, 0, R"_(
247 int px = b / 3; int k = b - px * 3;
248 uvec4 c = rgb_at(px, srcY);
249 if (k == 0) return c.r;
250 if (k == 1) return c.g;
251 return c.b;
252 )_");
253 }
254
255 // NTV2_FBF_24BIT_BGR: bytes [B,G,R], 3 B/px.
256 static std::unique_ptr<PackedRGBEncoder> bgr24()
257 {
258 return std::make_unique<PackedRGBEncoder>(3, 1, 255, 0, R"_(
259 int px = b / 3; int k = b - px * 3;
260 uvec4 c = rgb_at(px, srcY);
261 if (k == 0) return c.b;
262 if (k == 1) return c.g;
263 return c.r;
264 )_");
265 }
266
267 // NTV2_FBF_48BIT_RGB: R16,G16,B16 little-endian, full 16-bit range, 6 B/px.
268 // (The card reads this as full 16-bit, not 12-bit-in-low-bits: verified by
269 // round-trip, 12-bit values read back at ~6% brightness.)
270 static std::unique_ptr<PackedRGBEncoder> rgb48()
271 {
272 return std::make_unique<PackedRGBEncoder>(6, 1, 65535, 0, R"_(
273 int px = b / 6; int k = b - px * 6;
274 uvec4 c = rgb_at(px, srcY);
275 int comp = k >> 1; int hi = k & 1;
276 uint v = comp == 0 ? c.r : (comp == 1 ? c.g : c.b);
277 return hi == 1 ? ((v >> 8) & 0xFFu) : (v & 0xFFu);
278 )_");
279 }
280
281 // NTV2_FBF_12BIT_RGB_PACKED: 12-bit data, left-justified in 16 (v=val<<4),
282 // 2 px packed into 9 bytes. Layout per Convert16BitARGBTo12BitRGBPacked.
283 static std::unique_ptr<PackedRGBEncoder> rgb12packed()
284 {
285 return std::make_unique<PackedRGBEncoder>(9, 2, 4095, 0, R"_(
286 int grp = b / 9; int k = b - grp * 9; int px0 = grp * 2;
287 uvec4 c0 = rgb_at(px0, srcY);
288 uvec4 c1 = rgb_at(px0 + 1, srcY);
289 uint VR0 = c0.r << 4, VG0 = c0.g << 4, VB0 = c0.b << 4;
290 uint VR1 = c1.r << 4, VG1 = c1.g << 4, VB1 = c1.b << 4;
291 if (k == 0) return (VR0 >> 8) & 0xFFu;
292 if (k == 1) return (VR0 & 0xF0u) | ((VG0 >> 12) & 0x0Fu);
293 if (k == 2) return (VG0 >> 4) & 0xFFu;
294 if (k == 3) return (VB0 >> 8) & 0xFFu;
295 if (k == 4) return (VB0 & 0xF0u) | ((VR1 >> 12) & 0x0Fu);
296 if (k == 5) return (VR1 >> 4) & 0xFFu;
297 if (k == 6) return (VG1 >> 8) & 0xFFu;
298 if (k == 7) return (VG1 & 0xF0u) | ((VB1 >> 12) & 0x0Fu);
299 return (VB1 >> 4) & 0xFFu;
300 )_");
301 }
302
303 // NTV2_FBF_10BIT_ARGB: B,G,R,A 10-bit packed into 5 bytes/px.
304 static std::unique_ptr<PackedRGBEncoder> argb10()
305 {
306 return std::make_unique<PackedRGBEncoder>(5, 1, 1023, 1023, R"_(
307 int px = b / 5; int k = b - px * 5;
308 uvec4 c = rgb_at(px, srcY);
309 uint R = c.r, G = c.g, B = c.b, A = c.a;
310 if (k == 0) return B & 0xFFu;
311 if (k == 1) return ((B >> 8) & 0x03u) | ((G & 0x3Fu) << 2);
312 if (k == 2) return ((G >> 6) & 0x0Fu) | ((R & 0x0Fu) << 4);
313 if (k == 3) return ((R >> 4) & 0x3Fu) | ((A & 0x03u) << 6);
314 return (A >> 2) & 0xFFu;
315 )_");
316 }
317};
318
319} // 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
Generic packed-pixel RGB/byte encoder for AJA framebuffer formats.
Definition PackedRGB.hpp:34
void exec(QRhi &rhi, QRhiCommandBuffer &cb) override
Definition PackedRGB.hpp:156
void init(QRhi &rhi, const RenderState &state, QRhiTexture *inputRGBA, int width, int height, const QString &=colorMatrixOut()) override
Definition PackedRGB.hpp:108
QRhiTexture * outputTexture() const noexcept override
Definition PackedRGB.hpp:178
void setReadbackEnabled(bool e) noexcept override
Definition PackedRGB.hpp:179
int planeCount() const override
Number of readback planes (1 for UYVY, 2 for NV12, 3 for I420).
Definition PackedRGB.hpp:176
const QRhiReadbackResult & readback(int) const override
Get the readback result for a given plane. Valid after endOffscreenFrame.
Definition PackedRGB.hpp:177
void release() override
Release all GPU resources.
Definition PackedRGB.hpp:181