Loading...
Searching...
No Matches
encoders/NV12.hpp
1#pragma once
2#include <Gfx/Graph/encoders/GPUVideoEncoder.hpp>
3
4namespace score::gfx
5{
6
33{
34 // %1 = colorMatrixOut() shader defining convert_from_rgb(vec3)
35 static constexpr const char* y_frag = 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 )_" "%1" R"_(
40 vec2 flip_y(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 void main() {
53 vec3 rgb = texture(src_tex, flip_y(v_texcoord)).rgb;
54 vec3 yuv = convert_from_rgb(rgb);
55 fragColor = vec4(yuv.x, 0.0, 0.0, 1.0);
56 }
57 )_";
58
59 // Byte b of chroma row cy is U (b even) or V (b odd) of site b/2.
60 static constexpr const char* uv_frag = R"_(#version 450
61 layout(location = 0) in vec2 v_texcoord;
62 layout(location = 0) out vec4 fragColor;
63 layout(binding = 3) uniform sampler2D src_tex;
64 )_" "%1" R"_(
65 vec2 flip_y(vec2 tc) {
66 // Only OpenGL. The rest of the engine puts its geometry through
67 // renderer.clipSpaceCorrMatrix, which negates Y on Vulkan; this pass draws
68 // a hardcoded triangle in raw NDC and does not, so the correction it needs
69 // is not the same one. Flipping on Vulkan as well handed libav, GStreamer,
70 // NDI and every other consumer an upside-down picture.
71 #if defined(QSHADER_SPIRV) || defined(QSHADER_MSL) || defined(QSHADER_HLSL)
72 return tc;
73 #else
74 return vec2(tc.x, 1.0 - tc.y);
75 #endif
76 }
77 void main() {
78 ivec2 sz = textureSize(src_tex, 0);
79 // floor() the product, not the product minus half a texel: at output
80 // texel i the interpolated value is exactly i + 0.5, so flooring it has
81 // half a texel of slack either way where flooring i itself has none.
82 int b = int(floor(v_texcoord.x * float(sz.x)));
83 int cy = int(floor(v_texcoord.y * float(sz.y >> 1)));
84
85 vec2 csz = vec2(float(sz.x >> 1), float(sz.y >> 1));
86 vec2 tc = (vec2(float(b >> 1), float(cy)) + 0.5) / csz;
87 vec3 yuv = convert_from_rgb(texture(src_tex, flip_y(tc)).rgb);
88 fragColor = vec4(((b & 1) == 0) ? yuv.y : yuv.z, 0.0, 0.0, 1.0);
89 }
90 )_";
91
92 // Y plane resources
93 QRhiTexture* m_yTexture{};
94 QRhiTextureRenderTarget* m_yRT{};
95 QRhiRenderPassDescriptor* m_yRP{};
96 QRhiShaderResourceBindings* m_ySRB{};
97 QRhiGraphicsPipeline* m_yPipeline{};
98 QRhiReadbackResult m_yReadback{};
99
100 // UV plane resources
101 QRhiTexture* m_uvTexture{};
102 QRhiTextureRenderTarget* m_uvRT{};
103 QRhiRenderPassDescriptor* m_uvRP{};
104 QRhiShaderResourceBindings* m_uvSRB{};
105 QRhiGraphicsPipeline* m_uvPipeline{};
106 QRhiReadbackResult m_uvReadback{};
108 // Shared
109 QRhiSampler* m_sampler{};
110 int m_width{};
111 int m_height{};
112
113 void init(
114 QRhi& rhi, const RenderState& state, QRhiTexture* inputRGBA, int width,
115 int height, const QString& colorConversion) override
116 {
117 m_width = width;
118 m_height = height;
119
120 m_sampler = rhi.newSampler(
121 QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
122 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
123 m_sampler->create();
124
125 auto vertSrc = QString::fromLatin1(vertex_shader);
126
127 // Y plane setup
128 {
129 m_yTexture = rhi.newTexture(
130 QRhiTexture::R8, QSize{width, height}, 1,
131 QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource);
132 m_yTexture->create();
133
134 m_yRT = rhi.newTextureRenderTarget({m_yTexture});
135 m_yRP = m_yRT->newCompatibleRenderPassDescriptor();
136 m_yRT->setRenderPassDescriptor(m_yRP);
137 m_yRT->create();
138
139 m_ySRB = rhi.newShaderResourceBindings();
140 m_ySRB->setBindings({
141 QRhiShaderResourceBinding::sampledTexture(
142 3, QRhiShaderResourceBinding::FragmentStage, inputRGBA, m_sampler),
143 });
144 m_ySRB->create();
145
146 auto [vs, fs] = makeShaders(
147 state, vertSrc, QString::fromLatin1(y_frag).arg(colorConversion));
148 m_yPipeline = rhi.newGraphicsPipeline();
149 m_yPipeline->setShaderStages({
150 {QRhiShaderStage::Vertex, vs},
151 {QRhiShaderStage::Fragment, fs},
152 });
153 m_yPipeline->setVertexInputLayout({});
154 m_yPipeline->setShaderResourceBindings(m_ySRB);
155 m_yPipeline->setRenderPassDescriptor(m_yRP);
156 m_yPipeline->create();
157 }
158
159 // UV plane setup
160 {
161 // R8, not RG8 -- see the class comment.
162 m_uvTexture = rhi.newTexture(
163 QRhiTexture::R8, QSize{width, height / 2}, 1,
164 QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource);
165 m_uvTexture->create();
166
167 m_uvRT = rhi.newTextureRenderTarget({m_uvTexture});
168 m_uvRP = m_uvRT->newCompatibleRenderPassDescriptor();
169 m_uvRT->setRenderPassDescriptor(m_uvRP);
170 m_uvRT->create();
171
172 m_uvSRB = rhi.newShaderResourceBindings();
173 m_uvSRB->setBindings({
174 QRhiShaderResourceBinding::sampledTexture(
175 3, QRhiShaderResourceBinding::FragmentStage, inputRGBA, m_sampler),
176 });
177 m_uvSRB->create();
178
179 const char* uv_src = uv_frag;
180 auto [vs, fs]
181 = makeShaders(state, vertSrc, QString::fromLatin1(uv_src).arg(colorConversion));
182 m_uvPipeline = rhi.newGraphicsPipeline();
183 m_uvPipeline->setShaderStages({
184 {QRhiShaderStage::Vertex, vs},
185 {QRhiShaderStage::Fragment, fs},
186 });
187 m_uvPipeline->setVertexInputLayout({});
188 m_uvPipeline->setShaderResourceBindings(m_uvSRB);
189 m_uvPipeline->setRenderPassDescriptor(m_uvRP);
190 m_uvPipeline->create();
191 }
192 }
193
194 void exec(QRhi& rhi, QRhiCommandBuffer& cb) override
195 {
196 // Pass 1: Y plane (full resolution)
197 cb.beginPass(m_yRT, Qt::black, {0.0f, 0});
198 cb.setGraphicsPipeline(m_yPipeline);
199 cb.setShaderResources(m_ySRB);
200 cb.setViewport(QRhiViewport(0, 0, m_width, m_height));
201 cb.draw(3);
202
203 auto* yReadbackBatch = rhi.nextResourceUpdateBatch();
204 yReadbackBatch->readBackTexture(QRhiReadbackDescription{m_yTexture}, &m_yReadback);
205 cb.endPass(yReadbackBatch);
206
207 // Pass 2: UV plane -- R8 at full width, half height, U and V interleaved
208 // one byte per texel.
209 cb.beginPass(m_uvRT, Qt::black, {0.0f, 0});
210 cb.setGraphicsPipeline(m_uvPipeline);
211 cb.setShaderResources(m_uvSRB);
212 cb.setViewport(QRhiViewport(0, 0, m_width, m_height / 2));
213 cb.draw(3);
214
215 auto* uvReadbackBatch = rhi.nextResourceUpdateBatch();
216 uvReadbackBatch->readBackTexture(
217 QRhiReadbackDescription{m_uvTexture}, &m_uvReadback);
218 cb.endPass(uvReadbackBatch);
219 }
220
221 int planeCount() const override { return 2; }
223 const QRhiReadbackResult& readback(int plane) const override
224 {
225 return plane == 0 ? m_yReadback : m_uvReadback;
226 }
227
228 void release() override
229 {
230 delete m_uvPipeline;
231 delete m_uvSRB;
232 delete m_uvRP;
233 delete m_uvRT;
234 delete m_uvTexture;
235 delete m_yPipeline;
236 delete m_ySRB;
237 delete m_yRP;
238 delete m_yRT;
239 delete m_yTexture;
240 delete m_sampler;
241 m_uvPipeline = m_yPipeline = nullptr;
242 m_uvSRB = m_ySRB = nullptr;
243 m_uvRP = m_yRP = nullptr;
244 m_uvRT = m_yRT = nullptr;
245 m_uvTexture = m_yTexture = nullptr;
246 m_sampler = nullptr;
247 }
248};
249
250} // 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:1707
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->NV12 encoder (semi-planar 4:2:0).
Definition encoders/NV12.hpp:33
void release() override
Release all GPU resources.
Definition encoders/NV12.hpp:222
void exec(QRhi &rhi, QRhiCommandBuffer &cb) override
Definition encoders/NV12.hpp:188
int planeCount() const override
Number of readback planes (1 for UYVY, 2 for NV12, 3 for I420).
Definition encoders/NV12.hpp:215
const QRhiReadbackResult & readback(int plane) const override
Get the readback result for a given plane. Valid after endOffscreenFrame.
Definition encoders/NV12.hpp:217
void init(QRhi &rhi, const RenderState &state, QRhiTexture *inputRGBA, int width, int height, const QString &colorConversion) override
Definition encoders/NV12.hpp:107