Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
d3c802c
Add modernized legacy PBR shader support
eldaxe Oct 2, 2026
b0ae2e1
Migrate legacy PBR vertex generation
eldaxe Oct 2, 2026
e301757
Migrate legacy PBR fragment generation
eldaxe Oct 2, 2026
ccaeac1
Use modern fragment ID predicates for legacy PBR
eldaxe Oct 2, 2026
b863fc0
Reserve shader ID bit for legacy PBR alpha
eldaxe Oct 2, 2026
9e16ab7
Add legacy PBR alpha to modern fragment shader ID
eldaxe Oct 2, 2026
3b0e2c9
Bump shader cache version for legacy PBR ID
eldaxe Oct 2, 2026
f763fcd
Bump shader cache version for legacy PBR ID
eldaxe Oct 2, 2026
a1e366e
Revert Vulkan shader cache version change
eldaxe Oct 2, 2026
8a65e01
Revert GLES shader cache version change
eldaxe Oct 2, 2026
ca6329c
Restore OpenGL shader cache version for legacy PBR
eldaxe Oct 2, 2026
d301d11
Keep legacy PBR alpha shader ID OpenGL-specific
eldaxe Oct 2, 2026
e941048
Set legacy PBR alpha ID only on OpenGL
eldaxe Oct 2, 2026
085b805
Disable androidGitVersion test
eldaxe Oct 2, 2026
3f48b11
Fix LegacyPBR shader override path construction
eldaxe Oct 2, 2026
981c7ee
Fix legacy PBR fragment predicate declaration order
eldaxe Oct 2, 2026
0c06326
Fix legacy PBR GLSL override for PPSSPP scoped storage
eldaxe Oct 2, 2026
832ec5c
Fix legacy PBR GLSL path for PSP-root memsticks
eldaxe Oct 2, 2026
de1a6d3
Fix legacy PBR vertex predicate state
eldaxe Oct 2, 2026
8b39570
Fix legacy PBR vertex predicate compile error
eldaxe Oct 2, 2026
89fc5f2
Add generated GLSL fallback dump helper
eldaxe Oct 2, 2026
e78bd7b
Dump generated GLSL when override is absent
eldaxe Oct 2, 2026
61c7d42
Dump generated GLSL when override is absent
eldaxe Oct 2, 2026
d51b031
Fix LegacyPBR GLSL dump directory type
eldaxe Oct 2, 2026
322b0bf
Remove extra GLSL generator brace
eldaxe Oct 3, 2026
968905d
Remove extra GLSL generator brace
eldaxe Oct 3, 2026
5b58da2
Fix legacy PBR fragment generation and output selection
eldaxe Oct 3, 2026
78c2459
Fix legacy PBR alpha result preservation
eldaxe Oct 3, 2026
bb72f41
Add vertex linkage metadata to fragment generator
eldaxe Oct 8, 2026
de81902
Annotate generated fragment with linked vertex shader
eldaxe Oct 8, 2026
7655354
Pass linked vertex ID to fragment compilation
eldaxe Oct 8, 2026
1005513
Pass vertex ID when generating fragment shader
eldaxe Oct 8, 2026
caeddc1
Add VS linkage and Legacy PBR metadata to generated GLSL
eldaxe Oct 8, 2026
6a37282
Annotate generated GLSL with Legacy PBR vertex mode
eldaxe Oct 8, 2026
8425bb1
Tie Legacy PBR fragment activation to linked vertex mode
eldaxe Oct 8, 2026
2d3a0e0
Remove unused linked Legacy PBR mode state
eldaxe Oct 8, 2026
0b6d2f5
Use workflow-provided Java 17 instead of pinned Foojay Java 21
eldaxe Oct 8, 2026
cea71bb
Pass linked vertex shader ID to fragment generation
eldaxe Oct 8, 2026
e4f5708
Pass null linked vertex ID to Vulkan fragment generator
eldaxe Oct 8, 2026
f99cade
Fix escaped newlines in Legacy PBR shader comments
eldaxe Oct 9, 2026
0e9858f
Fix escaped newlines in Legacy PBR vertex comments
eldaxe Oct 9, 2026
e773aed
Avoid Legacy PBR output references in inactive shaders
eldaxe Oct 9, 2026
affa407
Declare fragment output before Legacy PBR functions
eldaxe Oct 9, 2026
2986021
Format generated Legacy PBR GLSL for readability
eldaxe Oct 9, 2026
9a9b40c
Implement legacy PBR texture atlas sampling
eldaxe Oct 23, 2026
0943940
Match legacy GLSL atlas material sampling
eldaxe Oct 23, 2026
e4a2261
Match legacy sphere-map coordinates
eldaxe Oct 23, 2026
8e467ee
Restore legacy atlas shader selection
eldaxe Oct 23, 2026
d9a9a14
Preserve legacy atlas alpha behavior
eldaxe Oct 23, 2026
6ee483e
Preserve legacy atlas alpha-test behavior
eldaxe Oct 23, 2026
17945e3
Preserve legacy atlas alpha-test behavior
eldaxe Oct 23, 2026
26b15f6
Remove unused legacy atlas shader bit
eldaxe Oct 23, 2026
3361e0f
Clean up legacy atlas shader generator
eldaxe Oct 23, 2026
03278ad
Match legacy mode 2 vertex requirements
eldaxe Oct 23, 2026
83edfb5
Restrict legacy PBR vertex modes to OpenGL
eldaxe Oct 23, 2026
1ca8859
Match legacy fragment cache requirements
eldaxe Oct 23, 2026
fb9509b
Define legacy PBR alpha fragment shader bit
eldaxe Oct 23, 2026
6cb1f90
Gate legacy PBR alpha bypass with shader ID bit
eldaxe Oct 23, 2026
2920fcb
Bump GLES shader cache version for legacy PBR alpha bit
eldaxe Oct 23, 2026
0d1b455
Bump Vulkan shader cache version for fragment shader ID change
eldaxe Oct 23, 2026
82ab878
Fix legacy PBR mode-2 helper call
eldaxe Oct 23, 2026
70a1063
Fix legacy PBR four-sphere atlas selection
eldaxe Oct 23, 2026
5e22ec2
Clamp legacy PBR sphere sampling to atlas quadrants
eldaxe Oct 23, 2026
5a709a1
Fix legacy PBR shader generation
eldaxe Oct 23, 2026
252ec36
Match legacy PBR fog lightness calculation
eldaxe Oct 23, 2026
cb38db6
Emit legacy PBR fog helper before main
eldaxe Oct 23, 2026
1157039
Gate legacy PBR rendering on replacement atlas
eldaxe Oct 23, 2026
c354dfe
Apply legacy PBR atlas detection to software transform draws
eldaxe Oct 23, 2026
a8c1eb4
Require explicit legacy PBR atlas replacement marker
eldaxe Oct 23, 2026
9d713d7
Rename legacy PBR marker directory to GLSL
eldaxe Oct 23, 2026
4f32afa
Decouple legacy PBR shader from fragment test cache
eldaxe Oct 23, 2026
46523ba
Decouple legacy PBR shader from fragment test cache
eldaxe Oct 23, 2026
3d3950a
Revert fragment test cache decoupling
eldaxe Oct 23, 2026
71aa272
Revert fragment test cache decoupling
eldaxe Oct 23, 2026
4429a3a
Move legacy PBR shader bits to avoid master conflict
eldaxe Oct 23, 2026
40717d2
Bump Vulkan shader cache version to 64
eldaxe Oct 23, 2026
0e6a7d3
Bump GLES shader cache version to 47
eldaxe Oct 23, 2026
976696a
Place no-depth-write bit before legacy PBR bits
eldaxe Oct 23, 2026
147af61
Keep legacy PBR atlas within 64-bit shader ID
eldaxe Oct 23, 2026
93df5c0
Clarify fragment shader ID bit capacity
eldaxe Oct 23, 2026
2b2f1a7
Restore no-depth-write bit comment
eldaxe Oct 23, 2026
88b9fc8
Merge branch 'master' into patch-glsl
eldaxe Oct 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/manual_generate_apk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
115 changes: 110 additions & 5 deletions GPU/Common/FragmentShaderGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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();
Expand All @@ -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;

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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");
}
Expand Down Expand Up @@ -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].
Expand All @@ -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.
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
}

3 changes: 2 additions & 1 deletion GPU/Common/FragmentShaderGenerator.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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);
Loading
Loading