Skip to content

Commit ea4cf98

Browse files
committed
gfx: fix RGB24 sRGB + RGBA64 black decoders + decode-correctness test
Two shipped video-decoder bugs found by the decode-correctness matrix (both verified under ASAN on llvmpipe): - RGB24/BGR24 rendered ~15 dB too dark: RGB24Decoder's packed R8 data texture was flagged QRhiTexture::sRGB, so the sampler applied the sRGB EOTF to raw bytes on texelFetch. Drop the flag (data texture, not colour). 15 -> 99 dB. - RGBA64LE/BGRA64LE rendered pure black: routed to a half-float RGBA16F texture, but the data is 16-bit UNORM integer -> reinterpreted as halfs -> NaN. QRhi has no 4-channel 16-bit UNORM, so add RGBA64Decoder (R16 x w*4 packed + texelFetch reassembly, mirroring RGB48Decoder). black -> 51 dB. (RGBAF16LE, a genuine half-float format, still uses RGBA16F — unchanged.) Test: test_video_decode_correctness — every software-decodable pixel format encoded as a known pattern (ffprobe-verified pix_fmt), decoded through VideoDecoderTester --expect/--psnr, RGBA readback asserted vs ffmpeg's own decode (per-format PSNR bound) + truncated/garbage fuzz. 43 PASS / 0 XFAIL. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
1 parent 696f579 commit ea4cf98

5 files changed

Lines changed: 691 additions & 12 deletions

File tree

src/plugins/score-plugin-gfx/Gfx/Graph/decoders/GPUVideoDecoderFactory.cpp

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -145,14 +145,13 @@ std::unique_ptr<GPUVideoDecoder> createGPUVideoDecoder(
145145
QRhiTexture::RGBA8, 4, format,
146146
"processed.rgba = tex.abgr; " + f);
147147

148-
// RGBA 16-bit
148+
// RGBA 16-bit (uint16 UNORM data -> R16 packed texture + texelFetch, NOT the
149+
// half-float RGBA16F path which reinterpreted the bytes as halfs -> black).
149150
case AV_PIX_FMT_RGBA64LE:
150-
return std::make_unique<PackedDecoder>(
151-
QRhiTexture::RGBA16F, 8, format, f);
151+
return std::make_unique<RGBA64Decoder>(format, f);
152152
case AV_PIX_FMT_BGRA64LE:
153-
return std::make_unique<PackedDecoder>(
154-
QRhiTexture::RGBA16F, 8, format,
155-
"processed.rgba = vec4(tex.b, tex.g, tex.r, tex.a); " + f);
153+
return std::make_unique<RGBA64Decoder>(
154+
format, "processed.rgb = tex.bgr; " + f);
156155

157156
#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
158157
case AV_PIX_FMT_X2RGB10LE:

src/plugins/score-plugin-gfx/Gfx/Graph/decoders/RGBA.hpp

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -332,8 +332,13 @@ struct RGB24Decoder : GPUVideoDecoder
332332
const auto w = decoder.width, h = decoder.height;
333333

334334
{
335-
// Create a texture
336-
auto tex = rhi.newTexture(QRhiTexture::R8, QSize{w * 3, h}, 1, QRhiTexture::sRGB);
335+
// Create a texture. NOTE: no QRhiTexture::sRGB — this is a *data* texture
336+
// (packed R8 bytes read individually with texelFetch), not a colour
337+
// texture. The sRGB flag makes the sampler apply the sRGB->linear EOTF to
338+
// every raw byte on fetch, so RGB24/BGR24 video decoded ~15 dB darker than
339+
// the identical frame in any other RGB pixel format (which set no flag and
340+
// are bit-exact). Found by tests/integration/video-decode-correctness.sh.
341+
auto tex = rhi.newTexture(QRhiTexture::R8, QSize{w * 3, h}, 1);
337342
tex->create();
338343

339344
// Create a sampler
@@ -442,5 +447,85 @@ struct RGB48Decoder : GPUVideoDecoder
442447
}
443448
};
444449

450+
// RGBA / BGRA 16-bit-per-channel (rgba64le / bgra64le). The data is 16-bit
451+
// UNORM *integer*; QRhi has no 4-channel 16-bit UNORM format (only R16/RG16), so
452+
// — like RGB48 — we upload the packed samples into an R16 data texture of width
453+
// w*4 and reassemble the four components with texelFetch. (The previous routing
454+
// to a half-float RGBA16F texture reinterpreted the uint16 bytes as halfs ->
455+
// NaN/denormals -> the frame decoded pure black. Found by
456+
// tests/integration/video-decode-correctness.sh.)
457+
struct RGBA64Decoder : GPUVideoDecoder
458+
{
459+
static const constexpr auto rgb_filter = R"_(#version 450
460+
461+
)_" SCORE_GFX_VIDEO_UNIFORMS R"_(
462+
463+
layout(binding=3) uniform sampler2D y_tex;
464+
465+
layout(location = 0) in vec2 v_texcoord;
466+
layout(location = 0) out vec4 fragColor;
467+
468+
vec4 processTexture(vec4 tex) {
469+
vec4 processed = tex;
470+
{ %1 }
471+
return processed;
472+
}
473+
474+
void main ()
475+
{
476+
float w = mat.texSz.x;
477+
float h = mat.texSz.y;
478+
int x = int(floor(v_texcoord.x * w) * 4.);
479+
int y = int(v_texcoord.y * h);
480+
float r = texelFetch(y_tex, ivec2(x + 0, y), 0).r;
481+
float g = texelFetch(y_tex, ivec2(x + 1, y), 0).r;
482+
float b = texelFetch(y_tex, ivec2(x + 2, y), 0).r;
483+
float a = texelFetch(y_tex, ivec2(x + 3, y), 0).r;
484+
fragColor = processTexture(vec4(r, g, b, a));
485+
})_";
486+
487+
RGBA64Decoder(Video::ImageFormat& d, QString f = "")
488+
: decoder{d}
489+
, filter{std::move(f)}
490+
{
491+
}
492+
Video::ImageFormat& decoder;
493+
QString filter;
494+
495+
std::pair<QShader, QShader> init(RenderList& r) override
496+
{
497+
auto& rhi = *r.state.rhi;
498+
const auto w = decoder.width, h = decoder.height;
499+
500+
{
501+
auto tex = rhi.newTexture(QRhiTexture::R16, QSize{w * 4, h}, 1, QRhiTexture::Flag{});
502+
tex->create();
503+
504+
auto sampler = rhi.newSampler(
505+
QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
506+
QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
507+
sampler->create();
508+
509+
samplers.push_back({sampler, tex});
510+
}
511+
512+
return score::gfx::makeShaders(
513+
r.state, vertexShader(), QString(rgb_filter).arg(filter));
514+
}
515+
516+
void exec(RenderList&, QRhiResourceUpdateBatch& res, AVFrame& frame) override
517+
{
518+
const auto w = decoder.width, h = decoder.height;
519+
auto y_tex = samplers[0].texture;
520+
521+
// 4 R16 samples per pixel = 8 bytes per pixel
522+
QRhiTextureUploadEntry entry{
523+
0, 0, createTextureUpload(frame.data[0], w * 4, h, 2, frame.linesize[0])};
524+
525+
QRhiTextureUploadDescription desc{entry};
526+
res.uploadTexture(y_tex, desc);
527+
}
528+
};
529+
445530
}
446531

0 commit comments

Comments
 (0)