From 70bc01925eba2baef83d6445c76ee705482db7e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 9 Oct 2026 20:18:14 +0200 Subject: [PATCH] Read the CLUT8 depal byte of an 8888 framebuffer exactly int(t * 254.99) gave each byte value minus one: c / 255 * 254.99 lands just below c. With 255.99 it's c for every byte. The texel also gets the best available precision: in fp16 the product rounds, and 16 of the 256 bytes came out one low. Burnout Dominator's car select lens flare gets closer to the PSP on Vulkan (57 -> 48 px); nothing else in the frame dump set changes. Co-Authored-By: Claude Opus 5.5 --- GPU/Common/FragmentShaderGenerator.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index 8c9cc1fc05..d9b94d9a46 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -829,15 +829,16 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu p.C(" uv_round.x = floor(uv_round.x * 0.5);\n"); } p.C(" uv_round = (uv_round + 0.5) / tsize;\n"); - p.C(" vec4 t = ").SampleTexture2D("tex", "uv_round").C(";\n"); + // The byte decodes below are exact in fp32, but the product rounding in fp16 makes 16 of 256 bytes come out one low. + p.F(" %s vec4 t = ", prec).SampleTexture2D("tex", "uv_round").C(";\n"); p.C(" int index;\n"); switch (shaderDepalFmt) { case GE_FORMAT_8888: p.C(" switch (component) {\n"); - p.C(" case 0: index = int(t.x * 254.99); break;\n"); // TODO: Not sure why 254.99 instead of 255.99, but it's currently needed. - p.C(" case 1: index = int(t.y * 254.99); break;\n"); - p.C(" case 2: index = int(t.z * 254.99); break;\n"); - p.C(" case 3: index = int(t.w * 254.99); break;\n"); + p.C(" case 0: index = int(t.x * 255.99); break;\n"); + p.C(" case 1: index = int(t.y * 255.99); break;\n"); + p.C(" case 2: index = int(t.z * 255.99); break;\n"); + p.C(" case 3: index = int(t.w * 255.99); break;\n"); p.C(" }\n"); break; default: