72 float chroma_byte(int b, int cr, ivec2 sz) {
73 vec2 uv = chroma_at(b >> 1, cr, sz);
74 return ((b & 1) == 0) ? uv.x : uv.y;
81 float chroma_byte(int b, int cr, ivec2 sz) {
82 int cw = sz.x >> 1; // bytes in one chroma plane row
83 int ch = sz.y >> 1; // rows in one chroma plane
84 // A target row is w bytes and a chroma plane row is w/2, so a target row
85 // holds EXACTLY two chroma rows. That makes the mapping shifts and
86 // compares -- no integer division, which is worth avoiding here because
87 // it would run once per chroma byte of every frame.
88 int sel = (b < cw) ? 0 : 1; // "half" is a GLSL reserved word
89 int col = b - sel * cw;
90 int gr = (cr << 1) + sel; // chroma row counted across both planes
92 vec2 uv = chroma_at(col, first ? gr : gr - ch, sz);
93 return first ? uv.%1 : uv.%2;
99 static constexpr const char* frag = R
"_(#version 450
100 layout(location = 0) in vec2 v_texcoord;
101 layout(location = 0) out vec4 fragColor;
102 layout(binding = 3) uniform sampler2D src_tex;
105 // Constants, so the branches on them fold away. Declared before
106 // chroma_at(), which uses SITING.
108 const int SITING = %4;
110 vec2 flip_y(vec2 tc) {
111 // Only OpenGL: this pass draws a hardcoded triangle in raw NDC rather than
112 // going through renderer.clipSpaceCorrMatrix.
114 // It flips the SOURCE lookup, never the target row order -- v_texcoord.y
115 // runs from 0 at the first row of the readback on both backends, which is
116 // what lets the luma/chroma split sit anywhere rather than at the midpoint.
117 #if defined(QSHADER_SPIRV) || defined(QSHADER_MSL) || defined(QSHADER_HLSL)
120 return vec2(tc.x, 1.0 - tc.y);
124 // Luma of source pixel (x, y). At an exact texel centre a Linear sampler
125 // returns that texel, so this is the plane encoder's full-size pass.
126 float luma_at(int x, int y, ivec2 sz) {
127 vec2 tc = (vec2(float(x), float(y)) + 0.5) / vec2(sz);
128 return convert_from_rgb(texture(src_tex, flip_y(tc)).rgb).x;
131 // (Cb, Cr) of chroma site (cx, cy). Vertically always the boundary
132 // between source rows 2*cy and 2*cy+1, so one tap averages the pair.
133 vec2 chroma_at(int cx, int cy, ivec2 sz) {
135 float y = float((cy << 1) + 1) / fs.y; // boundary of the row pair
138 float x = float((cx << 1) + 1) / fs.x; // boundary of the column pair
139 return convert_from_rgb(texture(src_tex, flip_y(vec2(x, y))).rgb).yz;
143 // [1 3 3 1]/8 each way as four bilinear taps: a tap at t=0.75 weights
144 // its pair 1:3 and one at t=0.25 weights it 3:1, so averaging the two
145 // gives 1:3:3:1 centred on the same boundary the box uses.
146 float xa = (float(cx << 1) + 0.25) / fs.x;
147 float xb = (float(cx << 1) + 1.75) / fs.x;
148 float ya = (float(cy << 1) + 0.25) / fs.y;
149 float yb = (float(cy << 1) + 1.75) / fs.y;
150 vec3 p0 = convert_from_rgb(texture(src_tex, flip_y(vec2(xa, ya))).rgb);
151 vec3 p1 = convert_from_rgb(texture(src_tex, flip_y(vec2(xb, ya))).rgb);
152 vec3 p2 = convert_from_rgb(texture(src_tex, flip_y(vec2(xa, yb))).rgb);
153 vec3 p3 = convert_from_rgb(texture(src_tex, flip_y(vec2(xb, yb))).rgb);
154 return (p0.yz + p1.yz + p2.yz + p3.yz) * 0.25;
156 float xa = float(cx << 1) / fs.x; // left of column 2*cx
157 float xb = float((cx << 1) + 1) / fs.x; // right of it
158 vec3 a = convert_from_rgb(texture(src_tex, flip_y(vec2(xa, y))).rgb);
159 vec3 b = convert_from_rgb(texture(src_tex, flip_y(vec2(xb, y))).rgb);
160 return (a.yz + b.yz) * 0.5;
165 float byte_at(int b, int outRow, ivec2 sz) {
166 return (outRow < sz.y) ? luma_at(b, outRow, sz)
167 : chroma_byte(b, outRow - sz.y, sz);
171 ivec2 sz = textureSize(src_tex, 0);
172 int outRows = sz.y + (sz.y >> 1);
174 int outRow = int(floor(v_texcoord.y * float(outRows)));
175 int t = int(floor(v_texcoord.x * float(sz.x / BPT)));
179 fragColor = vec4(byte_at(b0, outRow, sz), 0.0, 0.0, 1.0);
182 byte_at(b0, outRow, sz), byte_at(b0 + 1, outRow, sz),
183 byte_at(b0 + 2, outRow, sz), byte_at(b0 + 3, outRow, sz));
193 static std::unique_ptr<Yuv420PackedEncoder> nv12()
195 return std::make_unique<Yuv420PackedEncoder>(
Layout::NV12);
197 static std::unique_ptr<Yuv420PackedEncoder> i420()
199 return std::make_unique<Yuv420PackedEncoder>(
Layout::I420);
201 static std::unique_ptr<Yuv420PackedEncoder> yv12()
203 return std::make_unique<Yuv420PackedEncoder>(
Layout::YV12);
208 QRhiTexture* m_outTexture{};
209 QRhiTextureRenderTarget* m_renderTarget{};
210 QRhiRenderPassDescriptor* m_rpDesc{};
211 QRhiSampler* m_sampler{};
212 QRhiShaderResourceBindings* m_srb{};
213 QRhiGraphicsPipeline* m_pipeline{};
214 QRhiReadbackResult m_readback{};
217 int m_bytesPerTexel{1};
218 bool m_readbackEnabled{
true};
221 int framestoreRows() const noexcept {
return m_height + m_height / 2; }
224 QRhi& rhi,
const RenderState& state, QRhiTexture* inputRGBA,
int width,
225 int height,
const QString& colorConversion)
override
232 if(
const auto env = qgetenv(
"SCORE_GFX_CHROMA_SITING"); !env.isEmpty())
234 const auto e = env.toLower();
240 m_bytesPerTexel = (width % 4 == 0) ? 4 : 1;
241 m_outTexture = rhi.newTexture(
242 m_bytesPerTexel == 4 ? QRhiTexture::RGBA8 : QRhiTexture::R8,
244 QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource);
245 m_outTexture->create();
247 m_renderTarget = rhi.newTextureRenderTarget({m_outTexture});
248 m_rpDesc = m_renderTarget->newCompatibleRenderPassDescriptor();
249 m_renderTarget->setRenderPassDescriptor(m_rpDesc);
250 m_renderTarget->create();
254 m_sampler = rhi.newSampler(
255 QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
256 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
259 m_srb = rhi.newShaderResourceBindings();
261 QRhiShaderResourceBinding::sampledTexture(
262 3, QRhiShaderResourceBinding::FragmentStage, inputRGBA, m_sampler),
274 .arg(QStringLiteral(
"x"), QStringLiteral(
"y"));
278 .arg(QStringLiteral(
"y"), QStringLiteral(
"x"));
285 QString::fromLatin1(frag).arg(
286 colorConversion, chroma, QString::number(m_bytesPerTexel),
287 QString::number(
int(m_siting))));
289 m_pipeline = rhi.newGraphicsPipeline();
290 m_pipeline->setShaderStages({
291 {QRhiShaderStage::Vertex, vertS},
292 {QRhiShaderStage::Fragment, fragS},
294 m_pipeline->setVertexInputLayout({});
295 m_pipeline->setShaderResourceBindings(m_srb);
296 m_pipeline->setRenderPassDescriptor(m_rpDesc);
297 m_pipeline->create();
300 void exec(QRhi& rhi, QRhiCommandBuffer& cb)
override
302 cb.beginPass(m_renderTarget, Qt::black, {0.0f, 0});
303 cb.setGraphicsPipeline(m_pipeline);
304 cb.setShaderResources(m_srb);
309 if(m_readbackEnabled)
311 auto* readbackBatch = rhi.nextResourceUpdateBatch();
312 QRhiReadbackDescription rb(m_outTexture);
313 readbackBatch->readBackTexture(rb, &m_readback);
314 cb.endPass(readbackBatch);
324 const QRhiReadbackResult&
readback(
int)
const override {
return m_readback; }
325 QRhiTexture*
outputTexture() const noexcept
override {
return m_outTexture; }
331 m_pipeline =
nullptr;
338 delete m_renderTarget;
339 m_renderTarget =
nullptr;
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:1303