From 15e4c378fb80fa030bc60631d92610de358d4869 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 11 Nov 2026 23:37:19 +0100 Subject: [PATCH 1/3] GLES: Don't read back fragColor0 when it's the framebuffer fetch inout With EXT_shader_framebuffer_fetch, fragColor0 is inout. On Adreno 506 (driver V@145, Redmi Note 4), reading it back after writing it gives the wrong value: the color write mask path packs it and LittleBigPlanet's objects drew solid black. Build the color in a local and write the output once at the end. Co-Authored-By: Claude Opus 5.5 --- GPU/Common/FragmentShaderGenerator.cpp | 27 ++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index 951d2e7576..898eb632db 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -1200,18 +1200,26 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu } } + // With EXT_shader_framebuffer_fetch, fragColor0 is inout. Adreno 506 (driver V@145) reads it back wrong once + // the shader has written it (packing it for the write mask came out black), so build the color in a local. + const char *fragColor0 = compat.fragColor0; + if (fetchFramebuffer && compat.lastFragData && !strcmp(compat.lastFragData, fragColor0)) { + WRITE(p, " vec4 outColor0;\n"); + fragColor0 = "outColor0"; + } + switch (stencilToAlpha) { case REPLACE_ALPHA_DUALSOURCE: - WRITE(p, " %s = vec4(v.rgb, %s);\n", compat.fragColor0, replacedAlpha); + WRITE(p, " %s = vec4(v.rgb, %s);\n", fragColor0, replacedAlpha); WRITE(p, " %s = vec4(0.0, 0.0, 0.0, v.a);\n", compat.fragColor1); break; case REPLACE_ALPHA_YES: - WRITE(p, " %s = vec4(v.rgb, %s);\n", compat.fragColor0, replacedAlpha); + WRITE(p, " %s = vec4(v.rgb, %s);\n", fragColor0, replacedAlpha); break; case REPLACE_ALPHA_NO: - WRITE(p, " %s = v;\n", compat.fragColor0); + WRITE(p, " %s = v;\n", fragColor0); break; default: @@ -1221,10 +1229,10 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu switch (simulateLogicOpType) { case LOGICOPTYPE_ONE: - WRITE(p, " %s.rgb = splat3(1.0);\n", compat.fragColor0); + WRITE(p, " %s.rgb = splat3(1.0);\n", fragColor0); break; case LOGICOPTYPE_INVERT: - WRITE(p, " %s.rgb = splat3(1.0) - %s.rgb;\n", compat.fragColor0, compat.fragColor0); + WRITE(p, " %s.rgb = splat3(1.0) - %s.rgb;\n", fragColor0, fragColor0); break; case LOGICOPTYPE_NORMAL: break; @@ -1236,7 +1244,7 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu // Final color computed - apply logic ops and bitwise color write mask, through shader blending, if specified. if (colorWriteMask || replaceLogicOp) { - WRITE(p, " highp uint v32 = packUnorm4x8%s(%s);\n", packSuffix, compat.fragColor0); + WRITE(p, " highp uint v32 = packUnorm4x8%s(%s);\n", packSuffix, fragColor0); WRITE(p, " highp uint d32 = packUnorm4x8%s(destColor);\n", packSuffix); // v32 is both the "s" to the logical operation, and the value that we'll merge to the destination with masking later. @@ -1268,11 +1276,14 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu else WRITE(p, " v32 = (v32 & u_colorWriteMask & 0x00FFFFFFu) | (d32 & (~u_colorWriteMask | 0xFF000000u));\n"); } - WRITE(p, " %s = unpackUnorm4x8%s(v32);\n", compat.fragColor0, packSuffix); + WRITE(p, " %s = unpackUnorm4x8%s(v32);\n", fragColor0, packSuffix); } if (blueToAlpha) { - WRITE(p, " %s = vec4(0.0, 0.0, 0.0, %s.z); // blue to alpha\n", compat.fragColor0, compat.fragColor0); + WRITE(p, " %s = vec4(0.0, 0.0, 0.0, %s.z); // blue to alpha\n", fragColor0, fragColor0); + } + if (fragColor0 != compat.fragColor0) { + WRITE(p, " %s = %s;\n", compat.fragColor0, fragColor0); } if (fsDepthClamp || fsMinmaxDiscard) { From 4a98ebba1126875a72978aee6924b57fcdb5a40b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 11 Nov 2026 23:52:20 +0100 Subject: [PATCH 2/3] GLES: Don't use framebuffer fetch on Adreno 5xx On an Adreno 506 (driver V@145, Redmi Note 4), fetch can return black for pixels an earlier draw in the same render pass wrote, which showed up as dark noise on LittleBigPlanet's foliage. Framebuffer copies render it correctly, and that frame needs only a few of them. Co-Authored-By: Claude Opus 5.5 --- Common/GPU/OpenGL/thin3d_gl.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Common/GPU/OpenGL/thin3d_gl.cpp b/Common/GPU/OpenGL/thin3d_gl.cpp index 2fc87b164f..7a9a8c0b8f 100644 --- a/Common/GPU/OpenGL/thin3d_gl.cpp +++ b/Common/GPU/OpenGL/thin3d_gl.cpp @@ -746,7 +746,10 @@ OpenGLContext::OpenGLContext(bool canChangeSwapInterval) : renderManager_(frameT } // NOTE: We only support framebuffer fetch on ES3 due to past issues.. - if (gl_extensions.IsGLES && gl_extensions.GLES3) { + // Adreno 5xx (seen on a 506, driver V@145) can fetch black instead of what an earlier draw in the same pass + // wrote there, so it gets framebuffer copies instead. + bool brokenFramebufferFetch = caps_.vendor == GPUVendor::VENDOR_QUALCOMM && gl_extensions.modelNumber < 600; + if (gl_extensions.IsGLES && gl_extensions.GLES3 && !brokenFramebufferFetch) { caps_.framebufferFetchSupported = (gl_extensions.EXT_shader_framebuffer_fetch || gl_extensions.ARM_shader_framebuffer_fetch); if (gl_extensions.EXT_shader_framebuffer_fetch) { From e72dfa1006e853c260e4ab8246e7525e77e405a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 11 Nov 2026 23:58:33 +0100 Subject: [PATCH 3/3] Vulkan: Don't use the light ubershader on Adreno 5xx On an Adreno 506 (Vulkan 1.0.20 driver, Redmi Note 4), the ubershader's loop over the lights gives wrong lighting: LittleBigPlanet's Sackboy and lit scenery came out pale. The per-light shaders render it right. Uses the existing UNIFORM_INDEXING_BROKEN flag, set so far for old Mali. Co-Authored-By: Claude Opus 5.5 --- Common/GPU/Vulkan/thin3d_vulkan.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp index 5e93a3082f..8a1fc3d9ca 100644 --- a/Common/GPU/Vulkan/thin3d_vulkan.cpp +++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp @@ -1087,6 +1087,11 @@ VKContext::VKContext(VulkanContext *vulkan, bool useRenderThread) if (!turnip && (deviceProps.driverVersion < 0x801EA000 || deviceProps.deviceID < 0x06000000)) bugs_.Infest(Bugs::COLORWRITEMASK_BROKEN_WITH_DEPTHTEST); + // The light ubershader's loop over the lights gives wrong lighting (LittleBigPlanet's Sackboy and scenery came + // out pale). Seen on Adreno 506 with a Vulkan 1.0.20 driver; the per-light shaders are fine. + if (!turnip && deviceProps.deviceID >= 0x05000000 && deviceProps.deviceID < 0x06000000) + bugs_.Infest(Bugs::UNIFORM_INDEXING_BROKEN); + // Trying to follow all the rules in https://registry.khronos.org/vulkan/specs/1.3/html/vkspec.html#synchronization-pipeline-barriers-subpass-self-dependencies // and https://registry.khronos.org/vulkan/specs/1.3/html/vkspec.html#renderpass-feedbackloop, but still it doesn't // quite work - artifacts on triangle boundaries on Adreno.