Loading...
Searching...
No Matches
P216Packed.hpp
1#pragma once
2#include <Gfx/Graph/encoders/GPUVideoEncoder.hpp>
3
4namespace score::gfx
5{
6
31{
32 // %1 = colorMatrixOut() shader defining convert_from_rgb(vec3)
33 static constexpr const char* frag = R"_(#version 450
34 layout(location = 0) in vec2 v_texcoord;
35 layout(location = 0) out vec4 fragColor;
36 layout(binding = 3) uniform sampler2D src_tex;
37 )_" "%1" R"_(
38
39 vec2 flip_y(vec2 tc) {
40 // Only OpenGL. The rest of the engine puts its geometry through
41 // renderer.clipSpaceCorrMatrix, which negates Y on Vulkan; this pass draws
42 // a hardcoded triangle in raw NDC and does not, so the correction it needs
43 // is not the same one.
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 // One 16-bit sample as two RGBA8 bytes, little endian. The sample is the
52 // 8-bit code shifted left by 8 (see SCORE_GFX_MSB_ALIGNED_SCALE), so
53 // limited-range white is 235 << 8 == 60160. Rounded before the split: a
54 // fractional low byte would be rounded on its own by the UNORM write and
55 // could carry into nothing.
56 vec2 split16(float v) {
57 float u = floor(clamp(v, 0.0, 1.0) * 65280.0 + 0.5);
58 float hi = floor(u / 256.0);
59 float lo = u - hi * 256.0;
60 return vec2(lo / 255.0, hi / 255.0);
61 }
62
63 void main() {
64 vec2 srcSz = vec2(textureSize(src_tex, 0));
65 float outRows = srcSz.y * 2.0;
66
67 // Which row of the framestore this fragment is on, and therefore which
68 // plane: the luma rows come first, then the chroma rows.
69 float outRow = floor(v_texcoord.y * outRows);
70 bool chroma = outRow >= srcSz.y;
71 float row = chroma ? outRow - srcSz.y : outRow;
72
73 // The source row, with the Y-flip folded in as everywhere else.
74 float sy = (flip_y(vec2(0.0, (row + 0.5) / srcSz.y))).y;
75
76 // Two source pixels per output texel, as for UYVY.
77 float halfW = srcSz.x * 0.5;
78 // floor() the product, not the product minus half a texel: at output
79 // texel i the interpolated value is exactly i + 0.5, so flooring it has
80 // half a texel of slack either way where flooring i itself has none.
81 float outPixel = floor(v_texcoord.x * halfW);
82 float sx0 = (outPixel * 2.0 + 0.5) / srcSz.x;
83 float sx1 = (outPixel * 2.0 + 1.5) / srcSz.x;
84
85 vec3 yuv0 = convert_from_rgb(texture(src_tex, vec2(sx0, sy)).rgb);
86 vec3 yuv1 = convert_from_rgb(texture(src_tex, vec2(sx1, sy)).rgb);
87
88 if(chroma) {
89 // One (Cb, Cr) pair per chroma site, averaged across the pixel pair.
90 float u = (yuv0.y + yuv1.y) * 0.5;
91 float v = (yuv0.z + yuv1.z) * 0.5;
92 fragColor = vec4(split16(u), split16(v));
93 } else {
94 // Two luma samples.
95 fragColor = vec4(split16(yuv0.x), split16(yuv1.x));
96 }
97 }
98 )_";
99
100 QRhiTexture* m_outTexture{};
101 QRhiTextureRenderTarget* m_renderTarget{};
102 QRhiRenderPassDescriptor* m_rpDesc{};
103 QRhiSampler* m_sampler{};
104 QRhiShaderResourceBindings* m_srb{};
105 QRhiGraphicsPipeline* m_pipeline{};
106 QRhiReadbackResult m_readback{};
107 int m_width{};
108 int m_height{};
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) override
114 {
115 m_width = width;
116 m_height = height;
117
118 // The framestore: half width because each texel carries two 16-bit values,
119 // twice the height because luma and chroma planes are stacked.
120 m_outTexture = rhi.newTexture(
121 QRhiTexture::RGBA8, QSize{width / 2, height * 2}, 1,
122 QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource);
123 m_outTexture->create();
124
125 m_renderTarget = rhi.newTextureRenderTarget({m_outTexture});
126 m_rpDesc = m_renderTarget->newCompatibleRenderPassDescriptor();
127 m_renderTarget->setRenderPassDescriptor(m_rpDesc);
128 m_renderTarget->create();
129
130 // Nearest: every fetch here is at an exact texel centre, and a linear tap
131 // would blend neighbouring pixels into the packed bytes.
132 m_sampler = rhi.newSampler(
133 QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
134 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
135 m_sampler->create();
136
137 m_srb = rhi.newShaderResourceBindings();
138 m_srb->setBindings({
139 QRhiShaderResourceBinding::sampledTexture(
140 3, QRhiShaderResourceBinding::FragmentStage, inputRGBA, m_sampler),
141 });
142 m_srb->create();
143
144 auto [vertS, fragS] = makeShaders(
145 state, QString::fromLatin1(vertex_shader),
146 QString::fromLatin1(frag).arg(colorConversion));
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_width / 2, m_height * 2));
165 cb.draw(3);
166
167 if(m_readbackEnabled)
168 {
169 auto* readbackBatch = rhi.nextResourceUpdateBatch();
170 QRhiReadbackDescription rb(m_outTexture);
171 readbackBatch->readBackTexture(rb, &m_readback);
172 cb.endPass(readbackBatch);
173 }
174 else
175 {
176 cb.endPass();
178 }
180 int planeCount() const override { return 1; }
182 const QRhiReadbackResult& readback(int) const override { return m_readback; }
183 QRhiTexture* outputTexture() const noexcept override { return m_outTexture; }
184 void setReadbackEnabled(bool e) noexcept override { m_readbackEnabled = e; }
185
186 void release() override
187 {
188 delete m_pipeline;
189 m_pipeline = nullptr;
190 delete m_srb;
191 m_srb = nullptr;
192 delete m_sampler;
193 m_sampler = nullptr;
194 delete m_rpDesc;
195 m_rpDesc = nullptr;
196 delete m_renderTarget;
197 m_renderTarget = nullptr;
198 delete m_outTexture;
199 m_outTexture = nullptr;
200 }
201};
202
203} // 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 -> p216, as ONE texture holding the whole framestore.
Definition P216Packed.hpp:31
void init(QRhi &rhi, const RenderState &state, QRhiTexture *inputRGBA, int width, int height, const QString &colorConversion) override
Definition P216Packed.hpp:108
void exec(QRhi &rhi, QRhiCommandBuffer &cb) override
Definition P216Packed.hpp:156
void release() override
Release all GPU resources.
Definition P216Packed.hpp:183
void setReadbackEnabled(bool e) noexcept override
Definition P216Packed.hpp:181
QRhiTexture * outputTexture() const noexcept override
Definition P216Packed.hpp:180
int planeCount() const override
Number of readback planes (1 for UYVY, 2 for NV12, 3 for I420).
Definition P216Packed.hpp:177
const QRhiReadbackResult & readback(int) const override
Get the readback result for a given plane. Valid after endOffscreenFrame.
Definition P216Packed.hpp:179