From d3c802cfa82053a2946106e75e4044f6e27e636c Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 2 Oct 2026 23:41:15 +0700 Subject: [PATCH 01/81] Add modernized legacy PBR shader support --- GPU/Common/LegacyPBR.h | 67 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 GPU/Common/LegacyPBR.h diff --git a/GPU/Common/LegacyPBR.h b/GPU/Common/LegacyPBR.h new file mode 100644 index 000000000000..53d4dc87d4cb --- /dev/null +++ b/GPU/Common/LegacyPBR.h @@ -0,0 +1,67 @@ +#pragma once + +#include +#include + +#include "Common/GPU/ShaderWriter.h" +#include "Core/Config.h" +#include "Common/StringUtils.h" + +inline bool LegacyPBRVertexMode1(bool doTexture, bool useHWTransform, bool hasNormal, bool hasTexcoord, bool enableFog, bool enableLighting, bool vertexRangeCulling) { + return doTexture && useHWTransform && hasNormal && hasTexcoord && enableFog && enableLighting && vertexRangeCulling; +} + +inline bool LegacyPBRVertexMode2(bool doTexture, bool useHWTransform, bool hasTexcoord, bool enableFog, bool enableLighting, bool vertexRangeCulling) { + return doTexture && useHWTransform && hasTexcoord && enableFog && enableLighting && vertexRangeCulling; +} + +inline bool LegacyPBRFragmentBase(bool enableFragmentTestCache, bool doTexture, bool enableFog) { + return enableFragmentTestCache && doTexture && enableFog; +} + +inline void WriteLegacyPBRVaryingVS(ShaderWriter &p, const char *varying) { + p.F("%s lowp flat int flag;\n", varying); + for (int i = 1; i <= 8; ++i) + p.F("%s highp vec4 v_%d;\n", varying, i); +} + +inline void WriteLegacyPBRVaryingFS(ShaderWriter &p, const char *shading, const char *varying) { + p.F("precision highp float;\n"); + p.F("%s %s lowp flat int flag;\n", shading, varying); + for (int i = 1; i <= 8; ++i) + p.F("%s %s highp vec4 v_%d;\n", shading, varying, i); +} + +inline void WriteLegacyPBRPrelude(ShaderWriter &p) { + p.C("vec3 albedo;\n"); + p.C("float metallic = 0.5;\n"); + p.C("float roughness = 0.15;\n"); + p.C("vec3 lightPositions[2];\n"); + p.C("vec3 lightColors[2];\n"); + p.C("vec3 camPos = vec3(0.0, 0.0, 50.0);\n"); + p.C("const float PI = 3.14159265359;\n"); + p.C("float lightness(float R, float G, float B) { return pow(pow(R / 1.0, 2.2) + pow(G / 0.666666, 2.2) + pow(B / 1.666666, 2.2), 1.0 / 2.2) * 0.547373; }\n"); + p.C("float DistributionGGX(vec3 N, vec3 H, float r) { float a = r*r; float a2 = a*a; float n = max(dot(N,H),0.0); float n2=n*n; float nom=a2; float denom=(n2*(a2-1.0)+1.0); denom=PI*denom*denom; return nom/denom; }\n"); + p.C("float GeometrySchlickGGX(float NdotV, float r) { float k=(r+1.0)*(r+1.0)/8.0; return NdotV/(NdotV*(1.0-k)+k); }\n"); + p.C("float GeometrySmith(vec3 N, vec3 V, vec3 L, float r) { float a=max(dot(N,V),0.0); float b=max(dot(N,L),0.0); return GeometrySchlickGGX(a,r)*GeometrySchlickGGX(b,r); }\n"); + p.C("vec3 fresnelSchlick(float c, vec3 F0) { return F0+(1.0-F0)*pow(clamp(1.0-c,0.0,1.0),5.0); }\n"); + p.C("void PBR__2_0() {\n"); + p.C(" vec3 N=normalize(v_1.xyz);\n"); + p.C(" vec3 fogcolor=vec3(mix(vec3(dot(u_fogcolor,vec3(0.299,0.587,0.114))),u_fogcolor,0.5));\n"); + p.C(" float l=lightness(u_fogcolor.r,u_fogcolor.g,u_fogcolor.b);\n"); + p.C(" vec3 ld; float ll; float minv; fogcolor += 0.75-l; vec3 fogcolor_inverse=1.0-fogcolor; vec3 diff1,diff2;\n"); + p.C(" if(l<0.15){ minv=0.2; ll=1.5; ld=normalize(v_5.xyz); ld.z=abs(ld.z)*7.0; diff1=fogcolor*ll; diff2=fogcolor_inverse*ll; lightPositions[0].z=v_3.z*1000.0; lightPositions[1].z=v_4.z*1000.0; lightColors[0]=fogcolor_inverse; lightColors[1]=fogcolor; } else { minv=0.1; ll=2.0; ld=normalize(v_4.xyz); ld.z=abs(ld.z)*5.0; diff1=fogcolor_inverse; diff2=fogcolor*ll; lightPositions[0].z=v_4.z*1000.0; lightPositions[1].z=v_3.z*1000.0; lightColors[0]=fogcolor; lightColors[1]=fogcolor_inverse; }\n"); + p.C(" lightPositions[0].x=N.x*1000.0; lightPositions[0].y=N.y*2000.0; lightPositions[1].x=N.x*3500.0; lightPositions[1].y=N.y*5000.0;\n"); + p.C(" vec3 lightDir=normalize(ld*1000.0-v_2.xyz); float diff=max(dot(N,lightDir),0.0); if(diff0.015){ vec3 V=normalize(camPos-v_2.xyz); vec3 F0=vec3(0.04); F0=mix(F0,albedo,metallic); vec3 Lo=vec3(0.0); for(int i=0;i<2;++i){ vec3 L=normalize(lightPositions[i]-v_2.xyz); vec3 H=normalize(V+L); float NDF=DistributionGGX(N,H,roughness); float G=GeometrySmith(N,V,L,roughness); vec3 F=fresnelSchlick(clamp(dot(H,V),0.0,1.0),F0); vec3 numerator=NDF*G*F; float denominator=4.0*max(dot(N,V),0.0)*max(dot(N,L),0.0)+0.0001; vec3 specular=numerator/denominator; vec3 kS=F; vec3 kD=(vec3(1.0)-kS)*(1.0-metallic); float NdotL=max(dot(N,L),0.0); Lo+=(kD*albedo/PI+specular)*lightColors[i]*NdotL; } vec3 color=Lo+albedo; color=color/(color+vec3(1.0)); color*=diffColor; fragColor0=vec4(color,1.0); } else { fragColor0=vec4(albedo*diffColor*0.75,1.0); }\n"); + p.C("}\n"); +} + +inline bool LoadLegacyGLSLOverride(const std::string &stage, uint64_t shaderID, std::string *source) { + const std::string path = g_Config.memStickDirectory + "PSP/SHADERS/GLSL/" + stage + "_" + StringFromFormat("%016llx", (unsigned long long)shaderID) + ".glsl"; + std::ifstream f(path); + if (!f.is_open()) + return false; + *source = std::string((std::istreambuf_iterator(f)), std::istreambuf_iterator()); + return !source->empty() && source->size() + 1 < 32768; +} From b0ae2e116e4806a98e980161ca4d21c3435f6246 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 2 Oct 2026 23:41:37 +0700 Subject: [PATCH 02/81] Migrate legacy PBR vertex generation --- GPU/Common/VertexShaderGenerator.cpp | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/GPU/Common/VertexShaderGenerator.cpp b/GPU/Common/VertexShaderGenerator.cpp index b93ebd085f4b..116d456e139e 100644 --- a/GPU/Common/VertexShaderGenerator.cpp +++ b/GPU/Common/VertexShaderGenerator.cpp @@ -26,6 +26,7 @@ #include "GPU/Common/ShaderId.h" #include "GPU/Common/ShaderUniforms.h" #include "GPU/Common/VertexShaderGenerator.h" +#include "GPU/Common/LegacyPBR.h" #include "GPU/Vulkan/DrawEngineVulkan.h" #undef WRITE @@ -164,6 +165,8 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag } bool texCoordInVec3 = false; + const bool legacyPBRVertexMode1 = LegacyPBRVertexMode1(doTexture, useHWTransform, hasNormal, hasTexcoord, enableFog, enableLighting, rangeCulling); + const bool legacyPBRVertexMode2 = LegacyPBRVertexMode2(doTexture, useHWTransform, hasTexcoord, enableFog, enableLighting, rangeCulling); const char *minZClipPlaneSuffix = "[0]"; const char *maxZClipPlaneSuffix = "[1]"; @@ -423,6 +426,10 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag if (fsMinmaxDiscard || fsDepthClamp) { WRITE(p, "%s highp vec2 v_zw;\n", compat.varying_vs); } + if (ShaderLanguageIsOpenGL(compat.shaderLanguage)) { + WRITE(p, "//****** my_varying_vs *********\n"); + WriteLegacyPBRVaryingVS(p, compat.varying_vs); + } } if (useHWTransform) { @@ -508,6 +515,22 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag } else { WRITE(p, " mediump vec3 worldnormal = normalizeOr001(mul(vec4(0.0, 0.0, %s1.0, 0.0), u_world).xyz);\n", flipNormal ? "-" : ""); } + if (legacyPBRVertexMode1) { + WRITE(p, " flag = 1;\n"); + WRITE(p, " mat3 v;\n"); + WRITE(p, " v[0] = vec3(u_view[0].xyz);\n"); + WRITE(p, " v[1] = vec3(u_view[1].xyz);\n"); + WRITE(p, " v[2] = vec3(u_view[2].xyz);\n"); + WRITE(p, " v_1 = vec4(worldnormal.xyz, 1.0);\n"); + WRITE(p, " v_2 = vec4(worldpos, 1.0);\n"); + WRITE(p, " v_3 = vec4(normalize(vec3(0.,10000.,2000.) * v), 1.0);\n"); + WRITE(p, " v_4 = vec4(normalize(vec3(0.,10000.,-2000.) * v), 1.0);\n"); + WRITE(p, " v_5 = vec4(v_3.x, -v_3.y, v_3.z, 1.0);\n"); + } else if (legacyPBRVertexMode2) { + WRITE(p, " flag = 2;\n"); + } else { + WRITE(p, " flag = 0;\n"); + } if (enableLighting || doShadeMapping) { // The viewer is at infinity along view space +z: in world space, the view matrix's third column. if (compat.shaderLanguage == HLSL_D3D11) { @@ -1001,5 +1024,9 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag WRITE(p, " return Out;\n"); } WRITE(p, "}\n"); + std::string overrideSource; + if (ShaderLanguageIsOpenGL(compat.shaderLanguage) && LoadLegacyGLSLOverride("Vertex", id.ToUint64(), &overrideSource)) { + memcpy(buffer, overrideSource.c_str(), overrideSource.size() + 1); + } return true; } From e301757cc9dcae494d77470e9d5ca12c51d67643 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 2 Oct 2026 23:41:48 +0700 Subject: [PATCH 03/81] Migrate legacy PBR fragment generation --- GPU/Common/FragmentShaderGenerator.cpp | 31 ++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index ab51b2a21345..7b91683bb763 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -30,6 +30,7 @@ #include "GPU/Common/ShaderId.h" #include "GPU/Common/ShaderUniforms.h" #include "GPU/Common/FragmentShaderGenerator.h" +#include "GPU/Common/LegacyPBR.h" #include "GPU/Vulkan/DrawEngineVulkan.h" #include "GPU/ge_constants.h" #include "GPU/GPUState.h" @@ -69,6 +70,9 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu } bool texture3D = id.Bit(FS_BIT_3D_TEXTURE); + bool doTextureAlpha = id.Bit(FS_BIT_LEGACY_PBR_ALPHA); + const bool legacyPBRFragmentBase = LegacyPBRFragmentBase(enableFragmentTestCache, id.Bit(FS_BIT_DO_TEXTURE), gstate.isFogEnabled()); + const bool legacyPBRFragmentAlpha = legacyPBRFragmentBase && doTextureAlpha; bool arrayTexture = id.Bit(FS_BIT_SAMPLE_ARRAY_TEXTURE); bool forceDepthWritesOff = id.Bit(FS_BIT_DEPTH_TEST_NEVER); bool useDiscardStencilBugWorkaround = id.Bit(FS_BIT_NO_DEPTH_CANNOT_DISCARD_STENCIL) && !forceDepthWritesOff; @@ -416,6 +420,11 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu if (fsMinmaxDiscard || fsDepthClamp) { WRITE(p, "%s highp vec2 v_zw;\n", compat.varying_fs); } + if (ShaderLanguageIsOpenGL(compat.shaderLanguage) && legacyPBRFragmentBase) { + WRITE(p, "//****** my_varying_fs *********\n"); + WriteLegacyPBRVaryingFS(p, shading, compat.varying_fs); + WriteLegacyPBRPrelude(p); + } if (!enableFragmentTestCache) { if (enableAlphaTest && !alphaTestAgainstZero) { @@ -862,6 +871,24 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu WRITE(p, " vec4 v = v_color0%s;\n", secondary); } + if (ShaderLanguageIsOpenGL(compat.shaderLanguage) && legacyPBRFragmentBase) { + WRITE(p, " if (flag == 1) {\n"); + if (legacyPBRFragmentAlpha) { + WRITE(p, " vec4 tex_color = t;\n"); + WRITE(p, " if (tex_color.a > 0.15) {\n"); + WRITE(p, " albedo = tex_color.rgb;\n"); + WRITE(p, " roughness = 1.0 - (t.a - roughness);\n"); + WRITE(p, " PBR__2_0();\n"); + WRITE(p, " } else {\n"); + WRITE(p, " fragColor0 = vec4(tex_color.rgb, 1.0);\n"); + WRITE(p, " }\n"); + } else { + WRITE(p, " albedo = t.xyz;\n"); + WRITE(p, " PBR__2_0();\n"); + } + WRITE(p, " }\n"); + } + if (enableFog) { WRITE(p, " float fogCoef = clamp(v_fogdepth, 0.0, 1.0);\n"); WRITE(p, " v.rgb = mix(u_fogcolor, v.rgb, fogCoef);\n"); @@ -1225,6 +1252,10 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu } WRITE(p, "}\n"); + std::string overrideSource; + if (ShaderLanguageIsOpenGL(compat.shaderLanguage) && LoadLegacyGLSLOverride("Fragment", id.ToUint64(), &overrideSource)) { + memcpy(buffer, overrideSource.c_str(), overrideSource.size() + 1); + } return true; } From ccaeac1d8f40022f0a891d0dcfaa388710810a74 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 2 Oct 2026 23:42:01 +0700 Subject: [PATCH 04/81] Use modern fragment ID predicates for legacy PBR --- GPU/Common/FragmentShaderGenerator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index 7b91683bb763..9794a45ac187 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -71,7 +71,7 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu bool texture3D = id.Bit(FS_BIT_3D_TEXTURE); bool doTextureAlpha = id.Bit(FS_BIT_LEGACY_PBR_ALPHA); - const bool legacyPBRFragmentBase = LegacyPBRFragmentBase(enableFragmentTestCache, id.Bit(FS_BIT_DO_TEXTURE), gstate.isFogEnabled()); + const bool legacyPBRFragmentBase = LegacyPBRFragmentBase(enableFragmentTestCache, doTexture, enableFog); const bool legacyPBRFragmentAlpha = legacyPBRFragmentBase && doTextureAlpha; bool arrayTexture = id.Bit(FS_BIT_SAMPLE_ARRAY_TEXTURE); bool forceDepthWritesOff = id.Bit(FS_BIT_DEPTH_TEST_NEVER); From b863fc0ea71bc0f5cddd9344a816fa63535a1b9b Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 2 Oct 2026 23:42:05 +0700 Subject: [PATCH 05/81] Reserve shader ID bit for legacy PBR alpha --- GPU/Common/ShaderId.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/GPU/Common/ShaderId.h b/GPU/Common/ShaderId.h index bb9a322ec512..73f56434d382 100644 --- a/GPU/Common/ShaderId.h +++ b/GPU/Common/ShaderId.h @@ -110,7 +110,8 @@ enum FShaderBit : uint8_t { FS_BIT_USE_FRAMEBUFFER_FETCH = 59, FS_BIT_DEPTH_TEST_NEVER = 60, // Only used on Mali. Set when depth == NEVER. We forcibly avoid writing to depth in this case, since it crashes the driver. FS_BIT_SAMPLE_ARRAY_TEXTURE = 61, // For multiview, framebuffers are array textures and we need to sample the two layers correctly. - // Free bits: 62-63 + FS_BIT_LEGACY_PBR_ALPHA = 62, + // Free bit: 63 }; static inline FShaderBit operator +(FShaderBit bit, int i) { From 9e16ab7a4626f5dd90cbb2cb0eddb386c897519a Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 2 Oct 2026 23:42:10 +0700 Subject: [PATCH 06/81] Add legacy PBR alpha to modern fragment shader ID --- GPU/Common/ShaderId.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/GPU/Common/ShaderId.cpp b/GPU/Common/ShaderId.cpp index 6231168ed2c3..901fb775ce0a 100644 --- a/GPU/Common/ShaderId.cpp +++ b/GPU/Common/ShaderId.cpp @@ -331,6 +331,10 @@ void ComputeFragmentShaderID(FShaderID *id_out, const ComputedPipelineState &pip bool doFlatShading = gstate.getShadeMode() == GE_SHADE_FLAT; bool enableTexAlpha = gstate.isTextureAlphaUsed(); + const bool legacyPBRBase = gstate_c.Use(GPU_USE_FRAGMENT_TEST_CACHE) && gstate.isTextureMapEnabled() && enableFog; + if (legacyPBRBase && enableTexAlpha) { + id.SetBit(FS_BIT_LEGACY_PBR_ALPHA); + } ShaderDepalMode shaderDepalMode = gstate_c.shaderDepalMode; GEBufferFormat shaderDepalFormat = {}; From 3b0e2c9205c1dc6ee44cf4af7de7f0d992c59903 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 2 Oct 2026 23:42:15 +0700 Subject: [PATCH 07/81] Bump shader cache version for legacy PBR ID --- GPU/GLES/ShaderManagerGLES.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GPU/GLES/ShaderManagerGLES.cpp b/GPU/GLES/ShaderManagerGLES.cpp index 0ed21507aab4..99c9c469f3cf 100644 --- a/GPU/GLES/ShaderManagerGLES.cpp +++ b/GPU/GLES/ShaderManagerGLES.cpp @@ -856,7 +856,7 @@ enum class CacheDetectFlags { }; #define CACHE_HEADER_MAGIC 0x83277592 -#define CACHE_VERSION 44 +#define CACHE_VERSION 45 struct CacheHeader { uint32_t magic; From f763fcd6a6c5a7dc655d9229b958f1d93dd55ec4 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 2 Oct 2026 23:42:18 +0700 Subject: [PATCH 08/81] Bump shader cache version for legacy PBR ID --- GPU/Vulkan/ShaderManagerVulkan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GPU/Vulkan/ShaderManagerVulkan.cpp b/GPU/Vulkan/ShaderManagerVulkan.cpp index 926b4e006765..9b95053e1d4d 100644 --- a/GPU/Vulkan/ShaderManagerVulkan.cpp +++ b/GPU/Vulkan/ShaderManagerVulkan.cpp @@ -369,7 +369,7 @@ enum class VulkanCacheDetectFlags { }; #define CACHE_HEADER_MAGIC 0xff51f420 -#define CACHE_VERSION 61 +#define CACHE_VERSION 62 struct VulkanCacheHeader { uint32_t magic; From a1e366ea8713d5eaf3f19528ceac7599ba09c4d0 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 2 Oct 2026 23:48:07 +0700 Subject: [PATCH 09/81] Revert Vulkan shader cache version change --- GPU/Vulkan/ShaderManagerVulkan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GPU/Vulkan/ShaderManagerVulkan.cpp b/GPU/Vulkan/ShaderManagerVulkan.cpp index 9b95053e1d4d..926b4e006765 100644 --- a/GPU/Vulkan/ShaderManagerVulkan.cpp +++ b/GPU/Vulkan/ShaderManagerVulkan.cpp @@ -369,7 +369,7 @@ enum class VulkanCacheDetectFlags { }; #define CACHE_HEADER_MAGIC 0xff51f420 -#define CACHE_VERSION 62 +#define CACHE_VERSION 61 struct VulkanCacheHeader { uint32_t magic; From 8a65e01355a3acaeb998b2a72a97145f8242a422 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 2 Oct 2026 23:48:15 +0700 Subject: [PATCH 10/81] Revert GLES shader cache version change --- GPU/GLES/ShaderManagerGLES.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GPU/GLES/ShaderManagerGLES.cpp b/GPU/GLES/ShaderManagerGLES.cpp index 99c9c469f3cf..0ed21507aab4 100644 --- a/GPU/GLES/ShaderManagerGLES.cpp +++ b/GPU/GLES/ShaderManagerGLES.cpp @@ -856,7 +856,7 @@ enum class CacheDetectFlags { }; #define CACHE_HEADER_MAGIC 0x83277592 -#define CACHE_VERSION 45 +#define CACHE_VERSION 44 struct CacheHeader { uint32_t magic; From ca6329cbe4de29a995ee2e4a2f4ec13209c7c802 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 2 Oct 2026 23:50:17 +0700 Subject: [PATCH 11/81] Restore OpenGL shader cache version for legacy PBR --- GPU/GLES/ShaderManagerGLES.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GPU/GLES/ShaderManagerGLES.cpp b/GPU/GLES/ShaderManagerGLES.cpp index 0ed21507aab4..99c9c469f3cf 100644 --- a/GPU/GLES/ShaderManagerGLES.cpp +++ b/GPU/GLES/ShaderManagerGLES.cpp @@ -856,7 +856,7 @@ enum class CacheDetectFlags { }; #define CACHE_HEADER_MAGIC 0x83277592 -#define CACHE_VERSION 44 +#define CACHE_VERSION 45 struct CacheHeader { uint32_t magic; From d301d118795906d4f537b2b5bded6c637c0db978 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 2 Oct 2026 23:50:32 +0700 Subject: [PATCH 12/81] Keep legacy PBR alpha shader ID OpenGL-specific --- GPU/Common/ShaderId.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/GPU/Common/ShaderId.cpp b/GPU/Common/ShaderId.cpp index 901fb775ce0a..e263bbafd78d 100644 --- a/GPU/Common/ShaderId.cpp +++ b/GPU/Common/ShaderId.cpp @@ -330,11 +330,6 @@ void ComputeFragmentShaderID(FShaderID *id_out, const ComputedPipelineState &pip bool doTextureProjection = (gstate.getUVGenMode() == GE_TEXMAP_TEXTURE_MATRIX && MatrixNeedsProjection(gstate.tgenMatrix, gstate.getUVProjMode())); bool doFlatShading = gstate.getShadeMode() == GE_SHADE_FLAT; - bool enableTexAlpha = gstate.isTextureAlphaUsed(); - const bool legacyPBRBase = gstate_c.Use(GPU_USE_FRAGMENT_TEST_CACHE) && gstate.isTextureMapEnabled() && enableFog; - if (legacyPBRBase && enableTexAlpha) { - id.SetBit(FS_BIT_LEGACY_PBR_ALPHA); - } ShaderDepalMode shaderDepalMode = gstate_c.shaderDepalMode; GEBufferFormat shaderDepalFormat = {}; From e94104811febd0ed5ac21fd2c6f2b34c9596f3dd Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 2 Oct 2026 23:50:38 +0700 Subject: [PATCH 13/81] Set legacy PBR alpha ID only on OpenGL --- GPU/GLES/ShaderManagerGLES.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/GPU/GLES/ShaderManagerGLES.cpp b/GPU/GLES/ShaderManagerGLES.cpp index 99c9c469f3cf..1ebf77ea8fbb 100644 --- a/GPU/GLES/ShaderManagerGLES.cpp +++ b/GPU/GLES/ShaderManagerGLES.cpp @@ -718,6 +718,10 @@ LinkedShader *ShaderManagerGLES::ApplyFragmentShader(VShaderID VSID, Shader *vs, lastShader_ = nullptr; gstate_c.Clean(DIRTY_FRAGMENTSHADER_STATE); ComputeFragmentShaderID(&FSID, pipelineState, draw_->GetBugs(), clipInfoFlags); + // Legacy PBR alpha is an OpenGL/GLES-only shader variant. + if (gstate_c.Use(GPU_USE_FRAGMENT_TEST_CACHE) && gstate.isTextureMapEnabled() && gstate.isFogEnabled() && gstate.isTextureAlphaUsed()) { + FSID.SetBit(FS_BIT_LEGACY_PBR_ALPHA); + } } else { FSID = lastFSID_; } From 085b805d520fc2f3d2fc4115aacb6ba636f2b004 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 2 Oct 2026 23:54:53 +0700 Subject: [PATCH 14/81] Disable androidGitVersion test --- .github/workflows/manual_generate_apk.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/manual_generate_apk.yml b/.github/workflows/manual_generate_apk.yml index c2a06e947421..4a95b355193d 100644 --- a/.github/workflows/manual_generate_apk.yml +++ b/.github/workflows/manual_generate_apk.yml @@ -55,10 +55,10 @@ jobs: # with: # ndk-version: r21e - - name: Test androidGitVersion - run: | - echo "count=${{steps.valid-tags.outputs.count}}" - gradle --quiet androidGitVersion + #- name: Test androidGitVersion + # run: | + # echo "count=${{steps.valid-tags.outputs.count}}" + # gradle --quiet androidGitVersion - name: Assemble APK run: ./gradlew assemble${{ github.event.inputs.buildVariant }} --stacktrace From 3f48b11ff43954c865acf2a15ef5431812905ae2 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Sat, 3 Oct 2026 00:06:14 +0700 Subject: [PATCH 15/81] Fix LegacyPBR shader override path construction --- GPU/Common/LegacyPBR.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GPU/Common/LegacyPBR.h b/GPU/Common/LegacyPBR.h index 53d4dc87d4cb..967f43241695 100644 --- a/GPU/Common/LegacyPBR.h +++ b/GPU/Common/LegacyPBR.h @@ -58,7 +58,7 @@ inline void WriteLegacyPBRPrelude(ShaderWriter &p) { } inline bool LoadLegacyGLSLOverride(const std::string &stage, uint64_t shaderID, std::string *source) { - const std::string path = g_Config.memStickDirectory + "PSP/SHADERS/GLSL/" + stage + "_" + StringFromFormat("%016llx", (unsigned long long)shaderID) + ".glsl"; + const std::string path = (g_Config.memStickDirectory / "PSP/SHADERS/GLSL").ToString() + "/" + stage + "_" + StringFromFormat("%016llx", (unsigned long long)shaderID) + ".glsl"; std::ifstream f(path); if (!f.is_open()) return false; From 981c7ee2d62467cc8c6501747b78a5d3270f18ba Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Sat, 3 Oct 2026 00:08:48 +0700 Subject: [PATCH 16/81] Fix legacy PBR fragment predicate declaration order --- GPU/Common/FragmentShaderGenerator.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index 9794a45ac187..9d94892882d6 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -71,8 +71,6 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu bool texture3D = id.Bit(FS_BIT_3D_TEXTURE); bool doTextureAlpha = id.Bit(FS_BIT_LEGACY_PBR_ALPHA); - const bool legacyPBRFragmentBase = LegacyPBRFragmentBase(enableFragmentTestCache, doTexture, enableFog); - const bool legacyPBRFragmentAlpha = legacyPBRFragmentBase && doTextureAlpha; bool arrayTexture = id.Bit(FS_BIT_SAMPLE_ARRAY_TEXTURE); bool forceDepthWritesOff = id.Bit(FS_BIT_DEPTH_TEST_NEVER); bool useDiscardStencilBugWorkaround = id.Bit(FS_BIT_NO_DEPTH_CANNOT_DISCARD_STENCIL) && !forceDepthWritesOff; @@ -112,6 +110,8 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu bool lmode = id.Bit(FS_BIT_LMODE); bool doTexture = id.Bit(FS_BIT_DO_TEXTURE); bool enableFog = id.Bit(FS_BIT_ENABLE_FOG); + const bool legacyPBRFragmentBase = LegacyPBRFragmentBase(enableFragmentTestCache, doTexture, enableFog); + const bool legacyPBRFragmentAlpha = legacyPBRFragmentBase && doTextureAlpha; bool enableAlphaTest = id.Bit(FS_BIT_ALPHA_TEST); bool alphaTestAgainstZero = id.Bit(FS_BIT_ALPHA_AGAINST_ZERO); From 0c063265abb84bee3f0716401db47576ae169e9f Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Sat, 3 Oct 2026 00:13:01 +0700 Subject: [PATCH 17/81] Fix legacy PBR GLSL override for PPSSPP scoped storage --- GPU/Common/LegacyPBR.h | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/GPU/Common/LegacyPBR.h b/GPU/Common/LegacyPBR.h index 967f43241695..6677ff37fe55 100644 --- a/GPU/Common/LegacyPBR.h +++ b/GPU/Common/LegacyPBR.h @@ -1,10 +1,10 @@ #pragma once -#include #include #include "Common/GPU/ShaderWriter.h" #include "Core/Config.h" +#include "Common/File/FileUtil.h" #include "Common/StringUtils.h" inline bool LegacyPBRVertexMode1(bool doTexture, bool useHWTransform, bool hasNormal, bool hasTexcoord, bool enableFog, bool enableLighting, bool vertexRangeCulling) { @@ -58,10 +58,6 @@ inline void WriteLegacyPBRPrelude(ShaderWriter &p) { } inline bool LoadLegacyGLSLOverride(const std::string &stage, uint64_t shaderID, std::string *source) { - const std::string path = (g_Config.memStickDirectory / "PSP/SHADERS/GLSL").ToString() + "/" + stage + "_" + StringFromFormat("%016llx", (unsigned long long)shaderID) + ".glsl"; - std::ifstream f(path); - if (!f.is_open()) - return false; - *source = std::string((std::istreambuf_iterator(f)), std::istreambuf_iterator()); - return !source->empty() && source->size() + 1 < 32768; + const Path path = g_Config.memStickDirectory / "PSP/SHADERS/GLSL" / (stage + "_" + StringFromFormat("%016llx", (unsigned long long)shaderID) + ".glsl"); + return File::ReadTextFileToString(path, source) && !source->empty() && source->size() + 1 < 32768; } From 832ec5c3742261eb15eaba647cb43fb8e8056413 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Sat, 3 Oct 2026 00:19:35 +0700 Subject: [PATCH 18/81] Fix legacy PBR GLSL path for PSP-root memsticks --- GPU/Common/LegacyPBR.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/GPU/Common/LegacyPBR.h b/GPU/Common/LegacyPBR.h index 6677ff37fe55..8bd8341db18d 100644 --- a/GPU/Common/LegacyPBR.h +++ b/GPU/Common/LegacyPBR.h @@ -4,6 +4,7 @@ #include "Common/GPU/ShaderWriter.h" #include "Core/Config.h" +#include "Core/Util/PathUtil.h" #include "Common/File/FileUtil.h" #include "Common/StringUtils.h" @@ -58,6 +59,11 @@ inline void WriteLegacyPBRPrelude(ShaderWriter &p) { } inline bool LoadLegacyGLSLOverride(const std::string &stage, uint64_t shaderID, std::string *source) { - const Path path = g_Config.memStickDirectory / "PSP/SHADERS/GLSL" / (stage + "_" + StringFromFormat("%016llx", (unsigned long long)shaderID) + ".glsl"); + // memStickDirectory may itself be the PSP directory on Android (e.g. /sdcard/PSP). + // GetSysDirectory(DIRECTORY_PSP) normalizes both cases: + // /sdcard/PSP -> /sdcard/PSP + // /sdcard/PPSSPP -> /sdcard/PPSSPP/PSP + const Path shaderDirectory = GetSysDirectory(DIRECTORY_PSP) / "SHADERS/GLSL"; + const Path path = shaderDirectory / (stage + "_" + StringFromFormat("%016llx", (unsigned long long)shaderID) + ".glsl"); return File::ReadTextFileToString(path, source) && !source->empty() && source->size() + 1 < 32768; } From de1a6d3fa263af44633fe28266f642b0baa846b4 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Sat, 3 Oct 2026 04:23:05 +0700 Subject: [PATCH 19/81] Fix legacy PBR vertex predicate state --- GPU/Common/LegacyPBR.h | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/GPU/Common/LegacyPBR.h b/GPU/Common/LegacyPBR.h index 8bd8341db18d..1014d6f3f737 100644 --- a/GPU/Common/LegacyPBR.h +++ b/GPU/Common/LegacyPBR.h @@ -8,12 +8,16 @@ #include "Common/File/FileUtil.h" #include "Common/StringUtils.h" -inline bool LegacyPBRVertexMode1(bool doTexture, bool useHWTransform, bool hasNormal, bool hasTexcoord, bool enableFog, bool enableLighting, bool vertexRangeCulling) { - return doTexture && useHWTransform && hasNormal && hasTexcoord && enableFog && enableLighting && vertexRangeCulling; +// The legacy vertex flags included _doTexture and _enableFog, but the modern +// VShaderID does not carry either fragment-state bit. Those conditions are +// validated by the fragment shader when it selects the legacy PBR path. +// Here we only test state represented by VShaderID. +inline bool LegacyPBRVertexMode1(bool useHWTransform, bool hasNormal, bool hasTexcoord, bool enableLighting, bool vertexRangeCulling) { + return useHWTransform && hasNormal && hasTexcoord && enableLighting && vertexRangeCulling; } -inline bool LegacyPBRVertexMode2(bool doTexture, bool useHWTransform, bool hasTexcoord, bool enableFog, bool enableLighting, bool vertexRangeCulling) { - return doTexture && useHWTransform && hasTexcoord && enableFog && enableLighting && vertexRangeCulling; +inline bool LegacyPBRVertexMode2(bool useHWTransform, bool hasTexcoord, bool enableLighting, bool vertexRangeCulling) { + return useHWTransform && hasTexcoord && enableLighting && vertexRangeCulling; } inline bool LegacyPBRFragmentBase(bool enableFragmentTestCache, bool doTexture, bool enableFog) { From 8b395704117c5287510d8f0f7df0b48179134ff8 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Sat, 3 Oct 2026 05:12:25 +0700 Subject: [PATCH 20/81] Fix legacy PBR vertex predicate compile error --- GPU/Common/VertexShaderGenerator.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GPU/Common/VertexShaderGenerator.cpp b/GPU/Common/VertexShaderGenerator.cpp index 116d456e139e..c6a775d51c45 100644 --- a/GPU/Common/VertexShaderGenerator.cpp +++ b/GPU/Common/VertexShaderGenerator.cpp @@ -165,8 +165,8 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag } bool texCoordInVec3 = false; - const bool legacyPBRVertexMode1 = LegacyPBRVertexMode1(doTexture, useHWTransform, hasNormal, hasTexcoord, enableFog, enableLighting, rangeCulling); - const bool legacyPBRVertexMode2 = LegacyPBRVertexMode2(doTexture, useHWTransform, hasTexcoord, enableFog, enableLighting, rangeCulling); + const bool legacyPBRVertexMode1 = LegacyPBRVertexMode1(useHWTransform, hasNormal, hasTexcoord, enableLighting, rangeCulling); + const bool legacyPBRVertexMode2 = LegacyPBRVertexMode2(useHWTransform, hasTexcoord, enableLighting, rangeCulling); const char *minZClipPlaneSuffix = "[0]"; const char *maxZClipPlaneSuffix = "[1]"; From 89fc5f26a39deb5b0753130b90962ebb842c822c Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Sat, 3 Oct 2026 06:36:02 +0700 Subject: [PATCH 21/81] Add generated GLSL fallback dump helper --- GPU/Common/LegacyPBR.h | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/GPU/Common/LegacyPBR.h b/GPU/Common/LegacyPBR.h index 1014d6f3f737..48ea3c3860e8 100644 --- a/GPU/Common/LegacyPBR.h +++ b/GPU/Common/LegacyPBR.h @@ -62,12 +62,30 @@ inline void WriteLegacyPBRPrelude(ShaderWriter &p) { p.C("}\n"); } -inline bool LoadLegacyGLSLOverride(const std::string &stage, uint64_t shaderID, std::string *source) { +inline Path LegacyGLSLOverridePath(const std::string &stage, uint64_t shaderID) { // memStickDirectory may itself be the PSP directory on Android (e.g. /sdcard/PSP). // GetSysDirectory(DIRECTORY_PSP) normalizes both cases: // /sdcard/PSP -> /sdcard/PSP - // /sdcard/PPSSPP -> /sdcard/PPSSPP/PSP + // /sdcard/PPSSPP -> /sdcard/PPSSPP/PSP const Path shaderDirectory = GetSysDirectory(DIRECTORY_PSP) / "SHADERS/GLSL"; - const Path path = shaderDirectory / (stage + "_" + StringFromFormat("%016llx", (unsigned long long)shaderID) + ".glsl"); + return shaderDirectory / (stage + "_" + StringFromFormat("%016llx", (unsigned long long)shaderID) + ".glsl"); +} + +inline bool LoadLegacyGLSLOverride(const std::string &stage, uint64_t shaderID, std::string *source) { + const Path path = LegacyGLSLOverridePath(stage, shaderID); return File::ReadTextFileToString(path, source) && !source->empty() && source->size() + 1 < 32768; } + +inline bool SaveGeneratedLegacyGLSL(const std::string &stage, uint64_t shaderID, std::string_view source) { + const Path path = LegacyGLSLOverridePath(stage, shaderID); + + // Never overwrite a user-provided shader. If it already exists, the caller + // will load and use it instead. + if (File::Exists(path)) + return false; + + if (!File::CreateFullPath(path.GetDirectory())) + return false; + + return File::WriteStringToFile(true, source, path); +} From e78bd7bc049c18882c03776691a47d34709d09cb Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Sat, 3 Oct 2026 06:36:10 +0700 Subject: [PATCH 22/81] Dump generated GLSL when override is absent --- GPU/Common/VertexShaderGenerator.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/GPU/Common/VertexShaderGenerator.cpp b/GPU/Common/VertexShaderGenerator.cpp index c6a775d51c45..63771dd0c74c 100644 --- a/GPU/Common/VertexShaderGenerator.cpp +++ b/GPU/Common/VertexShaderGenerator.cpp @@ -1025,8 +1025,13 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag } WRITE(p, "}\n"); std::string overrideSource; - if (ShaderLanguageIsOpenGL(compat.shaderLanguage) && LoadLegacyGLSLOverride("Vertex", id.ToUint64(), &overrideSource)) { - memcpy(buffer, overrideSource.c_str(), overrideSource.size() + 1); + if (ShaderLanguageIsOpenGL(compat.shaderLanguage)) { + if (LoadLegacyGLSLOverride("Vertex", id.ToUint64(), &overrideSource)) { + memcpy(buffer, overrideSource.c_str(), overrideSource.size() + 1); + } else { + SaveGeneratedLegacyGLSL("Vertex", id.ToUint64(), std::string(buffer)); + } + } } return true; } From 61c7d4258477d2ed1bd82e21828b0adb0a4d08f1 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Sat, 3 Oct 2026 06:36:14 +0700 Subject: [PATCH 23/81] Dump generated GLSL when override is absent --- GPU/Common/FragmentShaderGenerator.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index 9d94892882d6..ac45ebb46e31 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -1253,8 +1253,13 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu WRITE(p, "}\n"); std::string overrideSource; - if (ShaderLanguageIsOpenGL(compat.shaderLanguage) && LoadLegacyGLSLOverride("Fragment", id.ToUint64(), &overrideSource)) { - memcpy(buffer, overrideSource.c_str(), overrideSource.size() + 1); + if (ShaderLanguageIsOpenGL(compat.shaderLanguage)) { + if (LoadLegacyGLSLOverride("Fragment", id.ToUint64(), &overrideSource)) { + memcpy(buffer, overrideSource.c_str(), overrideSource.size() + 1); + } else { + SaveGeneratedLegacyGLSL("Fragment", id.ToUint64(), std::string(buffer)); + } + } } return true; From d51b031602f993e5224368b2f049ed0bb1444e7d Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Sat, 3 Oct 2026 06:59:20 +0700 Subject: [PATCH 24/81] Fix LegacyPBR GLSL dump directory type --- GPU/Common/LegacyPBR.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GPU/Common/LegacyPBR.h b/GPU/Common/LegacyPBR.h index 48ea3c3860e8..1d581d1bc4e2 100644 --- a/GPU/Common/LegacyPBR.h +++ b/GPU/Common/LegacyPBR.h @@ -84,7 +84,7 @@ inline bool SaveGeneratedLegacyGLSL(const std::string &stage, uint64_t shaderID, if (File::Exists(path)) return false; - if (!File::CreateFullPath(path.GetDirectory())) + if (!File::CreateFullPath(Path(path.GetDirectory()))) return false; return File::WriteStringToFile(true, source, path); From 322b0bf53f38b8cb10eb543782966764add5df38 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Sat, 3 Oct 2026 07:08:10 +0700 Subject: [PATCH 25/81] Remove extra GLSL generator brace --- GPU/Common/VertexShaderGenerator.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/GPU/Common/VertexShaderGenerator.cpp b/GPU/Common/VertexShaderGenerator.cpp index 63771dd0c74c..07fafceba7e9 100644 --- a/GPU/Common/VertexShaderGenerator.cpp +++ b/GPU/Common/VertexShaderGenerator.cpp @@ -1032,6 +1032,5 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag SaveGeneratedLegacyGLSL("Vertex", id.ToUint64(), std::string(buffer)); } } - } return true; } From 968905d9363927a4847c98b8daa000b2480fa951 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Sat, 3 Oct 2026 07:08:13 +0700 Subject: [PATCH 26/81] Remove extra GLSL generator brace --- GPU/Common/FragmentShaderGenerator.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index ac45ebb46e31..cba1f61940df 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -1260,7 +1260,6 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu SaveGeneratedLegacyGLSL("Fragment", id.ToUint64(), std::string(buffer)); } } - } return true; } From 5b58da264a2eab9eb15695ce533bbb18c0b87b32 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Sat, 3 Oct 2026 11:49:23 +0700 Subject: [PATCH 27/81] Fix legacy PBR fragment generation and output selection --- GPU/Common/FragmentShaderGenerator.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index cba1f61940df..be2e2d3f92cd 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -110,7 +110,7 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu bool lmode = id.Bit(FS_BIT_LMODE); bool doTexture = id.Bit(FS_BIT_DO_TEXTURE); bool enableFog = id.Bit(FS_BIT_ENABLE_FOG); - const bool legacyPBRFragmentBase = LegacyPBRFragmentBase(enableFragmentTestCache, doTexture, enableFog); + const bool legacyPBRFragmentBase = ShaderLanguageIsOpenGL(compat.shaderLanguage) && doTexture && enableFog; const bool legacyPBRFragmentAlpha = legacyPBRFragmentBase && doTextureAlpha; bool enableAlphaTest = id.Bit(FS_BIT_ALPHA_TEST); @@ -871,6 +871,10 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu WRITE(p, " vec4 v = v_color0%s;\n", secondary); } + // Legacy PBR is generated before the normal post-processing. Preserve its result for the final output. + WRITE(p, " bool legacyPBRActive = false;\n"); + WRITE(p, " vec4 legacyPBRColor = vec4(0.0);\n"); + if (ShaderLanguageIsOpenGL(compat.shaderLanguage) && legacyPBRFragmentBase) { WRITE(p, " if (flag == 1) {\n"); if (legacyPBRFragmentAlpha) { @@ -880,11 +884,14 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu WRITE(p, " roughness = 1.0 - (t.a - roughness);\n"); WRITE(p, " PBR__2_0();\n"); WRITE(p, " } else {\n"); - WRITE(p, " fragColor0 = vec4(tex_color.rgb, 1.0);\n"); + WRITE(p, " legacyPBRColor = vec4(tex_color.rgb, 1.0);\n"); + WRITE(p, " legacyPBRActive = true;\n"); WRITE(p, " }\n"); } else { WRITE(p, " albedo = t.xyz;\n"); WRITE(p, " PBR__2_0();\n"); + WRITE(p, " legacyPBRColor = fragColor0;\n"); + WRITE(p, " legacyPBRActive = true;\n"); } WRITE(p, " }\n"); } @@ -1149,7 +1156,7 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu break; case REPLACE_ALPHA_NO: - WRITE(p, " %s = v;\n", compat.fragColor0); + WRITE(p, " %s = legacyPBRActive ? legacyPBRColor : v;\n", compat.fragColor0); break; default: @@ -1263,4 +1270,3 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu return true; } - From 78c24596b8dbb0925f226aed8ff84eafeeb4f98d Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Sat, 3 Oct 2026 12:35:27 +0700 Subject: [PATCH 28/81] Fix legacy PBR alpha result preservation --- GPU/Common/FragmentShaderGenerator.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index be2e2d3f92cd..f35e6a564f14 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -883,6 +883,8 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu WRITE(p, " albedo = tex_color.rgb;\n"); WRITE(p, " roughness = 1.0 - (t.a - roughness);\n"); WRITE(p, " PBR__2_0();\n"); + WRITE(p, " legacyPBRColor = fragColor0;\n"); + WRITE(p, " legacyPBRActive = true;\n"); WRITE(p, " } else {\n"); WRITE(p, " legacyPBRColor = vec4(tex_color.rgb, 1.0);\n"); WRITE(p, " legacyPBRActive = true;\n"); From bb72f4130c3e3a5d62947f89af4d914d8d0d7daf Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Thu, 8 Oct 2026 23:45:42 +0700 Subject: [PATCH 29/81] Add vertex linkage metadata to fragment generator --- GPU/Common/FragmentShaderGenerator.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/GPU/Common/FragmentShaderGenerator.h b/GPU/Common/FragmentShaderGenerator.h index 517f3f986ca4..3b0455fc8993 100644 --- a/GPU/Common/FragmentShaderGenerator.h +++ b/GPU/Common/FragmentShaderGenerator.h @@ -22,6 +22,7 @@ #include "Common/GPU/thin3d.h" struct FShaderID; +struct VShaderID; // Can technically be deduced from the fragment shader ID, but this is safer. enum class FragmentShaderFlags : u32 { @@ -30,4 +31,4 @@ enum class FragmentShaderFlags : u32 { }; ENUM_CLASS_BITOPS(FragmentShaderFlags); -bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLanguageDesc &compat, Draw::Bugs bugs, uint64_t *uniformMask, FragmentShaderFlags *fragmentShaderFlags, std::string *errorString); +bool GenerateFragmentShader(const FShaderID &id, const VShaderID *linkedVSID, char *buffer, const ShaderLanguageDesc &compat, Draw::Bugs bugs, uint64_t *uniformMask, FragmentShaderFlags *fragmentShaderFlags, std::string *errorString); From de81902d37d0d742059582213fe36a54b3ab69aa Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Thu, 8 Oct 2026 23:45:51 +0700 Subject: [PATCH 30/81] Annotate generated fragment with linked vertex shader --- GPU/Common/FragmentShaderGenerator.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index f35e6a564f14..59d7bd08a9a7 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -49,7 +49,7 @@ static const SamplerDef samplersStereo[3] = { { 2, "pal" }, }; -bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLanguageDesc &compat, Draw::Bugs bugs, uint64_t *uniformMask, FragmentShaderFlags *fragmentShaderFlags, std::string *errorString) { +bool GenerateFragmentShader(const FShaderID &id, const VShaderID *linkedVSID, char *buffer, const ShaderLanguageDesc &compat, Draw::Bugs bugs, uint64_t *uniformMask, FragmentShaderFlags *fragmentShaderFlags, std::string *errorString) { *uniformMask = 0; *fragmentShaderFlags = (FragmentShaderFlags)0; errorString->clear(); @@ -104,6 +104,9 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu ShaderWriter p(buffer, compat, ShaderStage::Fragment, extensions, flags); p.C("// %").W(id.Description()).endl(); + if (ShaderLanguageIsOpenGL(compat.shaderLanguage) && linkedVSID) { + p.F("// Used by vertex%016llx.glsl\\n", (unsigned long long)linkedVSID->ToUint64()); + } p.ApplySamplerMetadata(arrayTexture ? samplersStereo : samplersMono); From 7655354617d83be1cc428ddece85805f857e91c8 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Thu, 8 Oct 2026 23:46:06 +0700 Subject: [PATCH 31/81] Pass linked vertex ID to fragment compilation --- GPU/GLES/ShaderManagerGLES.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GPU/GLES/ShaderManagerGLES.h b/GPU/GLES/ShaderManagerGLES.h index db14a7135eb0..2a0ad3077f98 100644 --- a/GPU/GLES/ShaderManagerGLES.h +++ b/GPU/GLES/ShaderManagerGLES.h @@ -189,7 +189,7 @@ class ShaderManagerGLES : public ShaderManagerCommon { private: void Clear(); - Shader *CompileFragmentShader(FShaderID id); + Shader *CompileFragmentShader(FShaderID id, VShaderID VSID); Shader *CompileVertexShader(VShaderID id); struct LinkedShaderCacheEntry { From 100551333331f94654f69e60b3da3e6e53dac0ff Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Thu, 8 Oct 2026 23:46:09 +0700 Subject: [PATCH 32/81] Pass vertex ID when generating fragment shader --- GPU/GLES/ShaderManagerGLES.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GPU/GLES/ShaderManagerGLES.cpp b/GPU/GLES/ShaderManagerGLES.cpp index 1ebf77ea8fbb..6749325cad73 100644 --- a/GPU/GLES/ShaderManagerGLES.cpp +++ b/GPU/GLES/ShaderManagerGLES.cpp @@ -625,7 +625,7 @@ void ShaderManagerGLES::DeviceRestore(Draw::DrawContext *draw) { // Can only fail by failing to generate the code (bad FSID). // Any actual failures driver-side happens later in the render manager. -Shader *ShaderManagerGLES::CompileFragmentShader(FShaderID FSID) { +Shader *ShaderManagerGLES::CompileFragmentShader(FShaderID FSID, VShaderID VSID) { uint64_t uniformMask; std::string errorString; FragmentShaderFlags flags; @@ -738,7 +738,7 @@ LinkedShader *ShaderManagerGLES::ApplyFragmentShader(VShaderID VSID, Shader *vs, // Fragment shader not in cache. Let's compile it. // Can't really tell if we succeeded since the compile is on the GPU thread later. // Could fail to generate, in which case we're kinda screwed. - fs = CompileFragmentShader(FSID); + fs = CompileFragmentShader(FSID, VSID); if (!fs) { ERROR_LOG(Log::G3D, "Failed to generate fragment shader with ID %s", FSID.ToDebugString().c_str()); // Still insert it so we don't end up spamming generation. From caeddc13e001406cac844ad283946484781f13a4 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Thu, 8 Oct 2026 23:52:46 +0700 Subject: [PATCH 33/81] Add VS linkage and Legacy PBR metadata to generated GLSL --- GPU/Common/FragmentShaderGenerator.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index 59d7bd08a9a7..fac66e89bdce 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -105,7 +105,25 @@ bool GenerateFragmentShader(const FShaderID &id, const VShaderID *linkedVSID, ch ShaderWriter p(buffer, compat, ShaderStage::Fragment, extensions, flags); p.C("// %").W(id.Description()).endl(); if (ShaderLanguageIsOpenGL(compat.shaderLanguage) && linkedVSID) { + const bool linkedVSMode1 = LegacyPBRVertexMode1( + linkedVSID->Bit(VS_BIT_USE_HW_TRANSFORM), + linkedVSID->Bit(VS_BIT_HAS_NORMAL), + linkedVSID->Bit(VS_BIT_HAS_TEXCOORD) || !linkedVSID->Bit(VS_BIT_USE_HW_TRANSFORM), + linkedVSID->Bit(VS_BIT_LIGHTING_ENABLE), + linkedVSID->Bit(VS_BIT_VERTEX_RANGE_CULLING)); + const bool linkedVSMode2 = LegacyPBRVertexMode2( + linkedVSID->Bit(VS_BIT_USE_HW_TRANSFORM), + linkedVSID->Bit(VS_BIT_HAS_TEXCOORD) || !linkedVSID->Bit(VS_BIT_USE_HW_TRANSFORM), + linkedVSID->Bit(VS_BIT_LIGHTING_ENABLE), + linkedVSID->Bit(VS_BIT_VERTEX_RANGE_CULLING)); p.F("// Used by vertex%016llx.glsl\\n", (unsigned long long)linkedVSID->ToUint64()); + p.F("// Linked vertex shader ID: %016llx\\n", (unsigned long long)linkedVSID->ToUint64()); + if (linkedVSMode1) + p.C("// Legacy PBR vertex mode: 1 (fragment flag 1 is valid)\\n"); + else if (linkedVSMode2) + p.C("// Legacy PBR vertex mode: 2 (fragment flag 2; PBR path inactive)\\n"); + else + p.C("// Legacy PBR vertex mode: none (fragment PBR must remain inactive)\\n"); } p.ApplySamplerMetadata(arrayTexture ? samplersStereo : samplersMono); From 6a3728289fb68dcc62e149c2ce1ebc55249d0110 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Thu, 8 Oct 2026 23:52:49 +0700 Subject: [PATCH 34/81] Annotate generated GLSL with Legacy PBR vertex mode --- GPU/Common/VertexShaderGenerator.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/GPU/Common/VertexShaderGenerator.cpp b/GPU/Common/VertexShaderGenerator.cpp index 07fafceba7e9..90e3ac69241f 100644 --- a/GPU/Common/VertexShaderGenerator.cpp +++ b/GPU/Common/VertexShaderGenerator.cpp @@ -167,6 +167,12 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag bool texCoordInVec3 = false; const bool legacyPBRVertexMode1 = LegacyPBRVertexMode1(useHWTransform, hasNormal, hasTexcoord, enableLighting, rangeCulling); const bool legacyPBRVertexMode2 = LegacyPBRVertexMode2(useHWTransform, hasTexcoord, enableLighting, rangeCulling); + if (ShaderLanguageIsOpenGL(compat.shaderLanguage)) { + if (legacyPBRVertexMode1) + p.C("// Legacy PBR vertex mode: 1 (exports flag 1 + v_1..v_8)\\n"); + else if (legacyPBRVertexMode2) + p.C("// Legacy PBR vertex mode: 2 (exports flag 2 + v_1..v_8)\\n"); + } const char *minZClipPlaneSuffix = "[0]"; const char *maxZClipPlaneSuffix = "[1]"; From 8425bb1cfed1a09ba03c38f852f2405fd7e5b9a1 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Thu, 8 Oct 2026 23:53:10 +0700 Subject: [PATCH 35/81] Tie Legacy PBR fragment activation to linked vertex mode --- GPU/Common/FragmentShaderGenerator.cpp | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index fac66e89bdce..c6082ffa5f69 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -72,6 +72,22 @@ bool GenerateFragmentShader(const FShaderID &id, const VShaderID *linkedVSID, ch bool texture3D = id.Bit(FS_BIT_3D_TEXTURE); bool doTextureAlpha = id.Bit(FS_BIT_LEGACY_PBR_ALPHA); bool arrayTexture = id.Bit(FS_BIT_SAMPLE_ARRAY_TEXTURE); + + bool linkedLegacyPBRVertexMode1 = false; + bool linkedLegacyPBRVertexMode2 = false; + if (ShaderLanguageIsOpenGL(compat.shaderLanguage) && linkedVSID) { + linkedLegacyPBRVertexMode1 = LegacyPBRVertexMode1( + linkedVSID->Bit(VS_BIT_USE_HW_TRANSFORM), + linkedVSID->Bit(VS_BIT_HAS_NORMAL), + linkedVSID->Bit(VS_BIT_HAS_TEXCOORD) || !linkedVSID->Bit(VS_BIT_USE_HW_TRANSFORM), + linkedVSID->Bit(VS_BIT_LIGHTING_ENABLE), + linkedVSID->Bit(VS_BIT_VERTEX_RANGE_CULLING)); + linkedLegacyPBRVertexMode2 = LegacyPBRVertexMode2( + linkedVSID->Bit(VS_BIT_USE_HW_TRANSFORM), + linkedVSID->Bit(VS_BIT_HAS_TEXCOORD) || !linkedVSID->Bit(VS_BIT_USE_HW_TRANSFORM), + linkedVSID->Bit(VS_BIT_LIGHTING_ENABLE), + linkedVSID->Bit(VS_BIT_VERTEX_RANGE_CULLING)); + } bool forceDepthWritesOff = id.Bit(FS_BIT_DEPTH_TEST_NEVER); bool useDiscardStencilBugWorkaround = id.Bit(FS_BIT_NO_DEPTH_CANNOT_DISCARD_STENCIL) && !forceDepthWritesOff; @@ -131,7 +147,11 @@ bool GenerateFragmentShader(const FShaderID &id, const VShaderID *linkedVSID, ch bool lmode = id.Bit(FS_BIT_LMODE); bool doTexture = id.Bit(FS_BIT_DO_TEXTURE); bool enableFog = id.Bit(FS_BIT_ENABLE_FOG); - const bool legacyPBRFragmentBase = ShaderLanguageIsOpenGL(compat.shaderLanguage) && doTexture && enableFog; + const bool legacyPBRFragmentBase = + ShaderLanguageIsOpenGL(compat.shaderLanguage) && + linkedLegacyPBRVertexMode1 && + doTexture && + enableFog; const bool legacyPBRFragmentAlpha = legacyPBRFragmentBase && doTextureAlpha; bool enableAlphaTest = id.Bit(FS_BIT_ALPHA_TEST); From 2d3a0e02b6349e75086296615cc03f59b412a5b2 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Thu, 8 Oct 2026 23:53:21 +0700 Subject: [PATCH 36/81] Remove unused linked Legacy PBR mode state --- GPU/Common/FragmentShaderGenerator.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index c6082ffa5f69..5e2c8739e82f 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -74,7 +74,6 @@ bool GenerateFragmentShader(const FShaderID &id, const VShaderID *linkedVSID, ch bool arrayTexture = id.Bit(FS_BIT_SAMPLE_ARRAY_TEXTURE); bool linkedLegacyPBRVertexMode1 = false; - bool linkedLegacyPBRVertexMode2 = false; if (ShaderLanguageIsOpenGL(compat.shaderLanguage) && linkedVSID) { linkedLegacyPBRVertexMode1 = LegacyPBRVertexMode1( linkedVSID->Bit(VS_BIT_USE_HW_TRANSFORM), @@ -82,11 +81,6 @@ bool GenerateFragmentShader(const FShaderID &id, const VShaderID *linkedVSID, ch linkedVSID->Bit(VS_BIT_HAS_TEXCOORD) || !linkedVSID->Bit(VS_BIT_USE_HW_TRANSFORM), linkedVSID->Bit(VS_BIT_LIGHTING_ENABLE), linkedVSID->Bit(VS_BIT_VERTEX_RANGE_CULLING)); - linkedLegacyPBRVertexMode2 = LegacyPBRVertexMode2( - linkedVSID->Bit(VS_BIT_USE_HW_TRANSFORM), - linkedVSID->Bit(VS_BIT_HAS_TEXCOORD) || !linkedVSID->Bit(VS_BIT_USE_HW_TRANSFORM), - linkedVSID->Bit(VS_BIT_LIGHTING_ENABLE), - linkedVSID->Bit(VS_BIT_VERTEX_RANGE_CULLING)); } bool forceDepthWritesOff = id.Bit(FS_BIT_DEPTH_TEST_NEVER); bool useDiscardStencilBugWorkaround = id.Bit(FS_BIT_NO_DEPTH_CANNOT_DISCARD_STENCIL) && !forceDepthWritesOff; From 0b6d2f55f5ed52a854aed46c345262c187bf4218 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 9 Oct 2026 04:27:37 +0700 Subject: [PATCH 37/81] Use workflow-provided Java 17 instead of pinned Foojay Java 21 --- gradle/gradle-daemon-jvm.properties | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 gradle/gradle-daemon-jvm.properties diff --git a/gradle/gradle-daemon-jvm.properties b/gradle/gradle-daemon-jvm.properties deleted file mode 100644 index cddc377c999f..000000000000 --- a/gradle/gradle-daemon-jvm.properties +++ /dev/null @@ -1,13 +0,0 @@ -#This file is generated by updateDaemonJvm -toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/536afcd1dff540251f85e5d2c80458cf/redirect -toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/67a0fee3c4236b6397dcbe8575ca2011/redirect -toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/536afcd1dff540251f85e5d2c80458cf/redirect -toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/67a0fee3c4236b6397dcbe8575ca2011/redirect -toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/10fc3bf1ee0001078a473afe6e43cfdb/redirect -toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/9c55677aff3966382f3d853c0959bfb2/redirect -toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/536afcd1dff540251f85e5d2c80458cf/redirect -toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/67a0fee3c4236b6397dcbe8575ca2011/redirect -toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/39846e8427e64a3824c13e399d7d813c/redirect -toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/ac151d55def6b6a9a159dc4cb4642851/redirect -toolchainVendor=JETBRAINS -toolchainVersion=21 From cea71bb20577538d5d3df547aedf4c8eb866ca8b Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 9 Oct 2026 05:00:02 +0700 Subject: [PATCH 38/81] Pass linked vertex shader ID to fragment generation --- GPU/GLES/ShaderManagerGLES.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/GPU/GLES/ShaderManagerGLES.cpp b/GPU/GLES/ShaderManagerGLES.cpp index 6749325cad73..533104cbde1d 100644 --- a/GPU/GLES/ShaderManagerGLES.cpp +++ b/GPU/GLES/ShaderManagerGLES.cpp @@ -629,7 +629,7 @@ Shader *ShaderManagerGLES::CompileFragmentShader(FShaderID FSID, VShaderID VSID) uint64_t uniformMask; std::string errorString; FragmentShaderFlags flags; - if (!GenerateFragmentShader(FSID, codeBuffer_, draw_->GetShaderLanguageDesc(), draw_->GetBugs(), &uniformMask, &flags, &errorString)) { + if (!GenerateFragmentShader(FSID, &VSID, codeBuffer_, draw_->GetShaderLanguageDesc(), draw_->GetBugs(), &uniformMask, &flags, &errorString)) { ERROR_LOG_REPORT(Log::G3D, "FS shader gen error: %s (%s: %s)", errorString.c_str(), "GLES", FSID.ToDebugString().c_str()); return nullptr; } @@ -998,7 +998,16 @@ bool ShaderManagerGLES::LoadCache(File::IOFile &f) { for (size_t &i = pending.fragPos; i < pending.frag.size(); i++) { const FShaderID &id = pending.frag[i]; if (!fsCache_.ContainsKey(id)) { - Shader *fs = CompileFragmentShader(id); + // Fragment generation may depend on the linked vertex shader for legacy GLSL modes. + // Use the first vertex shader linked to this fragment ID in the pending cache. + VShaderID linkedVSID; + for (size_t linkIndex = pending.linkPos; linkIndex < pending.link.size(); ++linkIndex) { + if (pending.link[linkIndex].second == id) { + linkedVSID = pending.link[linkIndex].first; + break; + } + } + Shader *fs = CompileFragmentShader(id, linkedVSID); if (!fs) { // Give up on using the cache - something went wrong. // We'll still keep the shaders we generated so far around. From e4f57080e91f83c87cd297e94d8093b9a8c10559 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 9 Oct 2026 05:23:24 +0700 Subject: [PATCH 39/81] Pass null linked vertex ID to Vulkan fragment generator --- GPU/Vulkan/ShaderManagerVulkan.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GPU/Vulkan/ShaderManagerVulkan.cpp b/GPU/Vulkan/ShaderManagerVulkan.cpp index 926b4e006765..2ea71d2dfcc5 100644 --- a/GPU/Vulkan/ShaderManagerVulkan.cpp +++ b/GPU/Vulkan/ShaderManagerVulkan.cpp @@ -260,7 +260,7 @@ const VulkanFragmentShader *ShaderManagerVulkan::GetFragmentShaderFromID(FShader std::string genErrorString; uint64_t uniformMask = 0; // Not used FragmentShaderFlags flags{}; - bool success = GenerateFragmentShader(FSID, codeBuffer_, compat_, draw_->GetBugs(), &uniformMask, &flags, &genErrorString); + bool success = GenerateFragmentShader(FSID, nullptr, codeBuffer_, compat_, draw_->GetBugs(), &uniformMask, &flags, &genErrorString); _assert_msg_(success, "FS gen error: %s", genErrorString.c_str()); _assert_msg_(strlen(codeBuffer_) < CODE_BUFFER_SIZE, "FS length error: %d", (int)strlen(codeBuffer_)); @@ -457,7 +457,7 @@ bool ShaderManagerVulkan::LoadCache(FILE *f) { std::string genErrorString; uint64_t uniformMask = 0; FragmentShaderFlags flags; - if (!GenerateFragmentShader(id, codeBuffer_, compat_, draw_->GetBugs(), &uniformMask, &flags, &genErrorString)) { + if (!GenerateFragmentShader(id, nullptr, codeBuffer_, compat_, draw_->GetBugs(), &uniformMask, &flags, &genErrorString)) { ERROR_LOG(Log::G3D, "Failed to generate fragment shader during cache load"); // We just ignore this one and carry on. failCount++; From f99cade284fa1dfc6db0363c2f2f6d4f201275c0 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 9 Oct 2026 11:23:52 +0700 Subject: [PATCH 40/81] Fix escaped newlines in Legacy PBR shader comments --- GPU/Common/FragmentShaderGenerator.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index 5e2c8739e82f..b20d6cbfc0a6 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -126,14 +126,14 @@ bool GenerateFragmentShader(const FShaderID &id, const VShaderID *linkedVSID, ch linkedVSID->Bit(VS_BIT_HAS_TEXCOORD) || !linkedVSID->Bit(VS_BIT_USE_HW_TRANSFORM), linkedVSID->Bit(VS_BIT_LIGHTING_ENABLE), linkedVSID->Bit(VS_BIT_VERTEX_RANGE_CULLING)); - p.F("// Used by vertex%016llx.glsl\\n", (unsigned long long)linkedVSID->ToUint64()); - p.F("// Linked vertex shader ID: %016llx\\n", (unsigned long long)linkedVSID->ToUint64()); + p.F("// Used by vertex%016llx.glsl\n", (unsigned long long)linkedVSID->ToUint64()); + p.F("// Linked vertex shader ID: %016llx\n", (unsigned long long)linkedVSID->ToUint64()); if (linkedVSMode1) - p.C("// Legacy PBR vertex mode: 1 (fragment flag 1 is valid)\\n"); + p.C("// Legacy PBR vertex mode: 1 (fragment flag 1 is valid)\n"); else if (linkedVSMode2) - p.C("// Legacy PBR vertex mode: 2 (fragment flag 2; PBR path inactive)\\n"); + p.C("// Legacy PBR vertex mode: 2 (fragment flag 2; PBR path inactive)\n"); else - p.C("// Legacy PBR vertex mode: none (fragment PBR must remain inactive)\\n"); + p.C("// Legacy PBR vertex mode: none (fragment PBR must remain inactive)\n"); } p.ApplySamplerMetadata(arrayTexture ? samplersStereo : samplersMono); From 0e9858f5c97dc19c3a9620b30551fb157d46cefa Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 9 Oct 2026 11:50:14 +0700 Subject: [PATCH 41/81] Fix escaped newlines in Legacy PBR vertex comments --- GPU/Common/VertexShaderGenerator.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GPU/Common/VertexShaderGenerator.cpp b/GPU/Common/VertexShaderGenerator.cpp index 90e3ac69241f..8c7106b181c4 100644 --- a/GPU/Common/VertexShaderGenerator.cpp +++ b/GPU/Common/VertexShaderGenerator.cpp @@ -169,9 +169,9 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag const bool legacyPBRVertexMode2 = LegacyPBRVertexMode2(useHWTransform, hasTexcoord, enableLighting, rangeCulling); if (ShaderLanguageIsOpenGL(compat.shaderLanguage)) { if (legacyPBRVertexMode1) - p.C("// Legacy PBR vertex mode: 1 (exports flag 1 + v_1..v_8)\\n"); + p.C("// Legacy PBR vertex mode: 1 (exports flag 1 + v_1..v_8)\n"); else if (legacyPBRVertexMode2) - p.C("// Legacy PBR vertex mode: 2 (exports flag 2 + v_1..v_8)\\n"); + p.C("// Legacy PBR vertex mode: 2 (exports flag 2 + v_1..v_8)\n"); } const char *minZClipPlaneSuffix = "[0]"; From e773aedc3067075e365c9c4e93fcc880bd328206 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 9 Oct 2026 12:24:01 +0700 Subject: [PATCH 42/81] Avoid Legacy PBR output references in inactive shaders --- GPU/Common/FragmentShaderGenerator.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index b20d6cbfc0a6..add7c2d10729 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -1193,7 +1193,11 @@ bool GenerateFragmentShader(const FShaderID &id, const VShaderID *linkedVSID, ch break; case REPLACE_ALPHA_NO: - WRITE(p, " %s = legacyPBRActive ? legacyPBRColor : v;\n", compat.fragColor0); + if (legacyPBRFragmentBase) { + WRITE(p, " %s = legacyPBRActive ? legacyPBRColor : v;\n", compat.fragColor0); + } else { + WRITE(p, " %s = v;\n", compat.fragColor0); + } break; default: From affa407dd1b4a1b2a43a10f16f176b4fa0d0a54d Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 9 Oct 2026 13:22:45 +0700 Subject: [PATCH 43/81] Declare fragment output before Legacy PBR functions --- GPU/Common/FragmentShaderGenerator.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index add7c2d10729..dc592ac216eb 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -458,7 +458,6 @@ bool GenerateFragmentShader(const FShaderID &id, const VShaderID *linkedVSID, ch if (ShaderLanguageIsOpenGL(compat.shaderLanguage) && legacyPBRFragmentBase) { WRITE(p, "//****** my_varying_fs *********\n"); WriteLegacyPBRVaryingFS(p, shading, compat.varying_fs); - WriteLegacyPBRPrelude(p); } if (!enableFragmentTestCache) { @@ -495,6 +494,10 @@ bool GenerateFragmentShader(const FShaderID &id, const VShaderID *linkedVSID, ch WRITE(p, "%s vec4 fragColor0;\n", qualifierColor0); } } + // Emit PBR functions after fragment output declarations, since PBR__2_0 writes fragColor0. + if (ShaderLanguageIsOpenGL(compat.shaderLanguage) && legacyPBRFragmentBase) { + WriteLegacyPBRPrelude(p); + } } bool hasPackUnorm4x8 = false; From 2986021677d654e5cf6d656dad05dcb49b3afbef Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 9 Oct 2026 13:30:14 +0700 Subject: [PATCH 44/81] Format generated Legacy PBR GLSL for readability --- GPU/Common/LegacyPBR.h | 128 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 115 insertions(+), 13 deletions(-) diff --git a/GPU/Common/LegacyPBR.h b/GPU/Common/LegacyPBR.h index 1d581d1bc4e2..ce32116e7b91 100644 --- a/GPU/Common/LegacyPBR.h +++ b/GPU/Common/LegacyPBR.h @@ -45,20 +45,122 @@ inline void WriteLegacyPBRPrelude(ShaderWriter &p) { p.C("vec3 lightColors[2];\n"); p.C("vec3 camPos = vec3(0.0, 0.0, 50.0);\n"); p.C("const float PI = 3.14159265359;\n"); - p.C("float lightness(float R, float G, float B) { return pow(pow(R / 1.0, 2.2) + pow(G / 0.666666, 2.2) + pow(B / 1.666666, 2.2), 1.0 / 2.2) * 0.547373; }\n"); - p.C("float DistributionGGX(vec3 N, vec3 H, float r) { float a = r*r; float a2 = a*a; float n = max(dot(N,H),0.0); float n2=n*n; float nom=a2; float denom=(n2*(a2-1.0)+1.0); denom=PI*denom*denom; return nom/denom; }\n"); - p.C("float GeometrySchlickGGX(float NdotV, float r) { float k=(r+1.0)*(r+1.0)/8.0; return NdotV/(NdotV*(1.0-k)+k); }\n"); - p.C("float GeometrySmith(vec3 N, vec3 V, vec3 L, float r) { float a=max(dot(N,V),0.0); float b=max(dot(N,L),0.0); return GeometrySchlickGGX(a,r)*GeometrySchlickGGX(b,r); }\n"); - p.C("vec3 fresnelSchlick(float c, vec3 F0) { return F0+(1.0-F0)*pow(clamp(1.0-c,0.0,1.0),5.0); }\n"); + + p.C("float lightness(float R, float G, float B) {\n"); + p.C(" return pow(\n"); + p.C(" pow(R / 1.0, 2.2) +\n"); + p.C(" pow(G / 0.666666, 2.2) +\n"); + p.C(" pow(B / 1.666666, 2.2),\n"); + p.C(" 1.0 / 2.2\n"); + p.C(" ) * 0.547373;\n"); + p.C("}\n"); + + p.C("float DistributionGGX(vec3 N, vec3 H, float r) {\n"); + p.C(" float a = r * r;\n"); + p.C(" float a2 = a * a;\n"); + p.C(" float NdotH = max(dot(N, H), 0.0);\n"); + p.C(" float NdotH2 = NdotH * NdotH;\n"); + p.C(" float numerator = a2;\n"); + p.C(" float denominator = NdotH2 * (a2 - 1.0) + 1.0;\n"); + p.C(" denominator = PI * denominator * denominator;\n"); + p.C(" return numerator / denominator;\n"); + p.C("}\n"); + + p.C("float GeometrySchlickGGX(float NdotV, float r) {\n"); + p.C(" float k = (r + 1.0) * (r + 1.0) / 8.0;\n"); + p.C(" return NdotV / (NdotV * (1.0 - k) + k);\n"); + p.C("}\n"); + + p.C("float GeometrySmith(vec3 N, vec3 V, vec3 L, float r) {\n"); + p.C(" float NdotV = max(dot(N, V), 0.0);\n"); + p.C(" float NdotL = max(dot(N, L), 0.0);\n"); + p.C(" float ggxV = GeometrySchlickGGX(NdotV, r);\n"); + p.C(" float ggxL = GeometrySchlickGGX(NdotL, r);\n"); + p.C(" return ggxV * ggxL;\n"); + p.C("}\n"); + + p.C("vec3 fresnelSchlick(float cosTheta, vec3 F0) {\n"); + p.C(" return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);\n"); + p.C("}\n"); + p.C("void PBR__2_0() {\n"); - p.C(" vec3 N=normalize(v_1.xyz);\n"); - p.C(" vec3 fogcolor=vec3(mix(vec3(dot(u_fogcolor,vec3(0.299,0.587,0.114))),u_fogcolor,0.5));\n"); - p.C(" float l=lightness(u_fogcolor.r,u_fogcolor.g,u_fogcolor.b);\n"); - p.C(" vec3 ld; float ll; float minv; fogcolor += 0.75-l; vec3 fogcolor_inverse=1.0-fogcolor; vec3 diff1,diff2;\n"); - p.C(" if(l<0.15){ minv=0.2; ll=1.5; ld=normalize(v_5.xyz); ld.z=abs(ld.z)*7.0; diff1=fogcolor*ll; diff2=fogcolor_inverse*ll; lightPositions[0].z=v_3.z*1000.0; lightPositions[1].z=v_4.z*1000.0; lightColors[0]=fogcolor_inverse; lightColors[1]=fogcolor; } else { minv=0.1; ll=2.0; ld=normalize(v_4.xyz); ld.z=abs(ld.z)*5.0; diff1=fogcolor_inverse; diff2=fogcolor*ll; lightPositions[0].z=v_4.z*1000.0; lightPositions[1].z=v_3.z*1000.0; lightColors[0]=fogcolor; lightColors[1]=fogcolor_inverse; }\n"); - p.C(" lightPositions[0].x=N.x*1000.0; lightPositions[0].y=N.y*2000.0; lightPositions[1].x=N.x*3500.0; lightPositions[1].y=N.y*5000.0;\n"); - p.C(" vec3 lightDir=normalize(ld*1000.0-v_2.xyz); float diff=max(dot(N,lightDir),0.0); if(diff0.015){ vec3 V=normalize(camPos-v_2.xyz); vec3 F0=vec3(0.04); F0=mix(F0,albedo,metallic); vec3 Lo=vec3(0.0); for(int i=0;i<2;++i){ vec3 L=normalize(lightPositions[i]-v_2.xyz); vec3 H=normalize(V+L); float NDF=DistributionGGX(N,H,roughness); float G=GeometrySmith(N,V,L,roughness); vec3 F=fresnelSchlick(clamp(dot(H,V),0.0,1.0),F0); vec3 numerator=NDF*G*F; float denominator=4.0*max(dot(N,V),0.0)*max(dot(N,L),0.0)+0.0001; vec3 specular=numerator/denominator; vec3 kS=F; vec3 kD=(vec3(1.0)-kS)*(1.0-metallic); float NdotL=max(dot(N,L),0.0); Lo+=(kD*albedo/PI+specular)*lightColors[i]*NdotL; } vec3 color=Lo+albedo; color=color/(color+vec3(1.0)); color*=diffColor; fragColor0=vec4(color,1.0); } else { fragColor0=vec4(albedo*diffColor*0.75,1.0); }\n"); + p.C(" vec3 N = normalize(v_1.xyz);\n"); + p.C(" vec3 fogcolor = vec3(mix(\n"); + p.C(" vec3(dot(u_fogcolor, vec3(0.299, 0.587, 0.114))),\n"); + p.C(" u_fogcolor,\n"); + p.C(" 0.5\n"); + p.C(" ));\n"); + p.C(" float l = lightness(u_fogcolor.r, u_fogcolor.g, u_fogcolor.b);\n"); + p.C(" vec3 ld;\n"); + p.C(" float ll;\n"); + p.C(" float minv;\n"); + p.C(" fogcolor += 0.75 - l;\n"); + p.C(" vec3 fogcolor_inverse = 1.0 - fogcolor;\n"); + p.C(" vec3 diff1;\n"); + p.C(" vec3 diff2;\n"); + + p.C(" if (l < 0.15) {\n"); + p.C(" minv = 0.2;\n"); + p.C(" ll = 1.5;\n"); + p.C(" ld = normalize(v_5.xyz);\n"); + p.C(" ld.z = abs(ld.z) * 7.0;\n"); + p.C(" diff1 = fogcolor * ll;\n"); + p.C(" diff2 = fogcolor_inverse * ll;\n"); + p.C(" lightPositions[0].z = v_3.z * 1000.0;\n"); + p.C(" lightPositions[1].z = v_4.z * 1000.0;\n"); + p.C(" lightColors[0] = fogcolor_inverse;\n"); + p.C(" lightColors[1] = fogcolor;\n"); + p.C(" } else {\n"); + p.C(" minv = 0.1;\n"); + p.C(" ll = 2.0;\n"); + p.C(" ld = normalize(v_4.xyz);\n"); + p.C(" ld.z = abs(ld.z) * 5.0;\n"); + p.C(" diff1 = fogcolor_inverse;\n"); + p.C(" diff2 = fogcolor * ll;\n"); + p.C(" lightPositions[0].z = v_4.z * 1000.0;\n"); + p.C(" lightPositions[1].z = v_3.z * 1000.0;\n"); + p.C(" lightColors[0] = fogcolor;\n"); + p.C(" lightColors[1] = fogcolor_inverse;\n"); + p.C(" }\n"); + + p.C(" lightPositions[0].x = N.x * 1000.0;\n"); + p.C(" lightPositions[0].y = N.y * 2000.0;\n"); + p.C(" lightPositions[1].x = N.x * 3500.0;\n"); + p.C(" lightPositions[1].y = N.y * 5000.0;\n"); + + p.C(" vec3 lightDir = normalize(ld * 1000.0 - v_2.xyz);\n"); + p.C(" float diff = max(dot(N, lightDir), 0.0);\n"); + p.C(" if (diff < minv) {\n"); + p.C(" diff = minv;\n"); + p.C(" }\n"); + p.C(" vec3 diffColor = mix(diff1, diff2, diff);\n"); + + p.C(" if (gl_FragCoord.w > 0.015) {\n"); + p.C(" vec3 V = normalize(camPos - v_2.xyz);\n"); + p.C(" vec3 F0 = vec3(0.04);\n"); + p.C(" F0 = mix(F0, albedo, metallic);\n"); + p.C(" vec3 Lo = vec3(0.0);\n"); + p.C(" for (int i = 0; i < 2; ++i) {\n"); + p.C(" vec3 L = normalize(lightPositions[i] - v_2.xyz);\n"); + p.C(" vec3 H = normalize(V + L);\n"); + p.C(" float NDF = DistributionGGX(N, H, roughness);\n"); + p.C(" float G = GeometrySmith(N, V, L, roughness);\n"); + p.C(" vec3 F = fresnelSchlick(clamp(dot(H, V), 0.0, 1.0), F0);\n"); + p.C(" vec3 numerator = NDF * G * F;\n"); + p.C(" float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.0001;\n"); + p.C(" vec3 specular = numerator / denominator;\n"); + p.C(" vec3 kS = F;\n"); + p.C(" vec3 kD = (vec3(1.0) - kS) * (1.0 - metallic);\n"); + p.C(" float NdotL = max(dot(N, L), 0.0);\n"); + p.C(" Lo += (kD * albedo / PI + specular) * lightColors[i] * NdotL;\n"); + p.C(" }\n"); + p.C(" vec3 color = Lo + albedo;\n"); + p.C(" color = color / (color + vec3(1.0));\n"); + p.C(" color *= diffColor;\n"); + p.C(" fragColor0 = vec4(color, 1.0);\n"); + p.C(" } else {\n"); + p.C(" fragColor0 = vec4(albedo * diffColor * 0.75, 1.0);\n"); + p.C(" }\n"); p.C("}\n"); } From 9a9b40cf94a9128ea5ff74d5fe3e1cf7f03625ba Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 23 Oct 2026 16:00:45 +0700 Subject: [PATCH 45/81] Implement legacy PBR texture atlas sampling --- GPU/Common/FragmentShaderGenerator.cpp | 34 +++++++++++++++++--------- GPU/Common/LegacyPBR.h | 6 +++-- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index dc592ac216eb..25f510030bb5 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -916,17 +916,29 @@ bool GenerateFragmentShader(const FShaderID &id, const VShaderID *linkedVSID, ch if (ShaderLanguageIsOpenGL(compat.shaderLanguage) && legacyPBRFragmentBase) { WRITE(p, " if (flag == 1) {\n"); if (legacyPBRFragmentAlpha) { - WRITE(p, " vec4 tex_color = t;\n"); - WRITE(p, " if (tex_color.a > 0.15) {\n"); - WRITE(p, " albedo = tex_color.rgb;\n"); - WRITE(p, " roughness = 1.0 - (t.a - roughness);\n"); - WRITE(p, " PBR__2_0();\n"); - WRITE(p, " legacyPBRColor = fragColor0;\n"); - WRITE(p, " legacyPBRActive = true;\n"); - WRITE(p, " } else {\n"); - WRITE(p, " legacyPBRColor = vec4(tex_color.rgb, 1.0);\n"); - WRITE(p, " legacyPBRActive = true;\n"); - WRITE(p, " }\n"); + // RGBA legacy-PBR textures use a four-cell atlas: + // base color (top-left), tangent-space normal (top-right), + // material selector in alpha (bottom-left), and four sphere maps + // arranged as a 2x2 grid inside the bottom-right quadrant. + WRITE(p, " vec2 legacyAtlasUV = fract(v_texcoord.xy);\n"); + WRITE(p, " vec4 legacyBaseColor = %s(tex, legacyAtlasUV * 0.5);\n", compat.texture); + WRITE(p, " vec3 legacyNormalMap = %s(tex, legacyAtlasUV * 0.5 + vec2(0.5, 0.0)).xyz * 2.0 - 1.0;\n", compat.texture); + WRITE(p, " float legacyMaterialID = %s(tex, legacyAtlasUV * 0.5 + vec2(0.0, 0.5)).a;\n", compat.texture); + WRITE(p, " vec3 legacyGeometricNormal = normalize(v_1.xyz);\n"); + WRITE(p, " vec3 legacyTangentAxis = abs(legacyGeometricNormal.y) < 0.999 ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0);\n"); + WRITE(p, " vec3 legacyTangent = normalize(cross(legacyTangentAxis, legacyGeometricNormal));\n"); + WRITE(p, " vec3 legacyBitangent = normalize(cross(legacyGeometricNormal, legacyTangent));\n"); + WRITE(p, " legacyPBRNormal = normalize(mat3(legacyTangent, legacyBitangent, legacyGeometricNormal) * legacyNormalMap);\n"); + WRITE(p, " vec3 legacyViewDir = normalize(camPos - v_2.xyz);\n"); + WRITE(p, " vec3 legacyReflection = reflect(-legacyViewDir, legacyPBRNormal);\n"); + WRITE(p, " vec2 legacySphereUV = legacyReflection.xy / (2.0 * sqrt(max(legacyReflection.z + 1.0, 0.0001))) + 0.5;\n"); + WRITE(p, " vec2 legacySphereCell = legacyMaterialID < 0.25 ? vec2(0.0, 0.0) : (legacyMaterialID < 0.5 ? vec2(1.0, 0.0) : (legacyMaterialID < 0.75 ? vec2(0.0, 1.0) : vec2(1.0, 1.0)));\n"); + WRITE(p, " vec2 legacySphereAtlasUV = vec2(0.5, 0.5) + legacySphereCell * 0.25 + clamp(legacySphereUV, 0.0, 1.0) * 0.25;\n"); + WRITE(p, " legacyPBRMaterialSphere = %s(tex, legacySphereAtlasUV).rgb;\n", compat.texture); + WRITE(p, " albedo = legacyBaseColor.rgb;\n"); + WRITE(p, " PBR__2_0();\n"); + WRITE(p, " legacyPBRColor = fragColor0;\n"); + WRITE(p, " legacyPBRActive = true;\n"); } else { WRITE(p, " albedo = t.xyz;\n"); WRITE(p, " PBR__2_0();\n"); diff --git a/GPU/Common/LegacyPBR.h b/GPU/Common/LegacyPBR.h index ce32116e7b91..8d3c3cca8dbd 100644 --- a/GPU/Common/LegacyPBR.h +++ b/GPU/Common/LegacyPBR.h @@ -39,6 +39,8 @@ inline void WriteLegacyPBRVaryingFS(ShaderWriter &p, const char *shading, const inline void WriteLegacyPBRPrelude(ShaderWriter &p) { p.C("vec3 albedo;\n"); + p.C("vec3 legacyPBRNormal = vec3(0.0, 0.0, 1.0);\n"); + p.C("vec3 legacyPBRMaterialSphere = vec3(1.0);\n"); p.C("float metallic = 0.5;\n"); p.C("float roughness = 0.15;\n"); p.C("vec3 lightPositions[2];\n"); @@ -84,7 +86,7 @@ inline void WriteLegacyPBRPrelude(ShaderWriter &p) { p.C("}\n"); p.C("void PBR__2_0() {\n"); - p.C(" vec3 N = normalize(v_1.xyz);\n"); + p.C(" vec3 N = normalize(legacyPBRNormal);\n"); p.C(" vec3 fogcolor = vec3(mix(\n"); p.C(" vec3(dot(u_fogcolor, vec3(0.299, 0.587, 0.114))),\n"); p.C(" u_fogcolor,\n"); @@ -148,7 +150,7 @@ inline void WriteLegacyPBRPrelude(ShaderWriter &p) { p.C(" vec3 F = fresnelSchlick(clamp(dot(H, V), 0.0, 1.0), F0);\n"); p.C(" vec3 numerator = NDF * G * F;\n"); p.C(" float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.0001;\n"); - p.C(" vec3 specular = numerator / denominator;\n"); + p.C(" vec3 specular = (numerator / denominator) * legacyPBRMaterialSphere;\n"); p.C(" vec3 kS = F;\n"); p.C(" vec3 kD = (vec3(1.0) - kS) * (1.0 - metallic);\n"); p.C(" float NdotL = max(dot(N, L), 0.0);\n"); From 0943940bdd5168f880db695c221bea8c53af3bfd Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 23 Oct 2026 16:10:20 +0700 Subject: [PATCH 46/81] Match legacy GLSL atlas material sampling --- GPU/Common/FragmentShaderGenerator.cpp | 51 ++++++++++++++------------ 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index 25f510030bb5..d1385f7e5d3c 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -914,38 +914,41 @@ bool GenerateFragmentShader(const FShaderID &id, const VShaderID *linkedVSID, ch WRITE(p, " vec4 legacyPBRColor = vec4(0.0);\n"); if (ShaderLanguageIsOpenGL(compat.shaderLanguage) && legacyPBRFragmentBase) { - WRITE(p, " if (flag == 1) {\n"); if (legacyPBRFragmentAlpha) { - // RGBA legacy-PBR textures use a four-cell atlas: - // base color (top-left), tangent-space normal (top-right), - // material selector in alpha (bottom-left), and four sphere maps - // arranged as a 2x2 grid inside the bottom-right quadrant. - WRITE(p, " vec2 legacyAtlasUV = fract(v_texcoord.xy);\n"); - WRITE(p, " vec4 legacyBaseColor = %s(tex, legacyAtlasUV * 0.5);\n", compat.texture); - WRITE(p, " vec3 legacyNormalMap = %s(tex, legacyAtlasUV * 0.5 + vec2(0.5, 0.0)).xyz * 2.0 - 1.0;\n", compat.texture); - WRITE(p, " float legacyMaterialID = %s(tex, legacyAtlasUV * 0.5 + vec2(0.0, 0.5)).a;\n", compat.texture); - WRITE(p, " vec3 legacyGeometricNormal = normalize(v_1.xyz);\n"); - WRITE(p, " vec3 legacyTangentAxis = abs(legacyGeometricNormal.y) < 0.999 ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0);\n"); - WRITE(p, " vec3 legacyTangent = normalize(cross(legacyTangentAxis, legacyGeometricNormal));\n"); - WRITE(p, " vec3 legacyBitangent = normalize(cross(legacyGeometricNormal, legacyTangent));\n"); - WRITE(p, " legacyPBRNormal = normalize(mat3(legacyTangent, legacyBitangent, legacyGeometricNormal) * legacyNormalMap);\n"); - WRITE(p, " vec3 legacyViewDir = normalize(camPos - v_2.xyz);\n"); - WRITE(p, " vec3 legacyReflection = reflect(-legacyViewDir, legacyPBRNormal);\n"); - WRITE(p, " vec2 legacySphereUV = legacyReflection.xy / (2.0 * sqrt(max(legacyReflection.z + 1.0, 0.0001))) + 0.5;\n"); - WRITE(p, " vec2 legacySphereCell = legacyMaterialID < 0.25 ? vec2(0.0, 0.0) : (legacyMaterialID < 0.5 ? vec2(1.0, 0.0) : (legacyMaterialID < 0.75 ? vec2(0.0, 1.0) : vec2(1.0, 1.0)));\n"); - WRITE(p, " vec2 legacySphereAtlasUV = vec2(0.5, 0.5) + legacySphereCell * 0.25 + clamp(legacySphereUV, 0.0, 1.0) * 0.25;\n"); - WRITE(p, " legacyPBRMaterialSphere = %s(tex, legacySphereAtlasUV).rgb;\n", compat.texture); - WRITE(p, " albedo = legacyBaseColor.rgb;\n"); - WRITE(p, " PBR__2_0();\n"); - WRITE(p, " legacyPBRColor = fragColor0;\n"); + // Match the legacy GLSL atlas layout and sampling math: + // base color (top-left), normal data (top-right), emissive/material + // selector (bottom-left), and four sphere maps (bottom-right). + WRITE(p, " if (flag == 1) {\n"); + WRITE(p, " vec4 legacyBaseColor = %s(tex, v_texcoord.xy * 0.5);\n", compat.texture); + WRITE(p, " vec3 legacyNormalColor = %s(tex, vec2(0.5, 0.0) + v_texcoord.xy * 0.5).rgb;\n", compat.texture); + WRITE(p, " vec4 legacyEmissive = %s(tex, vec2(0.0, 0.5) + v_texcoord.xy * 0.5);\n", compat.texture); + WRITE(p, " vec3 legacyN = v_1.xyz + legacyNormalColor * 0.5;\n"); + WRITE(p, " legacyN = normalize(legacyN);\n"); + WRITE(p, " vec3 legacyR = reflect(-v_2.xyz, legacyN);\n"); + WRITE(p, " float legacyM = 2.0 * sqrt(max(dot(legacyR.xy, legacyR.xy) + (legacyR.z + 1.0) * legacyR.z, 0.000001));\n"); + WRITE(p, " vec2 legacySphereBaseUV = vec2(legacyR.x, legacyR.y) / legacyM * 0.25;\n"); + WRITE(p, " float legacyFogLightness = lightness(u_fogcolor.r, u_fogcolor.g, u_fogcolor.b);\n"); + WRITE(p, " vec3 legacyFogColor = vec3(mix(vec3(dot(u_fogcolor, vec3(0.299, 0.587, 0.114))), u_fogcolor, 0.6));\n"); + WRITE(p, " legacyFogColor += legacyFogLightness < 0.15 ? 0.6 - legacyFogLightness : 0.6;\n"); + WRITE(p, " vec2 legacySphereOffset = vec2(0.75, 0.75);\n"); + WRITE(p, " if (legacyEmissive.a >= 0.001 && legacyEmissive.a < 0.1) legacySphereOffset = vec2(0.5, 0.5);\n"); + WRITE(p, " else if (legacyEmissive.a >= 0.0999 && legacyEmissive.a < 0.2) legacySphereOffset = vec2(0.75, 0.5);\n"); + WRITE(p, " else if (legacyEmissive.a >= 0.1999 && legacyEmissive.a < 0.3) legacySphereOffset = vec2(0.5, 0.75);\n"); + WRITE(p, " vec3 legacySphere = %s(tex, legacySphereOffset + legacySphereBaseUV).rgb;\n", compat.texture); + WRITE(p, " legacyPBRColor = vec4(legacyBaseColor.rgb * legacySphere * legacyFogColor + legacyEmissive.rgb, v.a);\n"); + WRITE(p, " legacyPBRActive = true;\n"); + WRITE(p, " } else if (flag == 2) {\n"); + WRITE(p, " legacyPBRColor = %s(tex, v_texcoord.xy * 0.5);\n", compat.texture); WRITE(p, " legacyPBRActive = true;\n"); + WRITE(p, " }\n"); } else { + WRITE(p, " if (flag == 1) {\n"); WRITE(p, " albedo = t.xyz;\n"); WRITE(p, " PBR__2_0();\n"); WRITE(p, " legacyPBRColor = fragColor0;\n"); WRITE(p, " legacyPBRActive = true;\n"); + WRITE(p, " }\n"); } - WRITE(p, " }\n"); } if (enableFog) { From e4a2261ef4733183a6ab63190b6d67c9c9338493 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 23 Oct 2026 16:11:02 +0700 Subject: [PATCH 47/81] Match legacy sphere-map coordinates --- GPU/Common/FragmentShaderGenerator.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index d1385f7e5d3c..23000749bcc7 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -925,8 +925,8 @@ bool GenerateFragmentShader(const FShaderID &id, const VShaderID *linkedVSID, ch WRITE(p, " vec3 legacyN = v_1.xyz + legacyNormalColor * 0.5;\n"); WRITE(p, " legacyN = normalize(legacyN);\n"); WRITE(p, " vec3 legacyR = reflect(-v_2.xyz, legacyN);\n"); - WRITE(p, " float legacyM = 2.0 * sqrt(max(dot(legacyR.xy, legacyR.xy) + (legacyR.z + 1.0) * legacyR.z, 0.000001));\n"); - WRITE(p, " vec2 legacySphereBaseUV = vec2(legacyR.x, legacyR.y) / legacyM * 0.25;\n"); + WRITE(p, " float legacyM = 2.0 * sqrt(max(legacyR.x * legacyR.x + legacyR.y * legacyR.y + (legacyR.z + 1.0) * legacyR.z, 0.000001));\n"); + WRITE(p, " vec2 legacySphereBaseUV = (vec2(legacyR.x, legacyR.y) / legacyM + 0.5) * 0.25;\n"); WRITE(p, " float legacyFogLightness = lightness(u_fogcolor.r, u_fogcolor.g, u_fogcolor.b);\n"); WRITE(p, " vec3 legacyFogColor = vec3(mix(vec3(dot(u_fogcolor, vec3(0.299, 0.587, 0.114))), u_fogcolor, 0.6));\n"); WRITE(p, " legacyFogColor += legacyFogLightness < 0.15 ? 0.6 - legacyFogLightness : 0.6;\n"); From 8e467eecab1a0d9e6ccb0ee5c2ef0ecb3160e938 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 23 Oct 2026 16:31:33 +0700 Subject: [PATCH 48/81] Restore legacy atlas shader selection --- GPU/Common/FragmentShaderGenerator.cpp | 45 ++++++++++++-------------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index 23000749bcc7..ddea983893c5 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -70,10 +70,10 @@ bool GenerateFragmentShader(const FShaderID &id, const VShaderID *linkedVSID, ch } bool texture3D = id.Bit(FS_BIT_3D_TEXTURE); - bool doTextureAlpha = id.Bit(FS_BIT_LEGACY_PBR_ALPHA); - bool arrayTexture = id.Bit(FS_BIT_SAMPLE_ARRAY_TEXTURE); + bool arrayTexture = id.Bit(FS_BIT_SAMPLE_ARRAY_TEXTURE); bool linkedLegacyPBRVertexMode1 = false; + bool linkedLegacyPBRVertexMode2 = false; if (ShaderLanguageIsOpenGL(compat.shaderLanguage) && linkedVSID) { linkedLegacyPBRVertexMode1 = LegacyPBRVertexMode1( linkedVSID->Bit(VS_BIT_USE_HW_TRANSFORM), @@ -81,6 +81,11 @@ bool GenerateFragmentShader(const FShaderID &id, const VShaderID *linkedVSID, ch linkedVSID->Bit(VS_BIT_HAS_TEXCOORD) || !linkedVSID->Bit(VS_BIT_USE_HW_TRANSFORM), linkedVSID->Bit(VS_BIT_LIGHTING_ENABLE), linkedVSID->Bit(VS_BIT_VERTEX_RANGE_CULLING)); + linkedLegacyPBRVertexMode2 = LegacyPBRVertexMode2( + linkedVSID->Bit(VS_BIT_USE_HW_TRANSFORM), + linkedVSID->Bit(VS_BIT_HAS_TEXCOORD) || !linkedVSID->Bit(VS_BIT_USE_HW_TRANSFORM), + linkedVSID->Bit(VS_BIT_LIGHTING_ENABLE), + linkedVSID->Bit(VS_BIT_VERTEX_RANGE_CULLING)); } bool forceDepthWritesOff = id.Bit(FS_BIT_DEPTH_TEST_NEVER); bool useDiscardStencilBugWorkaround = id.Bit(FS_BIT_NO_DEPTH_CANNOT_DISCARD_STENCIL) && !forceDepthWritesOff; @@ -143,10 +148,9 @@ bool GenerateFragmentShader(const FShaderID &id, const VShaderID *linkedVSID, ch bool enableFog = id.Bit(FS_BIT_ENABLE_FOG); const bool legacyPBRFragmentBase = ShaderLanguageIsOpenGL(compat.shaderLanguage) && - linkedLegacyPBRVertexMode1 && + (linkedLegacyPBRVertexMode1 || linkedLegacyPBRVertexMode2) && doTexture && enableFog; - const bool legacyPBRFragmentAlpha = legacyPBRFragmentBase && doTextureAlpha; bool enableAlphaTest = id.Bit(FS_BIT_ALPHA_TEST); bool alphaTestAgainstZero = id.Bit(FS_BIT_ALPHA_AGAINST_ZERO); @@ -909,16 +913,15 @@ bool GenerateFragmentShader(const FShaderID &id, const VShaderID *linkedVSID, ch WRITE(p, " vec4 v = v_color0%s;\n", secondary); } - // Legacy PBR is generated before the normal post-processing. Preserve its result for the final output. + // Legacy PBR is generated before post-processing so alpha/color tests and blending use its result. WRITE(p, " bool legacyPBRActive = false;\n"); WRITE(p, " vec4 legacyPBRColor = vec4(0.0);\n"); if (ShaderLanguageIsOpenGL(compat.shaderLanguage) && legacyPBRFragmentBase) { - if (legacyPBRFragmentAlpha) { - // Match the legacy GLSL atlas layout and sampling math: + // Match the legacy GLSL atlas layout and sampling math: // base color (top-left), normal data (top-right), emissive/material // selector (bottom-left), and four sphere maps (bottom-right). - WRITE(p, " if (flag == 1) {\n"); + WRITE(p, " if (flag == 1) {\n"); WRITE(p, " vec4 legacyBaseColor = %s(tex, v_texcoord.xy * 0.5);\n", compat.texture); WRITE(p, " vec3 legacyNormalColor = %s(tex, vec2(0.5, 0.0) + v_texcoord.xy * 0.5).rgb;\n", compat.texture); WRITE(p, " vec4 legacyEmissive = %s(tex, vec2(0.0, 0.5) + v_texcoord.xy * 0.5);\n", compat.texture); @@ -935,25 +938,21 @@ bool GenerateFragmentShader(const FShaderID &id, const VShaderID *linkedVSID, ch WRITE(p, " else if (legacyEmissive.a >= 0.0999 && legacyEmissive.a < 0.2) legacySphereOffset = vec2(0.75, 0.5);\n"); WRITE(p, " else if (legacyEmissive.a >= 0.1999 && legacyEmissive.a < 0.3) legacySphereOffset = vec2(0.5, 0.75);\n"); WRITE(p, " vec3 legacySphere = %s(tex, legacySphereOffset + legacySphereBaseUV).rgb;\n", compat.texture); - WRITE(p, " legacyPBRColor = vec4(legacyBaseColor.rgb * legacySphere * legacyFogColor + legacyEmissive.rgb, v.a);\n"); + WRITE(p, " legacyPBRColor = vec4(legacyBaseColor.rgb * legacySphere * legacyFogColor + legacyEmissive.rgb, legacyBaseColor.a);\n"); WRITE(p, " legacyPBRActive = true;\n"); WRITE(p, " } else if (flag == 2) {\n"); WRITE(p, " legacyPBRColor = %s(tex, v_texcoord.xy * 0.5);\n", compat.texture); WRITE(p, " legacyPBRActive = true;\n"); - WRITE(p, " }\n"); - } else { - WRITE(p, " if (flag == 1) {\n"); - WRITE(p, " albedo = t.xyz;\n"); - WRITE(p, " PBR__2_0();\n"); - WRITE(p, " legacyPBRColor = fragColor0;\n"); - WRITE(p, " legacyPBRActive = true;\n"); - WRITE(p, " }\n"); - } + WRITE(p, " }\n"); + // Use the selected legacy texture result for alpha/color tests and blending. + WRITE(p, " if (legacyPBRActive) v = legacyPBRColor;\n"); } if (enableFog) { - WRITE(p, " float fogCoef = clamp(v_fogdepth, 0.0, 1.0);\n"); - WRITE(p, " v.rgb = mix(u_fogcolor, v.rgb, fogCoef);\n"); + WRITE(p, " if (!legacyPBRActive) {\n"); + WRITE(p, " float fogCoef = clamp(v_fogdepth, 0.0, 1.0);\n"); + WRITE(p, " v.rgb = mix(u_fogcolor, v.rgb, fogCoef);\n"); + WRITE(p, " }\n"); } // Texture access is at half texels [0.5/256, 255.5/256], but colors are normalized [0, 255]. @@ -1211,11 +1210,7 @@ bool GenerateFragmentShader(const FShaderID &id, const VShaderID *linkedVSID, ch break; case REPLACE_ALPHA_NO: - if (legacyPBRFragmentBase) { - WRITE(p, " %s = legacyPBRActive ? legacyPBRColor : v;\n", compat.fragColor0); - } else { - WRITE(p, " %s = v;\n", compat.fragColor0); - } + WRITE(p, " %s = v;\n", compat.fragColor0); break; default: From d9a9a1420f53285cdcac961b33a4dc10e12e40b4 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 23 Oct 2026 16:32:18 +0700 Subject: [PATCH 49/81] Preserve legacy atlas alpha behavior --- GPU/Common/FragmentShaderGenerator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index ddea983893c5..5a3300d4ef56 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -938,7 +938,7 @@ bool GenerateFragmentShader(const FShaderID &id, const VShaderID *linkedVSID, ch WRITE(p, " else if (legacyEmissive.a >= 0.0999 && legacyEmissive.a < 0.2) legacySphereOffset = vec2(0.75, 0.5);\n"); WRITE(p, " else if (legacyEmissive.a >= 0.1999 && legacyEmissive.a < 0.3) legacySphereOffset = vec2(0.5, 0.75);\n"); WRITE(p, " vec3 legacySphere = %s(tex, legacySphereOffset + legacySphereBaseUV).rgb;\n", compat.texture); - WRITE(p, " legacyPBRColor = vec4(legacyBaseColor.rgb * legacySphere * legacyFogColor + legacyEmissive.rgb, legacyBaseColor.a);\n"); + WRITE(p, " legacyPBRColor = vec4(legacyBaseColor.rgb * legacySphere * legacyFogColor + legacyEmissive.rgb, v.a);\n"); WRITE(p, " legacyPBRActive = true;\n"); WRITE(p, " } else if (flag == 2) {\n"); WRITE(p, " legacyPBRColor = %s(tex, v_texcoord.xy * 0.5);\n", compat.texture); From 6ee483ec2ac93a57458c92d96fca46ab0b4b045f Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 23 Oct 2026 16:32:43 +0700 Subject: [PATCH 50/81] Preserve legacy atlas alpha-test behavior --- GPU/Common/FragmentShaderGenerator.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index 5a3300d4ef56..825868fa3c14 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -970,6 +970,8 @@ bool GenerateFragmentShader(const FShaderID &id, const VShaderID *linkedVSID, ch const char *discardStatement = testForceToZero ? "v.a = 0.0;" : "DISCARD;"; if (enableAlphaTest) { *fragmentShaderFlags |= FragmentShaderFlags::USES_DISCARD; + if (legacyPBRFragmentBase) + WRITE(p, " if (!(legacyPBRActive && flag == 1)) {\\n"); if (alphaTestAgainstZero) { // When testing against 0 (extremely common), we can avoid some math. @@ -1005,6 +1007,8 @@ bool GenerateFragmentShader(const FShaderID &id, const VShaderID *linkedVSID, ch WRITE(p, " %s\n", discardStatement); } } + if (legacyPBRFragmentBase) + WRITE(p, " }\\n"); } if (enableColorTest) { From 17945e3ed508605af6818fc46bf19a8febcb6845 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 23 Oct 2026 16:32:57 +0700 Subject: [PATCH 51/81] Preserve legacy atlas alpha-test behavior --- GPU/Common/FragmentShaderGenerator.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index 825868fa3c14..1d3022d5bff5 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -971,7 +971,7 @@ bool GenerateFragmentShader(const FShaderID &id, const VShaderID *linkedVSID, ch if (enableAlphaTest) { *fragmentShaderFlags |= FragmentShaderFlags::USES_DISCARD; if (legacyPBRFragmentBase) - WRITE(p, " if (!(legacyPBRActive && flag == 1)) {\\n"); + WRITE(p, " if (!(legacyPBRActive && flag == 1)) {\n"); if (alphaTestAgainstZero) { // When testing against 0 (extremely common), we can avoid some math. @@ -1008,7 +1008,7 @@ bool GenerateFragmentShader(const FShaderID &id, const VShaderID *linkedVSID, ch } } if (legacyPBRFragmentBase) - WRITE(p, " }\\n"); + WRITE(p, " }\n"); } if (enableColorTest) { From 26b15f69c1f75170d2ba08b5a7367e7000c87ee9 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 23 Oct 2026 16:33:32 +0700 Subject: [PATCH 52/81] Remove unused legacy atlas shader bit --- GPU/Common/ShaderId.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/GPU/Common/ShaderId.h b/GPU/Common/ShaderId.h index 73f56434d382..bb9a322ec512 100644 --- a/GPU/Common/ShaderId.h +++ b/GPU/Common/ShaderId.h @@ -110,8 +110,7 @@ enum FShaderBit : uint8_t { FS_BIT_USE_FRAMEBUFFER_FETCH = 59, FS_BIT_DEPTH_TEST_NEVER = 60, // Only used on Mali. Set when depth == NEVER. We forcibly avoid writing to depth in this case, since it crashes the driver. FS_BIT_SAMPLE_ARRAY_TEXTURE = 61, // For multiview, framebuffers are array textures and we need to sample the two layers correctly. - FS_BIT_LEGACY_PBR_ALPHA = 62, - // Free bit: 63 + // Free bits: 62-63 }; static inline FShaderBit operator +(FShaderBit bit, int i) { From 3361e0fb39d5bcc1ee5ba3d9bc118d11ab060d60 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 23 Oct 2026 16:33:50 +0700 Subject: [PATCH 53/81] Clean up legacy atlas shader generator --- GPU/Common/FragmentShaderGenerator.cpp | 46 +++++++++++++------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index 1d3022d5bff5..ca7ac95ff802 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -919,30 +919,30 @@ bool GenerateFragmentShader(const FShaderID &id, const VShaderID *linkedVSID, ch if (ShaderLanguageIsOpenGL(compat.shaderLanguage) && legacyPBRFragmentBase) { // Match the legacy GLSL atlas layout and sampling math: - // base color (top-left), normal data (top-right), emissive/material - // selector (bottom-left), and four sphere maps (bottom-right). + // base color (top-left), normal data (top-right), emissive/material + // selector (bottom-left), and four sphere maps (bottom-right). WRITE(p, " if (flag == 1) {\n"); - WRITE(p, " vec4 legacyBaseColor = %s(tex, v_texcoord.xy * 0.5);\n", compat.texture); - WRITE(p, " vec3 legacyNormalColor = %s(tex, vec2(0.5, 0.0) + v_texcoord.xy * 0.5).rgb;\n", compat.texture); - WRITE(p, " vec4 legacyEmissive = %s(tex, vec2(0.0, 0.5) + v_texcoord.xy * 0.5);\n", compat.texture); - WRITE(p, " vec3 legacyN = v_1.xyz + legacyNormalColor * 0.5;\n"); - WRITE(p, " legacyN = normalize(legacyN);\n"); - WRITE(p, " vec3 legacyR = reflect(-v_2.xyz, legacyN);\n"); - WRITE(p, " float legacyM = 2.0 * sqrt(max(legacyR.x * legacyR.x + legacyR.y * legacyR.y + (legacyR.z + 1.0) * legacyR.z, 0.000001));\n"); - WRITE(p, " vec2 legacySphereBaseUV = (vec2(legacyR.x, legacyR.y) / legacyM + 0.5) * 0.25;\n"); - WRITE(p, " float legacyFogLightness = lightness(u_fogcolor.r, u_fogcolor.g, u_fogcolor.b);\n"); - WRITE(p, " vec3 legacyFogColor = vec3(mix(vec3(dot(u_fogcolor, vec3(0.299, 0.587, 0.114))), u_fogcolor, 0.6));\n"); - WRITE(p, " legacyFogColor += legacyFogLightness < 0.15 ? 0.6 - legacyFogLightness : 0.6;\n"); - WRITE(p, " vec2 legacySphereOffset = vec2(0.75, 0.75);\n"); - WRITE(p, " if (legacyEmissive.a >= 0.001 && legacyEmissive.a < 0.1) legacySphereOffset = vec2(0.5, 0.5);\n"); - WRITE(p, " else if (legacyEmissive.a >= 0.0999 && legacyEmissive.a < 0.2) legacySphereOffset = vec2(0.75, 0.5);\n"); - WRITE(p, " else if (legacyEmissive.a >= 0.1999 && legacyEmissive.a < 0.3) legacySphereOffset = vec2(0.5, 0.75);\n"); - WRITE(p, " vec3 legacySphere = %s(tex, legacySphereOffset + legacySphereBaseUV).rgb;\n", compat.texture); - WRITE(p, " legacyPBRColor = vec4(legacyBaseColor.rgb * legacySphere * legacyFogColor + legacyEmissive.rgb, v.a);\n"); - WRITE(p, " legacyPBRActive = true;\n"); - WRITE(p, " } else if (flag == 2) {\n"); - WRITE(p, " legacyPBRColor = %s(tex, v_texcoord.xy * 0.5);\n", compat.texture); - WRITE(p, " legacyPBRActive = true;\n"); + WRITE(p, " vec4 legacyBaseColor = %s(tex, v_texcoord.xy * 0.5);\n", compat.texture); + WRITE(p, " vec3 legacyNormalColor = %s(tex, vec2(0.5, 0.0) + v_texcoord.xy * 0.5).rgb;\n", compat.texture); + WRITE(p, " vec4 legacyEmissive = %s(tex, vec2(0.0, 0.5) + v_texcoord.xy * 0.5);\n", compat.texture); + WRITE(p, " vec3 legacyN = v_1.xyz + legacyNormalColor * 0.5;\n"); + WRITE(p, " legacyN = normalize(legacyN);\n"); + WRITE(p, " vec3 legacyR = reflect(-v_2.xyz, legacyN);\n"); + WRITE(p, " float legacyM = 2.0 * sqrt(max(legacyR.x * legacyR.x + legacyR.y * legacyR.y + (legacyR.z + 1.0) * legacyR.z, 0.000001));\n"); + WRITE(p, " vec2 legacySphereBaseUV = (vec2(legacyR.x, legacyR.y) / legacyM + 0.5) * 0.25;\n"); + WRITE(p, " float legacyFogLightness = lightness(u_fogcolor.r, u_fogcolor.g, u_fogcolor.b);\n"); + WRITE(p, " vec3 legacyFogColor = vec3(mix(vec3(dot(u_fogcolor, vec3(0.299, 0.587, 0.114))), u_fogcolor, 0.6));\n"); + WRITE(p, " legacyFogColor += legacyFogLightness < 0.15 ? 0.6 - legacyFogLightness : 0.6;\n"); + WRITE(p, " vec2 legacySphereOffset = vec2(0.75, 0.75);\n"); + WRITE(p, " if (legacyEmissive.a >= 0.001 && legacyEmissive.a < 0.1) legacySphereOffset = vec2(0.5, 0.5);\n"); + WRITE(p, " else if (legacyEmissive.a >= 0.0999 && legacyEmissive.a < 0.2) legacySphereOffset = vec2(0.75, 0.5);\n"); + WRITE(p, " else if (legacyEmissive.a >= 0.1999 && legacyEmissive.a < 0.3) legacySphereOffset = vec2(0.5, 0.75);\n"); + WRITE(p, " vec3 legacySphere = %s(tex, legacySphereOffset + legacySphereBaseUV).rgb;\n", compat.texture); + WRITE(p, " legacyPBRColor = vec4(legacyBaseColor.rgb * legacySphere * legacyFogColor + legacyEmissive.rgb, v.a);\n"); + WRITE(p, " legacyPBRActive = true;\n"); + WRITE(p, " } else if (flag == 2) {\n"); + WRITE(p, " legacyPBRColor = %s(tex, v_texcoord.xy * 0.5);\n", compat.texture); + WRITE(p, " legacyPBRActive = true;\n"); WRITE(p, " }\n"); // Use the selected legacy texture result for alpha/color tests and blending. WRITE(p, " if (legacyPBRActive) v = legacyPBRColor;\n"); From 03278ada38dbe23d4f0518e3af274a10ccda1180 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 23 Oct 2026 16:53:51 +0700 Subject: [PATCH 54/81] Match legacy mode 2 vertex requirements --- GPU/Common/LegacyPBR.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/GPU/Common/LegacyPBR.h b/GPU/Common/LegacyPBR.h index 8d3c3cca8dbd..1e2e37192db3 100644 --- a/GPU/Common/LegacyPBR.h +++ b/GPU/Common/LegacyPBR.h @@ -16,8 +16,11 @@ inline bool LegacyPBRVertexMode1(bool useHWTransform, bool hasNormal, bool hasTe return useHWTransform && hasNormal && hasTexcoord && enableLighting && vertexRangeCulling; } -inline bool LegacyPBRVertexMode2(bool useHWTransform, bool hasTexcoord, bool enableLighting, bool vertexRangeCulling) { - return useHWTransform && hasTexcoord && enableLighting && vertexRangeCulling; +// The legacy mode-2 shader (0x2006410) does not require lighting. +// Keep its vertex-state requirements separate from fragment requirements +// (texturing and fog), which are checked by the fragment generator. +inline bool LegacyPBRVertexMode2(bool useHWTransform, bool hasTexcoord, bool vertexRangeCulling) { + return useHWTransform && hasTexcoord && vertexRangeCulling; } inline bool LegacyPBRFragmentBase(bool enableFragmentTestCache, bool doTexture, bool enableFog) { From 83edfb5b7d11e60920e0ace31db4249c8df64788 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 23 Oct 2026 16:54:07 +0700 Subject: [PATCH 55/81] Restrict legacy PBR vertex modes to OpenGL --- GPU/Common/VertexShaderGenerator.cpp | 36 +++++++++++++++------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/GPU/Common/VertexShaderGenerator.cpp b/GPU/Common/VertexShaderGenerator.cpp index 8c7106b181c4..27d1c92aeade 100644 --- a/GPU/Common/VertexShaderGenerator.cpp +++ b/GPU/Common/VertexShaderGenerator.cpp @@ -165,8 +165,8 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag } bool texCoordInVec3 = false; - const bool legacyPBRVertexMode1 = LegacyPBRVertexMode1(useHWTransform, hasNormal, hasTexcoord, enableLighting, rangeCulling); - const bool legacyPBRVertexMode2 = LegacyPBRVertexMode2(useHWTransform, hasTexcoord, enableLighting, rangeCulling); + const bool legacyPBRVertexMode1 = ShaderLanguageIsOpenGL(compat.shaderLanguage) && LegacyPBRVertexMode1(useHWTransform, hasNormal, hasTexcoord, enableLighting, rangeCulling); + const bool legacyPBRVertexMode2 = ShaderLanguageIsOpenGL(compat.shaderLanguage) && LegacyPBRVertexMode2(useHWTransform, hasTexcoord, rangeCulling); if (ShaderLanguageIsOpenGL(compat.shaderLanguage)) { if (legacyPBRVertexMode1) p.C("// Legacy PBR vertex mode: 1 (exports flag 1 + v_1..v_8)\n"); @@ -521,21 +521,23 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag } else { WRITE(p, " mediump vec3 worldnormal = normalizeOr001(mul(vec4(0.0, 0.0, %s1.0, 0.0), u_world).xyz);\n", flipNormal ? "-" : ""); } - if (legacyPBRVertexMode1) { - WRITE(p, " flag = 1;\n"); - WRITE(p, " mat3 v;\n"); - WRITE(p, " v[0] = vec3(u_view[0].xyz);\n"); - WRITE(p, " v[1] = vec3(u_view[1].xyz);\n"); - WRITE(p, " v[2] = vec3(u_view[2].xyz);\n"); - WRITE(p, " v_1 = vec4(worldnormal.xyz, 1.0);\n"); - WRITE(p, " v_2 = vec4(worldpos, 1.0);\n"); - WRITE(p, " v_3 = vec4(normalize(vec3(0.,10000.,2000.) * v), 1.0);\n"); - WRITE(p, " v_4 = vec4(normalize(vec3(0.,10000.,-2000.) * v), 1.0);\n"); - WRITE(p, " v_5 = vec4(v_3.x, -v_3.y, v_3.z, 1.0);\n"); - } else if (legacyPBRVertexMode2) { - WRITE(p, " flag = 2;\n"); - } else { - WRITE(p, " flag = 0;\n"); + if (ShaderLanguageIsOpenGL(compat.shaderLanguage)) { + if (legacyPBRVertexMode1) { + WRITE(p, " flag = 1;\n"); + WRITE(p, " mat3 v;\n"); + WRITE(p, " v[0] = vec3(u_view[0].xyz);\n"); + WRITE(p, " v[1] = vec3(u_view[1].xyz);\n"); + WRITE(p, " v[2] = vec3(u_view[2].xyz);\n"); + WRITE(p, " v_1 = vec4(worldnormal.xyz, 1.0);\n"); + WRITE(p, " v_2 = vec4(worldpos, 1.0);\n"); + WRITE(p, " v_3 = vec4(normalize(vec3(0.,10000.,2000.) * v), 1.0);\n"); + WRITE(p, " v_4 = vec4(normalize(vec3(0.,10000.,-2000.) * v), 1.0);\n"); + WRITE(p, " v_5 = vec4(v_3.x, -v_3.y, v_3.z, 1.0);\n"); + } else if (legacyPBRVertexMode2) { + WRITE(p, " flag = 2;\n"); + } else { + WRITE(p, " flag = 0;\n"); + } } if (enableLighting || doShadeMapping) { // The viewer is at infinity along view space +z: in world space, the view matrix's third column. From 1ca8859424f976723d03050f86cbdb08e3ec5975 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 23 Oct 2026 16:54:23 +0700 Subject: [PATCH 56/81] Match legacy fragment cache requirements --- GPU/Common/FragmentShaderGenerator.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index ca7ac95ff802..7cf7198fa0be 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -84,7 +84,6 @@ bool GenerateFragmentShader(const FShaderID &id, const VShaderID *linkedVSID, ch linkedLegacyPBRVertexMode2 = LegacyPBRVertexMode2( linkedVSID->Bit(VS_BIT_USE_HW_TRANSFORM), linkedVSID->Bit(VS_BIT_HAS_TEXCOORD) || !linkedVSID->Bit(VS_BIT_USE_HW_TRANSFORM), - linkedVSID->Bit(VS_BIT_LIGHTING_ENABLE), linkedVSID->Bit(VS_BIT_VERTEX_RANGE_CULLING)); } bool forceDepthWritesOff = id.Bit(FS_BIT_DEPTH_TEST_NEVER); @@ -147,10 +146,8 @@ bool GenerateFragmentShader(const FShaderID &id, const VShaderID *linkedVSID, ch bool doTexture = id.Bit(FS_BIT_DO_TEXTURE); bool enableFog = id.Bit(FS_BIT_ENABLE_FOG); const bool legacyPBRFragmentBase = - ShaderLanguageIsOpenGL(compat.shaderLanguage) && (linkedLegacyPBRVertexMode1 || linkedLegacyPBRVertexMode2) && - doTexture && - enableFog; + LegacyPBRFragmentBase(enableFragmentTestCache, doTexture, enableFog); bool enableAlphaTest = id.Bit(FS_BIT_ALPHA_TEST); bool alphaTestAgainstZero = id.Bit(FS_BIT_ALPHA_AGAINST_ZERO); From fb9509b1d1b6a3b3239d0480ab3cb0c9739fb1a3 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 23 Oct 2026 17:20:39 +0700 Subject: [PATCH 57/81] Define legacy PBR alpha fragment shader bit --- GPU/Common/ShaderId.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/GPU/Common/ShaderId.h b/GPU/Common/ShaderId.h index bb9a322ec512..768f12901e7b 100644 --- a/GPU/Common/ShaderId.h +++ b/GPU/Common/ShaderId.h @@ -110,7 +110,8 @@ enum FShaderBit : uint8_t { FS_BIT_USE_FRAMEBUFFER_FETCH = 59, FS_BIT_DEPTH_TEST_NEVER = 60, // Only used on Mali. Set when depth == NEVER. We forcibly avoid writing to depth in this case, since it crashes the driver. FS_BIT_SAMPLE_ARRAY_TEXTURE = 61, // For multiview, framebuffers are array textures and we need to sample the two layers correctly. - // Free bits: 62-63 + FS_BIT_LEGACY_PBR_ALPHA = 62, // Legacy OpenGL PBR variant that bypasses the standard alpha test for atlas mode 1. + // Free bit: 63 }; static inline FShaderBit operator +(FShaderBit bit, int i) { From 6cb1f90c1f3ad40ba9056c413953b1843b3df5de Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 23 Oct 2026 17:20:55 +0700 Subject: [PATCH 58/81] Gate legacy PBR alpha bypass with shader ID bit --- GPU/Common/FragmentShaderGenerator.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index 7cf7198fa0be..261e7178aa3f 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -967,7 +967,7 @@ bool GenerateFragmentShader(const FShaderID &id, const VShaderID *linkedVSID, ch const char *discardStatement = testForceToZero ? "v.a = 0.0;" : "DISCARD;"; if (enableAlphaTest) { *fragmentShaderFlags |= FragmentShaderFlags::USES_DISCARD; - if (legacyPBRFragmentBase) + if (legacyPBRFragmentBase && id.Bit(FS_BIT_LEGACY_PBR_ALPHA)) WRITE(p, " if (!(legacyPBRActive && flag == 1)) {\n"); if (alphaTestAgainstZero) { @@ -1004,7 +1004,7 @@ bool GenerateFragmentShader(const FShaderID &id, const VShaderID *linkedVSID, ch WRITE(p, " %s\n", discardStatement); } } - if (legacyPBRFragmentBase) + if (legacyPBRFragmentBase && id.Bit(FS_BIT_LEGACY_PBR_ALPHA)) WRITE(p, " }\n"); } From 2920fcb0b65500dddaf98f79e97eec2eb172a340 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 23 Oct 2026 17:21:02 +0700 Subject: [PATCH 59/81] Bump GLES shader cache version for legacy PBR alpha bit --- GPU/GLES/ShaderManagerGLES.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GPU/GLES/ShaderManagerGLES.cpp b/GPU/GLES/ShaderManagerGLES.cpp index 533104cbde1d..347511c814c9 100644 --- a/GPU/GLES/ShaderManagerGLES.cpp +++ b/GPU/GLES/ShaderManagerGLES.cpp @@ -860,7 +860,7 @@ enum class CacheDetectFlags { }; #define CACHE_HEADER_MAGIC 0x83277592 -#define CACHE_VERSION 45 +#define CACHE_VERSION 46 struct CacheHeader { uint32_t magic; From 0d1b455c8d55ee8de3df843182c068615abb8e38 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 23 Oct 2026 17:21:05 +0700 Subject: [PATCH 60/81] Bump Vulkan shader cache version for fragment shader ID change --- GPU/Vulkan/ShaderManagerVulkan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GPU/Vulkan/ShaderManagerVulkan.cpp b/GPU/Vulkan/ShaderManagerVulkan.cpp index 2ea71d2dfcc5..8cea96cf78ff 100644 --- a/GPU/Vulkan/ShaderManagerVulkan.cpp +++ b/GPU/Vulkan/ShaderManagerVulkan.cpp @@ -369,7 +369,7 @@ enum class VulkanCacheDetectFlags { }; #define CACHE_HEADER_MAGIC 0xff51f420 -#define CACHE_VERSION 61 +#define CACHE_VERSION 62 struct VulkanCacheHeader { uint32_t magic; From 82ab87854880de7c67289e84a10bfe39dc6322ae Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 23 Oct 2026 17:21:12 +0700 Subject: [PATCH 61/81] Fix legacy PBR mode-2 helper call --- GPU/Common/FragmentShaderGenerator.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index 261e7178aa3f..4a539e2f462c 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -128,7 +128,6 @@ bool GenerateFragmentShader(const FShaderID &id, const VShaderID *linkedVSID, ch const bool linkedVSMode2 = LegacyPBRVertexMode2( linkedVSID->Bit(VS_BIT_USE_HW_TRANSFORM), linkedVSID->Bit(VS_BIT_HAS_TEXCOORD) || !linkedVSID->Bit(VS_BIT_USE_HW_TRANSFORM), - linkedVSID->Bit(VS_BIT_LIGHTING_ENABLE), linkedVSID->Bit(VS_BIT_VERTEX_RANGE_CULLING)); p.F("// Used by vertex%016llx.glsl\n", (unsigned long long)linkedVSID->ToUint64()); p.F("// Linked vertex shader ID: %016llx\n", (unsigned long long)linkedVSID->ToUint64()); From 70a1063e3062304bdf79b199ea038e7a442853ca Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 23 Oct 2026 17:39:51 +0700 Subject: [PATCH 62/81] Fix legacy PBR four-sphere atlas selection --- GPU/Common/FragmentShaderGenerator.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index 4a539e2f462c..311a39d1fedb 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -933,6 +933,7 @@ bool GenerateFragmentShader(const FShaderID &id, const VShaderID *linkedVSID, ch WRITE(p, " if (legacyEmissive.a >= 0.001 && legacyEmissive.a < 0.1) legacySphereOffset = vec2(0.5, 0.5);\n"); WRITE(p, " else if (legacyEmissive.a >= 0.0999 && legacyEmissive.a < 0.2) legacySphereOffset = vec2(0.75, 0.5);\n"); WRITE(p, " else if (legacyEmissive.a >= 0.1999 && legacyEmissive.a < 0.3) legacySphereOffset = vec2(0.5, 0.75);\n"); + WRITE(p, " else if (legacyEmissive.a >= 0.2999 && legacyEmissive.a < 0.4) legacySphereOffset = vec2(0.75, 0.75);\n"); WRITE(p, " vec3 legacySphere = %s(tex, legacySphereOffset + legacySphereBaseUV).rgb;\n", compat.texture); WRITE(p, " legacyPBRColor = vec4(legacyBaseColor.rgb * legacySphere * legacyFogColor + legacyEmissive.rgb, v.a);\n"); WRITE(p, " legacyPBRActive = true;\n"); From 5e22ec233247ae91a833a996b508803ee5a8fb54 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 23 Oct 2026 17:51:20 +0700 Subject: [PATCH 63/81] Clamp legacy PBR sphere sampling to atlas quadrants --- GPU/Common/FragmentShaderGenerator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index 311a39d1fedb..4d65b7030ca5 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -925,7 +925,7 @@ bool GenerateFragmentShader(const FShaderID &id, const VShaderID *linkedVSID, ch WRITE(p, " legacyN = normalize(legacyN);\n"); WRITE(p, " vec3 legacyR = reflect(-v_2.xyz, legacyN);\n"); WRITE(p, " float legacyM = 2.0 * sqrt(max(legacyR.x * legacyR.x + legacyR.y * legacyR.y + (legacyR.z + 1.0) * legacyR.z, 0.000001));\n"); - WRITE(p, " vec2 legacySphereBaseUV = (vec2(legacyR.x, legacyR.y) / legacyM + 0.5) * 0.25;\n"); + // Keep reflection coordinates inside the selected 512x512 sphere-map quadrant.\n // The half-texel inset avoids filtering across atlas quadrant boundaries.\n WRITE(p, " vec2 legacySphereBaseUV = clamp((vec2(legacyR.x, legacyR.y) / legacyM + 0.5) * 0.25, vec2(0.0001220703125), vec2(0.2498779296875));\\n"); WRITE(p, " float legacyFogLightness = lightness(u_fogcolor.r, u_fogcolor.g, u_fogcolor.b);\n"); WRITE(p, " vec3 legacyFogColor = vec3(mix(vec3(dot(u_fogcolor, vec3(0.299, 0.587, 0.114))), u_fogcolor, 0.6));\n"); WRITE(p, " legacyFogColor += legacyFogLightness < 0.15 ? 0.6 - legacyFogLightness : 0.6;\n"); From 5a709a16a393f45a6d78f27160c62d9a7f94843c Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 23 Oct 2026 17:59:08 +0700 Subject: [PATCH 64/81] Fix legacy PBR shader generation --- GPU/Common/FragmentShaderGenerator.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index 4d65b7030ca5..ad134c30720f 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -914,6 +914,11 @@ bool GenerateFragmentShader(const FShaderID &id, const VShaderID *linkedVSID, ch WRITE(p, " vec4 legacyPBRColor = vec4(0.0);\n"); if (ShaderLanguageIsOpenGL(compat.shaderLanguage) && legacyPBRFragmentBase) { + // The original replacement shader derives a tint from the fog color. + // Emit its helper outside main(), as required by GLSL. + WRITE(p, "float lightness(float R, float G, float B) {\n"); + WRITE(p, " return pow(pow(R, 2.2) + pow(G / 0.666666, 2.2) + pow(B / 1.666666, 2.2), 1.0 / 2.2) * 0.547373;\n"); + WRITE(p, "}\n"); // Match the legacy GLSL atlas layout and sampling math: // base color (top-left), normal data (top-right), emissive/material // selector (bottom-left), and four sphere maps (bottom-right). @@ -925,7 +930,7 @@ bool GenerateFragmentShader(const FShaderID &id, const VShaderID *linkedVSID, ch WRITE(p, " legacyN = normalize(legacyN);\n"); WRITE(p, " vec3 legacyR = reflect(-v_2.xyz, legacyN);\n"); WRITE(p, " float legacyM = 2.0 * sqrt(max(legacyR.x * legacyR.x + legacyR.y * legacyR.y + (legacyR.z + 1.0) * legacyR.z, 0.000001));\n"); - // Keep reflection coordinates inside the selected 512x512 sphere-map quadrant.\n // The half-texel inset avoids filtering across atlas quadrant boundaries.\n WRITE(p, " vec2 legacySphereBaseUV = clamp((vec2(legacyR.x, legacyR.y) / legacyM + 0.5) * 0.25, vec2(0.0001220703125), vec2(0.2498779296875));\\n"); + // Keep reflection coordinates inside the selected 512x512 sphere-map quadrant.\n // The half-texel inset avoids filtering across atlas quadrant boundaries.\n WRITE(p, " vec2 legacySphereBaseUV = clamp((vec2(legacyR.x, legacyR.y) / legacyM + 0.5) * 0.25, vec2(0.0001220703125), vec2(0.2498779296875));\n"); WRITE(p, " float legacyFogLightness = lightness(u_fogcolor.r, u_fogcolor.g, u_fogcolor.b);\n"); WRITE(p, " vec3 legacyFogColor = vec3(mix(vec3(dot(u_fogcolor, vec3(0.299, 0.587, 0.114))), u_fogcolor, 0.6));\n"); WRITE(p, " legacyFogColor += legacyFogLightness < 0.15 ? 0.6 - legacyFogLightness : 0.6;\n"); From 252ec36c09e1d325df02de8a472d3f4941eb7647 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 23 Oct 2026 17:59:24 +0700 Subject: [PATCH 65/81] Match legacy PBR fog lightness calculation --- GPU/Common/FragmentShaderGenerator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index ad134c30720f..5aafadff53cd 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -917,7 +917,7 @@ bool GenerateFragmentShader(const FShaderID &id, const VShaderID *linkedVSID, ch // The original replacement shader derives a tint from the fog color. // Emit its helper outside main(), as required by GLSL. WRITE(p, "float lightness(float R, float G, float B) {\n"); - WRITE(p, " return pow(pow(R, 2.2) + pow(G / 0.666666, 2.2) + pow(B / 1.666666, 2.2), 1.0 / 2.2) * 0.547373;\n"); + WRITE(p, " return pow(pow(R / 1.0, 2.2) + pow(B / 1.666666, 2.2) + pow(G / 0.666666, 2.2), 1.0 / 2.2) * 0.547373;\n"); WRITE(p, "}\n"); // Match the legacy GLSL atlas layout and sampling math: // base color (top-left), normal data (top-right), emissive/material From cb38db6dbce6327a9b74ec9ede17967b682c47d6 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 23 Oct 2026 17:59:41 +0700 Subject: [PATCH 66/81] Emit legacy PBR fog helper before main --- GPU/Common/FragmentShaderGenerator.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index 5aafadff53cd..762735abb65f 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -545,6 +545,14 @@ bool GenerateFragmentShader(const FShaderID &id, const VShaderID *linkedVSID, ch if (writeDepth) { WRITE(p, " float gl_FragDepth;\n"); } + if (ShaderLanguageIsOpenGL(compat.shaderLanguage) && legacyPBRFragmentBase) { + // The original replacement shader derives a tint from the fog color. + // Emit its helper outside main(), as required by GLSL. + WRITE(p, "float lightness(float R, float G, float B) {\n"); + WRITE(p, " return pow(pow(R / 1.0, 2.2) + pow(B / 1.666666, 2.2) + pow(G / 0.666666, 2.2), 1.0 / 2.2) * 0.547373;\n"); + WRITE(p, "}\n"); + } + } else { WRITE(p, "void main() {\n"); } @@ -914,11 +922,6 @@ bool GenerateFragmentShader(const FShaderID &id, const VShaderID *linkedVSID, ch WRITE(p, " vec4 legacyPBRColor = vec4(0.0);\n"); if (ShaderLanguageIsOpenGL(compat.shaderLanguage) && legacyPBRFragmentBase) { - // The original replacement shader derives a tint from the fog color. - // Emit its helper outside main(), as required by GLSL. - WRITE(p, "float lightness(float R, float G, float B) {\n"); - WRITE(p, " return pow(pow(R / 1.0, 2.2) + pow(B / 1.666666, 2.2) + pow(G / 0.666666, 2.2), 1.0 / 2.2) * 0.547373;\n"); - WRITE(p, "}\n"); // Match the legacy GLSL atlas layout and sampling math: // base color (top-left), normal data (top-right), emissive/material // selector (bottom-left), and four sphere maps (bottom-right). From 1157039aa4dd55ee33cbfc4acd84b691f6a8025a Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 23 Oct 2026 18:03:05 +0700 Subject: [PATCH 67/81] Gate legacy PBR rendering on replacement atlas --- GPU/Common/FragmentShaderGenerator.cpp | 1 + GPU/Common/ShaderId.h | 2 +- GPU/GLES/DrawEngineGLES.cpp | 13 ++++++++++++- GPU/GLES/DrawEngineGLES.h | 1 + GPU/GLES/ShaderManagerGLES.cpp | 6 +++++- GPU/GLES/ShaderManagerGLES.h | 2 +- 6 files changed, 21 insertions(+), 4 deletions(-) diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index 762735abb65f..2581a52d6a7f 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -145,6 +145,7 @@ bool GenerateFragmentShader(const FShaderID &id, const VShaderID *linkedVSID, ch bool doTexture = id.Bit(FS_BIT_DO_TEXTURE); bool enableFog = id.Bit(FS_BIT_ENABLE_FOG); const bool legacyPBRFragmentBase = + id.Bit(FS_BIT_LEGACY_PBR_ATLAS) && (linkedLegacyPBRVertexMode1 || linkedLegacyPBRVertexMode2) && LegacyPBRFragmentBase(enableFragmentTestCache, doTexture, enableFog); bool enableAlphaTest = id.Bit(FS_BIT_ALPHA_TEST); diff --git a/GPU/Common/ShaderId.h b/GPU/Common/ShaderId.h index 768f12901e7b..4eaf0649636a 100644 --- a/GPU/Common/ShaderId.h +++ b/GPU/Common/ShaderId.h @@ -111,7 +111,7 @@ enum FShaderBit : uint8_t { FS_BIT_DEPTH_TEST_NEVER = 60, // Only used on Mali. Set when depth == NEVER. We forcibly avoid writing to depth in this case, since it crashes the driver. FS_BIT_SAMPLE_ARRAY_TEXTURE = 61, // For multiview, framebuffers are array textures and we need to sample the two layers correctly. FS_BIT_LEGACY_PBR_ALPHA = 62, // Legacy OpenGL PBR variant that bypasses the standard alpha test for atlas mode 1. - // Free bit: 63 + FS_BIT_LEGACY_PBR_ATLAS = 63, // Explicitly identified legacy PBR replacement atlas. }; static inline FShaderBit operator +(FShaderBit bit, int i) { diff --git a/GPU/GLES/DrawEngineGLES.cpp b/GPU/GLES/DrawEngineGLES.cpp index 0ee92fc4d109..e3997dccc917 100644 --- a/GPU/GLES/DrawEngineGLES.cpp +++ b/GPU/GLES/DrawEngineGLES.cpp @@ -28,6 +28,7 @@ #include "GPU/Common/SplineCommon.h" #include "GPU/Common/VertexDecoderCommon.h" #include "GPU/Common/SoftwareTransformCommon.h" +#include "GPU/Common/ReplacedTexture.h" #include "GPU/GLES/DrawEngineGLES.h" #include "GPU/GLES/ShaderManagerGLES.h" #include "GPU/GLES/GPU_GLES.h" @@ -314,8 +315,18 @@ void DrawEngineGLES::Flush() { gstate_c.vertexFullAlpha = gstate_c.vertexFullAlpha && ((hasColor && (gstate.materialupdate & 1)) || gstate.getMaterialAmbientA() == 255) && (!gstate.isLightingEnabled() || gstate.getAmbientA() == 255); } + if (!gstate.isTextureMapEnabled() || gstate.isModeClear()) { + legacyPBRAtlasTexture_ = false; + } if (gstate_c.IsDirty(DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS) && !gstate.isModeClear() && gstate.isTextureMapEnabled()) { TextureApplyResult textureResult = textureCache_->ApplyTexture(true); + legacyPBRAtlasTexture_ = false; + if (textureResult.texCacheEntry && textureResult.texCacheEntry->replacedTexture && textureResult.texCacheEntry->replacedTexture->State() == ReplacementState::ACTIVE) { + int replacementW = 0; + int replacementH = 0; + textureResult.texCacheEntry->replacedTexture->GetSize(0, &replacementW, &replacementH); + legacyPBRAtlasTexture_ = replacementW == 2048 && replacementH == 2048; + } textureCache_->ApplySampler(textureResult, clipInfoFlags_ & ClipInfoFlags::FlatZ, false); gstate_c.Clean(DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS); } else if (gstate.getTextureAddress(0) == (gstate.getFrameBufRawAddress() | 0x04000000)) { @@ -327,7 +338,7 @@ void DrawEngineGLES::Flush() { ApplyDrawState(prim); ApplyDrawStateLate(false, 0); - LinkedShader *program = shaderManager_->ApplyFragmentShader(vsid, vshader, pipelineState_, clipInfoFlags_, false); + LinkedShader *program = shaderManager_->ApplyFragmentShader(vsid, vshader, pipelineState_, clipInfoFlags_, false, legacyPBRAtlasTexture_); if (!program) { // Failed to link. No program is bound, so drawing would use whatever was bound before. goto bail; diff --git a/GPU/GLES/DrawEngineGLES.h b/GPU/GLES/DrawEngineGLES.h index 9220f5da2582..c8632266757b 100644 --- a/GPU/GLES/DrawEngineGLES.h +++ b/GPU/GLES/DrawEngineGLES.h @@ -98,6 +98,7 @@ class DrawEngineGLES : public DrawEngineCommon { // Other ShaderManagerGLES *shaderManager_ = nullptr; TextureCacheGLES *textureCache_ = nullptr; + bool legacyPBRAtlasTexture_ = false; FramebufferManagerGLES *framebufferManager_ = nullptr; FragmentTestCacheGLES *fragmentTestCache_ = nullptr; Draw::DrawContext *draw_; diff --git a/GPU/GLES/ShaderManagerGLES.cpp b/GPU/GLES/ShaderManagerGLES.cpp index 347511c814c9..3095fe443ba0 100644 --- a/GPU/GLES/ShaderManagerGLES.cpp +++ b/GPU/GLES/ShaderManagerGLES.cpp @@ -704,7 +704,7 @@ Shader *ShaderManagerGLES::ApplyVertexShader(bool useHWTransform, u32 vertexType return vs; } -LinkedShader *ShaderManagerGLES::ApplyFragmentShader(VShaderID VSID, Shader *vs, const ComputedPipelineState &pipelineState, ClipInfoFlags clipInfoFlags, bool pixelMapped) { +LinkedShader *ShaderManagerGLES::ApplyFragmentShader(VShaderID VSID, Shader *vs, const ComputedPipelineState &pipelineState, ClipInfoFlags clipInfoFlags, bool pixelMapped, bool legacyPBRAtlas) { uint64_t dirty = gstate_c.GetDirtyUniforms(); if (dirty) { if (lastShader_) @@ -718,6 +718,10 @@ LinkedShader *ShaderManagerGLES::ApplyFragmentShader(VShaderID VSID, Shader *vs, lastShader_ = nullptr; gstate_c.Clean(DIRTY_FRAGMENTSHADER_STATE); ComputeFragmentShaderID(&FSID, pipelineState, draw_->GetBugs(), clipInfoFlags); + // Only explicitly identified replacement atlases enable the legacy PBR shader path. + if (legacyPBRAtlas) { + FSID.SetBit(FS_BIT_LEGACY_PBR_ATLAS); + } // Legacy PBR alpha is an OpenGL/GLES-only shader variant. if (gstate_c.Use(GPU_USE_FRAGMENT_TEST_CACHE) && gstate.isTextureMapEnabled() && gstate.isFogEnabled() && gstate.isTextureAlphaUsed()) { FSID.SetBit(FS_BIT_LEGACY_PBR_ALPHA); diff --git a/GPU/GLES/ShaderManagerGLES.h b/GPU/GLES/ShaderManagerGLES.h index 2a0ad3077f98..b23da60ed19b 100644 --- a/GPU/GLES/ShaderManagerGLES.h +++ b/GPU/GLES/ShaderManagerGLES.h @@ -171,7 +171,7 @@ class ShaderManagerGLES : public ShaderManagerCommon { // This is the old ApplyShader split into two parts, because of annoying information dependencies. // If you call ApplyVertexShader, you MUST call ApplyFragmentShader soon afterwards. Shader *ApplyVertexShader(bool useHWTransform, u32 vertexType, ClipInfoFlags clipInfoFlags, VShaderID *VSID); - LinkedShader *ApplyFragmentShader(VShaderID VSID, Shader *vs, const ComputedPipelineState &pipelineState, ClipInfoFlags clipInfoFlags, bool pixelMapped); + LinkedShader *ApplyFragmentShader(VShaderID VSID, Shader *vs, const ComputedPipelineState &pipelineState, ClipInfoFlags clipInfoFlags, bool pixelMapped, bool legacyPBRAtlas); void DeviceLost() override; void DeviceRestore(Draw::DrawContext *draw) override; From c354dfeeabfd367c5ea72465919cbe120e56a5cd Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 23 Oct 2026 18:03:37 +0700 Subject: [PATCH 68/81] Apply legacy PBR atlas detection to software transform draws --- GPU/GLES/DrawEngineGLES.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/GPU/GLES/DrawEngineGLES.cpp b/GPU/GLES/DrawEngineGLES.cpp index e3997dccc917..d04888175991 100644 --- a/GPU/GLES/DrawEngineGLES.cpp +++ b/GPU/GLES/DrawEngineGLES.cpp @@ -392,10 +392,20 @@ void DrawEngineGLES::Flush() { bool textureNeedsApply = false; TextureApplyResult textureResult; + if (!gstate.isTextureMapEnabled() || gstate.isModeClear()) { + legacyPBRAtlasTexture_ = false; + } if (gstate_c.IsDirty(DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS) && !gstate.isModeClear() && gstate.isTextureMapEnabled()) { gstate_c.Clean(DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS); gstate_c.dstSquared = false; textureResult = textureCache_->ApplyTexture(true); + legacyPBRAtlasTexture_ = false; + if (textureResult.texCacheEntry && textureResult.texCacheEntry->replacedTexture && textureResult.texCacheEntry->replacedTexture->State() == ReplacementState::ACTIVE) { + int replacementW = 0; + int replacementH = 0; + textureResult.texCacheEntry->replacedTexture->GetSize(0, &replacementW, &replacementH); + legacyPBRAtlasTexture_ = replacementW == 2048 && replacementH == 2048; + } textureNeedsApply = true; } else if (gstate.getTextureAddress(0) == (gstate.getFrameBufRawAddress() | 0x04000000)) { // This catches the case of clearing a texture. (#10957) @@ -425,7 +435,7 @@ void DrawEngineGLES::Flush() { ApplyDrawState(prim); ApplyDrawStateLate(result.setStencil, result.stencilValue); - LinkedShader *linked = shaderManager_->ApplyFragmentShader(vsid, vshader, pipelineState_, clipInfoFlags_, result.pixelMapped); + LinkedShader *linked = shaderManager_->ApplyFragmentShader(vsid, vshader, pipelineState_, clipInfoFlags_, result.pixelMapped, legacyPBRAtlasTexture_); if (!linked) { // Not much we can do here. Let's skip drawing. goto bail; From a8c1eb45799837f855daa3d458df2f5ba0271efc Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 23 Oct 2026 18:06:09 +0700 Subject: [PATCH 69/81] Require explicit legacy PBR atlas replacement marker --- GPU/GLES/DrawEngineGLES.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/GPU/GLES/DrawEngineGLES.cpp b/GPU/GLES/DrawEngineGLES.cpp index d04888175991..9407d82566a6 100644 --- a/GPU/GLES/DrawEngineGLES.cpp +++ b/GPU/GLES/DrawEngineGLES.cpp @@ -322,10 +322,16 @@ void DrawEngineGLES::Flush() { TextureApplyResult textureResult = textureCache_->ApplyTexture(true); legacyPBRAtlasTexture_ = false; if (textureResult.texCacheEntry && textureResult.texCacheEntry->replacedTexture && textureResult.texCacheEntry->replacedTexture->State() == ReplacementState::ACTIVE) { - int replacementW = 0; - int replacementH = 0; - textureResult.texCacheEntry->replacedTexture->GetSize(0, &replacementW, &replacementH); - legacyPBRAtlasTexture_ = replacementW == 2048 && replacementH == 2048; + // Explicit opt-in: place legacy PBR atlas replacements under + // TEXTURES//legacy_pbr/ and map the texture hash to that file + // in textures.ini. Do not infer shader semantics from image dimensions. + const ReplacementDesc &replacementDesc = textureResult.texCacheEntry->replacedTexture->Desc(); + for (const std::string &filename : replacementDesc.filenames) { + if (filename.rfind("legacy_pbr/", 0) == 0) { + legacyPBRAtlasTexture_ = true; + break; + } + } } textureCache_->ApplySampler(textureResult, clipInfoFlags_ & ClipInfoFlags::FlatZ, false); gstate_c.Clean(DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS); From 9d713d7ddf7a0a18ea60f664c0f3a4486b605a3c Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 23 Oct 2026 18:10:58 +0700 Subject: [PATCH 70/81] Rename legacy PBR marker directory to GLSL --- GPU/GLES/DrawEngineGLES.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GPU/GLES/DrawEngineGLES.cpp b/GPU/GLES/DrawEngineGLES.cpp index 9407d82566a6..3cae546ee0d3 100644 --- a/GPU/GLES/DrawEngineGLES.cpp +++ b/GPU/GLES/DrawEngineGLES.cpp @@ -323,11 +323,11 @@ void DrawEngineGLES::Flush() { legacyPBRAtlasTexture_ = false; if (textureResult.texCacheEntry && textureResult.texCacheEntry->replacedTexture && textureResult.texCacheEntry->replacedTexture->State() == ReplacementState::ACTIVE) { // Explicit opt-in: place legacy PBR atlas replacements under - // TEXTURES//legacy_pbr/ and map the texture hash to that file + // TEXTURES//GLSL/ and map the texture hash to that file // in textures.ini. Do not infer shader semantics from image dimensions. const ReplacementDesc &replacementDesc = textureResult.texCacheEntry->replacedTexture->Desc(); for (const std::string &filename : replacementDesc.filenames) { - if (filename.rfind("legacy_pbr/", 0) == 0) { + if (filename.rfind("GLSL/", 0) == 0) { legacyPBRAtlasTexture_ = true; break; } From 4f32afa742bff673b6b8485d8f554d348ac3a885 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 23 Oct 2026 18:58:57 +0700 Subject: [PATCH 71/81] Decouple legacy PBR shader from fragment test cache --- GPU/Common/LegacyPBR.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/GPU/Common/LegacyPBR.h b/GPU/Common/LegacyPBR.h index 1e2e37192db3..f6348f886894 100644 --- a/GPU/Common/LegacyPBR.h +++ b/GPU/Common/LegacyPBR.h @@ -23,8 +23,10 @@ inline bool LegacyPBRVertexMode2(bool useHWTransform, bool hasTexcoord, bool ver return useHWTransform && hasTexcoord && vertexRangeCulling; } -inline bool LegacyPBRFragmentBase(bool enableFragmentTestCache, bool doTexture, bool enableFog) { - return enableFragmentTestCache && doTexture && enableFog; +inline bool LegacyPBRFragmentBase(bool doTexture, bool enableFog) { + // The custom PBR shader path is independent of PPSSPP's fragment test cache. + // That cache is an optimization and must not disable the generated shader variant. + return doTexture && enableFog; } inline void WriteLegacyPBRVaryingVS(ShaderWriter &p, const char *varying) { From 46523bacb624b1f2750a28f8bf5592610e33d4d0 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Fri, 23 Oct 2026 18:59:01 +0700 Subject: [PATCH 72/81] Decouple legacy PBR shader from fragment test cache --- GPU/Common/FragmentShaderGenerator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index 2581a52d6a7f..125094e2e2a5 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -147,7 +147,7 @@ bool GenerateFragmentShader(const FShaderID &id, const VShaderID *linkedVSID, ch const bool legacyPBRFragmentBase = id.Bit(FS_BIT_LEGACY_PBR_ATLAS) && (linkedLegacyPBRVertexMode1 || linkedLegacyPBRVertexMode2) && - LegacyPBRFragmentBase(enableFragmentTestCache, doTexture, enableFog); + LegacyPBRFragmentBase(doTexture, enableFog); bool enableAlphaTest = id.Bit(FS_BIT_ALPHA_TEST); bool alphaTestAgainstZero = id.Bit(FS_BIT_ALPHA_AGAINST_ZERO); From 3d3950ad692e7d0b61639262d63f147666d92891 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Sat, 24 Oct 2026 05:12:39 +0700 Subject: [PATCH 73/81] Revert fragment test cache decoupling --- GPU/Common/LegacyPBR.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/GPU/Common/LegacyPBR.h b/GPU/Common/LegacyPBR.h index f6348f886894..1e2e37192db3 100644 --- a/GPU/Common/LegacyPBR.h +++ b/GPU/Common/LegacyPBR.h @@ -23,10 +23,8 @@ inline bool LegacyPBRVertexMode2(bool useHWTransform, bool hasTexcoord, bool ver return useHWTransform && hasTexcoord && vertexRangeCulling; } -inline bool LegacyPBRFragmentBase(bool doTexture, bool enableFog) { - // The custom PBR shader path is independent of PPSSPP's fragment test cache. - // That cache is an optimization and must not disable the generated shader variant. - return doTexture && enableFog; +inline bool LegacyPBRFragmentBase(bool enableFragmentTestCache, bool doTexture, bool enableFog) { + return enableFragmentTestCache && doTexture && enableFog; } inline void WriteLegacyPBRVaryingVS(ShaderWriter &p, const char *varying) { From 71aa27243a11882c22d6ccb7106e0326819c92a2 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Sat, 24 Oct 2026 05:12:42 +0700 Subject: [PATCH 74/81] Revert fragment test cache decoupling --- GPU/Common/FragmentShaderGenerator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index 125094e2e2a5..2581a52d6a7f 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -147,7 +147,7 @@ bool GenerateFragmentShader(const FShaderID &id, const VShaderID *linkedVSID, ch const bool legacyPBRFragmentBase = id.Bit(FS_BIT_LEGACY_PBR_ATLAS) && (linkedLegacyPBRVertexMode1 || linkedLegacyPBRVertexMode2) && - LegacyPBRFragmentBase(doTexture, enableFog); + LegacyPBRFragmentBase(enableFragmentTestCache, doTexture, enableFog); bool enableAlphaTest = id.Bit(FS_BIT_ALPHA_TEST); bool alphaTestAgainstZero = id.Bit(FS_BIT_ALPHA_AGAINST_ZERO); From 4429a3a83f50276c5c3ddda15a55f08f5683c5bb Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Sat, 24 Oct 2026 05:36:25 +0700 Subject: [PATCH 75/81] Move legacy PBR shader bits to avoid master conflict --- GPU/Common/ShaderId.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GPU/Common/ShaderId.h b/GPU/Common/ShaderId.h index 4eaf0649636a..808a2b34ee6d 100644 --- a/GPU/Common/ShaderId.h +++ b/GPU/Common/ShaderId.h @@ -110,8 +110,8 @@ enum FShaderBit : uint8_t { FS_BIT_USE_FRAMEBUFFER_FETCH = 59, FS_BIT_DEPTH_TEST_NEVER = 60, // Only used on Mali. Set when depth == NEVER. We forcibly avoid writing to depth in this case, since it crashes the driver. FS_BIT_SAMPLE_ARRAY_TEXTURE = 61, // For multiview, framebuffers are array textures and we need to sample the two layers correctly. - FS_BIT_LEGACY_PBR_ALPHA = 62, // Legacy OpenGL PBR variant that bypasses the standard alpha test for atlas mode 1. - FS_BIT_LEGACY_PBR_ATLAS = 63, // Explicitly identified legacy PBR replacement atlas. + FS_BIT_LEGACY_PBR_ALPHA = 63, // Legacy OpenGL PBR variant that bypasses the standard alpha test for atlas mode 1. + FS_BIT_LEGACY_PBR_ATLAS = 64, // Explicitly identified legacy PBR replacement atlas. }; static inline FShaderBit operator +(FShaderBit bit, int i) { From 40717d24432902fe5c4451e97796202b8232e50f Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Sat, 24 Oct 2026 05:36:36 +0700 Subject: [PATCH 76/81] Bump Vulkan shader cache version to 64 --- GPU/Vulkan/ShaderManagerVulkan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GPU/Vulkan/ShaderManagerVulkan.cpp b/GPU/Vulkan/ShaderManagerVulkan.cpp index 8cea96cf78ff..9eb8aea9d766 100644 --- a/GPU/Vulkan/ShaderManagerVulkan.cpp +++ b/GPU/Vulkan/ShaderManagerVulkan.cpp @@ -369,7 +369,7 @@ enum class VulkanCacheDetectFlags { }; #define CACHE_HEADER_MAGIC 0xff51f420 -#define CACHE_VERSION 62 +#define CACHE_VERSION 64 struct VulkanCacheHeader { uint32_t magic; From 0e6a7d31d5f802eaf8c435577f6dc75c40216b55 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Sat, 24 Oct 2026 05:36:39 +0700 Subject: [PATCH 77/81] Bump GLES shader cache version to 47 --- GPU/GLES/ShaderManagerGLES.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GPU/GLES/ShaderManagerGLES.cpp b/GPU/GLES/ShaderManagerGLES.cpp index 3095fe443ba0..6c5f24c65fff 100644 --- a/GPU/GLES/ShaderManagerGLES.cpp +++ b/GPU/GLES/ShaderManagerGLES.cpp @@ -864,7 +864,7 @@ enum class CacheDetectFlags { }; #define CACHE_HEADER_MAGIC 0x83277592 -#define CACHE_VERSION 46 +#define CACHE_VERSION 47 struct CacheHeader { uint32_t magic; From 976696a6a3279231e7e53bb2a97b4cee493d8a52 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Sat, 24 Oct 2026 05:40:03 +0700 Subject: [PATCH 78/81] Place no-depth-write bit before legacy PBR bits --- GPU/Common/ShaderId.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/GPU/Common/ShaderId.h b/GPU/Common/ShaderId.h index 808a2b34ee6d..5f27010e3c13 100644 --- a/GPU/Common/ShaderId.h +++ b/GPU/Common/ShaderId.h @@ -110,8 +110,10 @@ enum FShaderBit : uint8_t { FS_BIT_USE_FRAMEBUFFER_FETCH = 59, FS_BIT_DEPTH_TEST_NEVER = 60, // Only used on Mali. Set when depth == NEVER. We forcibly avoid writing to depth in this case, since it crashes the driver. FS_BIT_SAMPLE_ARRAY_TEXTURE = 61, // For multiview, framebuffers are array textures and we need to sample the two layers correctly. + FS_BIT_NO_DEPTH_WRITE = 62, FS_BIT_LEGACY_PBR_ALPHA = 63, // Legacy OpenGL PBR variant that bypasses the standard alpha test for atlas mode 1. FS_BIT_LEGACY_PBR_ATLAS = 64, // Explicitly identified legacy PBR replacement atlas. + // Free bit: 65 }; static inline FShaderBit operator +(FShaderBit bit, int i) { From 147af617fd916e26b9e45c9ceec2cc4a5d273d8d Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Sat, 24 Oct 2026 05:41:36 +0700 Subject: [PATCH 79/81] Keep legacy PBR atlas within 64-bit shader ID --- GPU/Common/ShaderId.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/GPU/Common/ShaderId.h b/GPU/Common/ShaderId.h index 5f27010e3c13..6ded6be6950e 100644 --- a/GPU/Common/ShaderId.h +++ b/GPU/Common/ShaderId.h @@ -110,9 +110,11 @@ enum FShaderBit : uint8_t { FS_BIT_USE_FRAMEBUFFER_FETCH = 59, FS_BIT_DEPTH_TEST_NEVER = 60, // Only used on Mali. Set when depth == NEVER. We forcibly avoid writing to depth in this case, since it crashes the driver. FS_BIT_SAMPLE_ARRAY_TEXTURE = 61, // For multiview, framebuffers are array textures and we need to sample the two layers correctly. + // GLES-only legacy PBR atlas and Vulkan-only array-texture mode share bit 61. + // These modes are mutually exclusive by backend, preserving the 64-bit shader ID layout. + FS_BIT_LEGACY_PBR_ATLAS = FS_BIT_SAMPLE_ARRAY_TEXTURE, FS_BIT_NO_DEPTH_WRITE = 62, FS_BIT_LEGACY_PBR_ALPHA = 63, // Legacy OpenGL PBR variant that bypasses the standard alpha test for atlas mode 1. - FS_BIT_LEGACY_PBR_ATLAS = 64, // Explicitly identified legacy PBR replacement atlas. // Free bit: 65 }; From 93df5c0319f03f5af9e4f6f259221dd586b94d65 Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Sat, 24 Oct 2026 05:41:52 +0700 Subject: [PATCH 80/81] Clarify fragment shader ID bit capacity --- GPU/Common/ShaderId.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GPU/Common/ShaderId.h b/GPU/Common/ShaderId.h index 6ded6be6950e..cd097d67dde0 100644 --- a/GPU/Common/ShaderId.h +++ b/GPU/Common/ShaderId.h @@ -115,7 +115,7 @@ enum FShaderBit : uint8_t { FS_BIT_LEGACY_PBR_ATLAS = FS_BIT_SAMPLE_ARRAY_TEXTURE, FS_BIT_NO_DEPTH_WRITE = 62, FS_BIT_LEGACY_PBR_ALPHA = 63, // Legacy OpenGL PBR variant that bypasses the standard alpha test for atlas mode 1. - // Free bit: 65 + // No free bits remain in the 64-bit fragment shader ID. }; static inline FShaderBit operator +(FShaderBit bit, int i) { From 2b2f1a728de163c93d089a3042533ef92549ee8d Mon Sep 17 00:00:00 2001 From: Eld Axe Date: Sat, 24 Oct 2026 05:45:43 +0700 Subject: [PATCH 81/81] Restore no-depth-write bit comment --- GPU/Common/ShaderId.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GPU/Common/ShaderId.h b/GPU/Common/ShaderId.h index cd097d67dde0..881a3971fd53 100644 --- a/GPU/Common/ShaderId.h +++ b/GPU/Common/ShaderId.h @@ -113,7 +113,7 @@ enum FShaderBit : uint8_t { // GLES-only legacy PBR atlas and Vulkan-only array-texture mode share bit 61. // These modes are mutually exclusive by backend, preserving the 64-bit shader ID layout. FS_BIT_LEGACY_PBR_ATLAS = FS_BIT_SAMPLE_ARRAY_TEXTURE, - FS_BIT_NO_DEPTH_WRITE = 62, + FS_BIT_NO_DEPTH_WRITE = 62, // Depth test effectively off, so skip the fragment depth write. FS_BIT_LEGACY_PBR_ALPHA = 63, // Legacy OpenGL PBR variant that bypasses the standard alpha test for atlas mode 1. // No free bits remain in the 64-bit fragment shader ID. };