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 diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index 951d2e7576f9..658ef953aac2 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" @@ -48,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(); @@ -69,7 +70,22 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu } bool texture3D = id.Bit(FS_BIT_3D_TEXTURE); - 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), + 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_VERTEX_RANGE_CULLING)); + } bool forceDepthWritesOff = id.Bit(FS_BIT_DEPTH_TEST_NEVER); bool useDiscardStencilBugWorkaround = id.Bit(FS_BIT_NO_DEPTH_CANNOT_DISCARD_STENCIL) && !forceDepthWritesOff; @@ -102,12 +118,36 @@ 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) { + 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_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); 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 = + id.Bit(FS_BIT_LEGACY_PBR_ATLAS) && + (linkedLegacyPBRVertexMode1 || linkedLegacyPBRVertexMode2) && + LegacyPBRFragmentBase(enableFragmentTestCache, doTexture, enableFog); bool enableAlphaTest = id.Bit(FS_BIT_ALPHA_TEST); bool alphaTestAgainstZero = id.Bit(FS_BIT_ALPHA_AGAINST_ZERO); @@ -436,6 +476,10 @@ 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); + } if (!enableFragmentTestCache) { if (enableAlphaTest && !alphaTestAgainstZero) { @@ -471,6 +515,10 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu 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; @@ -518,6 +566,14 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu 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"); } @@ -930,9 +986,47 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu WRITE(p, " vec4 v = v_color0%s;\n", secondary); } + // 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) { + // 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(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"); + 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, " 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"); + 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"); + } + 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]. @@ -950,6 +1044,8 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu const char *discardStatement = testForceToZero ? "v.a = 0.0;" : "DISCARD;"; if (enableAlphaTest) { *fragmentShaderFlags |= FragmentShaderFlags::USES_DISCARD; + if (legacyPBRFragmentBase && id.Bit(FS_BIT_LEGACY_PBR_ALPHA)) + WRITE(p, " if (!(legacyPBRActive && flag == 1)) {\n"); if (alphaTestAgainstZero) { // When testing against 0 (extremely common), we can avoid some math. @@ -985,6 +1081,8 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu WRITE(p, " %s\n", discardStatement); } } + if (legacyPBRFragmentBase && id.Bit(FS_BIT_LEGACY_PBR_ALPHA)) + WRITE(p, " }\n"); } if (enableColorTest) { @@ -1318,7 +1416,14 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu } WRITE(p, "}\n"); + std::string overrideSource; + 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; } - 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); diff --git a/GPU/Common/LegacyPBR.h b/GPU/Common/LegacyPBR.h new file mode 100644 index 000000000000..1e2e37192db3 --- /dev/null +++ b/GPU/Common/LegacyPBR.h @@ -0,0 +1,198 @@ +#pragma once + +#include + +#include "Common/GPU/ShaderWriter.h" +#include "Core/Config.h" +#include "Core/Util/PathUtil.h" +#include "Common/File/FileUtil.h" +#include "Common/StringUtils.h" + +// 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; +} + +// 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) { + 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("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"); + 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) {\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(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"); + 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) * 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"); + 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"); +} + +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 + const Path shaderDirectory = GetSysDirectory(DIRECTORY_PSP) / "SHADERS/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(path.GetDirectory()))) + return false; + + return File::WriteStringToFile(true, source, path); +} diff --git a/GPU/Common/ShaderId.cpp b/GPU/Common/ShaderId.cpp index 1eef57c555d6..bb61fd2f83f7 100644 --- a/GPU/Common/ShaderId.cpp +++ b/GPU/Common/ShaderId.cpp @@ -332,7 +332,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(); ShaderDepalMode shaderDepalMode = gstate_c.shaderDepalMode; GEBufferFormat shaderDepalFormat = {}; diff --git a/GPU/Common/ShaderId.h b/GPU/Common/ShaderId.h index 10ac650ea690..63eea4714463 100644 --- a/GPU/Common/ShaderId.h +++ b/GPU/Common/ShaderId.h @@ -112,8 +112,12 @@ 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, // Depth test effectively off, so skip the fragment depth write. - // Free bit: 63 + 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. }; static inline FShaderBit operator +(FShaderBit bit, int i) { diff --git a/GPU/Common/VertexShaderGenerator.cpp b/GPU/Common/VertexShaderGenerator.cpp index 331a34d9ca3c..cd126ce01c34 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 @@ -165,6 +166,14 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag } bool texCoordInVec3 = false; + 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"); + 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]"; @@ -428,6 +437,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) { @@ -514,6 +527,24 @@ 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 (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. if (compat.shaderLanguage == HLSL_D3D11) { @@ -1035,5 +1066,13 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag WRITE(p, " return Out;\n"); } WRITE(p, "}\n"); + std::string overrideSource; + 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; } diff --git a/GPU/GLES/DrawEngineGLES.cpp b/GPU/GLES/DrawEngineGLES.cpp index 3b05d0140860..94f9ed4b2668 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,24 @@ 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) { + // Explicit opt-in: place legacy PBR atlas replacements under + // 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("GLSL/", 0) == 0) { + legacyPBRAtlasTexture_ = true; + break; + } + } + } 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 +344,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; @@ -381,10 +398,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) @@ -415,7 +442,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; 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 e2db128ff53c..f7b365d81fac 100644 --- a/GPU/GLES/ShaderManagerGLES.cpp +++ b/GPU/GLES/ShaderManagerGLES.cpp @@ -627,11 +627,11 @@ 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; - 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; } @@ -706,7 +706,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_) @@ -720,6 +720,14 @@ 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); + } } else { FSID = lastFSID_; } @@ -736,7 +744,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. @@ -997,7 +1005,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. diff --git a/GPU/GLES/ShaderManagerGLES.h b/GPU/GLES/ShaderManagerGLES.h index ce5b2d551e05..31c9910c10a9 100644 --- a/GPU/GLES/ShaderManagerGLES.h +++ b/GPU/GLES/ShaderManagerGLES.h @@ -172,7 +172,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; @@ -190,7 +190,7 @@ class ShaderManagerGLES : public ShaderManagerCommon { private: void Clear(); - Shader *CompileFragmentShader(FShaderID id); + Shader *CompileFragmentShader(FShaderID id, VShaderID VSID); Shader *CompileVertexShader(VShaderID id); struct LinkedShaderCacheEntry { diff --git a/GPU/Vulkan/ShaderManagerVulkan.cpp b/GPU/Vulkan/ShaderManagerVulkan.cpp index 6bcc8b4c0013..9eb8aea9d766 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++; 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