Skip to content

fix(agc): defer shaders that cannot be prepared at registration to their first use - #1836

Merged
oneandonlydean merged 1 commit into
boykopovar:mainfrom
EdouardSence:fix/agc-defer-unpreparable-shaders
Oct 10, 2026
Merged

oneandonlydean merged 1 commit into
boykopovar:mainfrom
EdouardSence:fix/agc-defer-unpreparable-shaders

Conversation

@EdouardSence

@EdouardSence EdouardSence commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

What

Since #639, RegisterShader prepares every registered shader up front, and the first one that can't be prepared ends the run. Before that, such a shader only failed when it was dispatched, so titles that never dispatch it, or dispatch it late, kept running.

Several titles now stop at registration on compute shaders that call a user-data function pointer with s_swappc_b64, which is not implemented (#1817): Astro Bot, Ghost of Yōtei, Rise of the Ronin, Epic Mickey: Rebrushed, Stray and GTA V. Silksong and Ratchet & Clank: Rift Apart also register headers without their program address registers.

This change marks those shaders deferred instead of preparing them, and prepares them when they are first used. The deferral is decided before any preparation starts, and only in two cases:

  • The stage's program address registers are missing from the header.
  • The program has an s_swappc_b64 whose target the call analysis can't resolve. ShaderRecompiler::UnresolvableSwappcTarget is the condition that GraphBuilder::Build throws on today. It is shared with the analysis through a new isFetchCall helper, so fetch-shader calls are still accepted.

Every other error, including any GraphBuilder, Structurizer or PrepareShader failure, still throws at registration. There is no retry after an error.

SourceHandleFor and InvocationFor prepare a deferred shader at dispatch, the path unregistered compute shaders already take. So the original error is raised there if the shader is really used.

Tested

Linux (CachyOS, GCC), RX 9070 XT, Mesa 26.2.4 RADV. Head 52b77c2 on main 43972e6.

  • agc_driver_prepared_shaders_deferred registers a compute shader made of s_swappc_b64 s[4:5], s[4:5].
    • Registration succeeds, and logs its s_swappc_b64 at program counter 0x0 calls a computed target.
    • The dispatch fails with not statically resolvable.
    • The test now returns the skip code when no Vulkan device can be opened, like the other device tests.
  • agc_driver (tests/Submit.cpp, testHeaderWithoutProgramAddress, contributed by @lubaxe) registers a type 4 header without SPI_SHADER_PGM_LO/HI. On main it fails with missing static shader ABI register 201.
  • Full ctest: 512/513. The failing test is import_audit, which can't read the commit hash in my worktree and is unrelated.
  • tools/check_conventions.py passes. UnresolvableSwappcTarget is added to shaderCacheReviewedNames: it only decides whether to prepare at registration and doesn't change what a recompile produces.
  • Astro Bot, scripted input, headless:

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed in TechnicalDebt
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • AI-assisted: yes. The investigation, code, tests and this description were written by Claude (Claude Code) on my machine.

🤖 Generated with Claude Code

@fisherjoey

fisherjoey commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Tested on a third title: Stray (PPSA02100 v1.05, Unreal Engine 4) stops at the same point on main, and this PR gets it past it.

Setup: Linux (Nobara 44, kernel 7.2), RTX 5070 Ti, NVIDIA 595.91.07. Base is main 1be6f9f + #861 (Linux needs it to boot titles).

Without this PR, Stray exits about 10 s after launch, before any window, on its first sceAgcCreateShader:

what():  computed/data-dependent s_swappc_b64 call target at program counter 0x00000074 is not statically resolvable

Stack from the core: sceAgcCreateShader -> Driver::RegisterShader -> PrepareRegistered -> GraphBuilder::Build -> analyzeSwappcCalls.

With this PR, registration gets through 111 deferred shaders:

The run then stops on a different error that is not related to this PR: AGC driver: unsupported registered shader type, which I'm looking into separately.

One note: the PR no longer merges cleanly onto current main. #1851 added RegisteredFloatMode(snapshot) to the RecompileRequest in PrepareRegistered. I kept your try/catch and added that argument inside it.

AI-assisted: yes

@Dextroid17

Copy link
Copy Markdown
Contributor

Title evidence — the deferral is what unblocks a real title.

PPSA26344 (Ghost of Yōtei 01.008): decrypted eboot.bin relinked, staged runtime layout, RTX 3080 Ti on Linux. Stack measured: main + #757 (image/sampler tables at runtime offsets) + this PR.

stack outcome
main exit 134 (SIGABRT) at presentation — GetImageResource dword 0 is not a valid runtime value
main + #757 exit 134 — that wall cleared, dies at computed/data-dependent s_swappc_b64 call target at program counter 0x74 is not statically resolvable
main + #757 + this PR still running

With this PR the run does not terminate: the shaders it cannot prepare are deferred and the title continues.

[Shaders/ShaderRegistry.cpp:332 operator()] Shader 0x55a82bc5f700 could not be prepared at registration
    (computed/data-dependent s_swappc_b64 call target at program counter 0x00000074 is not statically resolvable)
×20 deferrals, then the run continues

Measured ~2 minutes in: the process is alive with 29 threads at ~100% CPU, the GPU is active, the shader disk cache is past 68 writes, and the frame path prints no error (the earlier run's [videoout] presentation failed is gone). I cannot yet say a frame is on screen — this is a Wayland session with no window-listing tooling here — so the claim is "the title keeps running instead of aborting", not "the title renders".

Note #757 is a prerequisite for reaching this point: on main alone the title dies at the earlier image-descriptor wall, so a rebase/test loop needs both.

Merge note: one conflict, Execution/src/Driver/Shaders/ShaderRegistry.cpp — kept this PR's try/defer structure with main's newer RecompileRequest (it gained RegisteredFloatMode(snapshot) since this PR's base). Worth checking that when you rebase.

Raw log kept. Written with AI assistance.

@lubaxe

lubaxe commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

PPSA12544 (Hollow Knight: Silksong) on Windows (RTX 5090) hits the "program address registers are missing from the header" case of this PR during startup. It registers a type 4 shader whose header has only these registers:

[shader] 0x21906d000 type 4 cx sh 8a=60000002 8b=00030008 ca=03000002

main stops with AGC driver: missing static shader ABI register 201 (0xc9, SPI_SHADER_PGM_HI of the vertex/mesh front). I worked around it locally by returning no prepared entries at registration when programRegister or programRegister + 1 is missing. That is the first case your PR covers. With it, the title reaches gameplay (tut_01).

If it helps, this is the regression I used, for tests/Submit.cpp (agc_driver_tests). It fails on main with exactly that error:

void testHeaderWithoutProgramAddress() {
    alignas(256) static const std::array<std::uint32_t, 64> code{0xbf810000};
    struct Header {
        Shader shader{};
        std::array<ShaderRegister, 3> registers{};
    };
    alignas(8) static Header header{};
    header.shader.file_header = 0x34333231;
    header.shader.version = 0x18;
    header.shader.header_size = sizeof(Header);
    header.shader.shader_size = sizeof(code);
    header.shader.code = code.data();
    header.shader.type = 4;
    header.registers = {{{0x08a, 0x60000002}, {0x08b, 0x00030008}, {0x0ca, 0x03000002}}};
    header.shader.sh_registers = header.registers.data();
    header.shader.num_sh_registers = header.registers.size();
    AgcDriverRegisterShader_nid_postfix(&header.shader);
}

I haven't tested this PR's branch itself yet, since it currently conflicts with main.

(Written with AI assistance: Claude Code.)

@EdouardSence

Copy link
Copy Markdown
Contributor Author

Rebased onto main a8b032b as 177c4b2. It merges cleanly again. The conflict was the one @fisherjoey and @Dextroid17 described: the try block now builds the RecompileRequest with RegisteredFloatMode(snapshot).

@lubaxe thanks for the Silksong header and the regression test. I added it to tests/Submit.cpp as testHeaderWithoutProgramAddress. On main it fails with AGC driver: missing static shader ABI register 201. On this branch it passes, so it covers the second deferral path (missing program address registers). The first path, an unresolvable s_swappc_b64, is covered by the deferred prepared-shader test.

ctest -j8: 495 of 496 pass. The one failure is import_audit, which can't read the commit hash in my worktree and is unrelated.

As @Dextroid17 says, this PR only moves the failure from registration to first use. Inlining user-data s_swappc_b64 targets is still #1817.

Thanks to all three of you for testing it on Stray, Ghost of Yōtei and Silksong.

AI-assisted: the rebase, the added test and this reply were done by Claude (Claude Code) on my machine.

@github-actions

github-actions Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.

Pull request Conflicting files (lines on main) Same exports Same new files
#1902 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp L178-180, L219-221
#2261 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp L13, L178-180, L219-221
core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp L263
#2313 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp L178-181, L219-222

@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 9, 2026
oneandonlydean added a commit to oneandonlydean/AnyPS5 that referenced this pull request Oct 10, 2026
oneandonlydean added a commit to oneandonlydean/AnyPS5 that referenced this pull request Oct 10, 2026
@oneandonlydean
oneandonlydean merged commit 25e4c05 into boykopovar:main Oct 10, 2026
9 checks passed
adriadam10 pushed a commit to Dextroid17/AnyPS5 that referenced this pull request Oct 10, 2026
…orting

The registered-shader decode ran before the deferral decision, so a single
instruction the decoder does not implement ended the title at registration
instead of deferring the shader to its first use.

Give the decoder's unsupported-instruction failures their own type
(UnsupportedInstructionError): the five family-scoped SOP opcode defaults and
the MIMG opcode lookup (which now names the opcode) raise it, and the decode in
PrepareRegistered defers the shader with that reason when it hits one. Any
other exception still throws, so the corrupt-code contract is unchanged.

PPSA26344 hits this: unsupported MIMG opcode 229 (0xE5), issue boykopovar#1941. The
failing word is reproducible offline from the title's own shader store
(cache_ps5/all_shaders.xpps, code word 260), which is how the fixture was
checked before it was written.

Scope: this defers the scalar-family and MIMG opcode gaps. The vector (VOP*)
and memory (SMEM/MUBUF/MTBUF/FLAT/DS) opcode lookups, the modifier-level
"not supported" throws and the malformed/truncated throws still abort.
Widening the deferral to those is a policy call for the maintainers.

Stacked on boykopovar#1836 (the deferral machinery); re-stacked on its current head so
this diff no longer carries boykopovar#1836's earlier broad catch. Once boykopovar#1836 lands only
the decode change remains.

Written with AI assistance.
adriadam10 pushed a commit to Dextroid17/AnyPS5 that referenced this pull request Oct 10, 2026
…orting

The registered-shader decode ran before the deferral decision, so a single
instruction the decoder does not implement ended the title at registration
instead of deferring the shader to its first use.

Give the decoder's unsupported-instruction failures their own type
(UnsupportedInstructionError): the five family-scoped SOP opcode defaults and
the MIMG opcode lookup (which now names the opcode) raise it, and the decode in
PrepareRegistered defers the shader with that reason when it hits one. Any
other exception still throws, so the corrupt-code contract is unchanged.

PPSA26344 hits this: unsupported MIMG opcode 229 (0xE5), issue boykopovar#1941. The
failing word is reproducible offline from the title's own shader store
(cache_ps5/all_shaders.xpps, code word 260), which is how the fixture was
checked before it was written.

Scope: this defers the scalar-family and MIMG opcode gaps. The vector (VOP*)
and memory (SMEM/MUBUF/MTBUF/FLAT/DS) opcode lookups, the modifier-level
"not supported" throws and the malformed/truncated throws still abort.
Widening the deferral to those is a policy call for the maintainers.

Stacked on boykopovar#1836 (the deferral machinery); re-stacked on its current head so
this diff no longer carries boykopovar#1836's earlier broad catch. Once boykopovar#1836 lands only
the decode change remains.

Written with AI assistance.
SleglJan pushed a commit to SleglJan/AnyPS5 that referenced this pull request Oct 10, 2026
…preparable-shaders

fix(agc): defer shaders that cannot be prepared at registration to their first use
SleglJan pushed a commit to SleglJan/AnyPS5 that referenced this pull request Oct 10, 2026
…orting

The registered-shader decode ran before the deferral decision, so a single
instruction the decoder does not implement ended the title at registration
instead of deferring the shader to its first use.

Give the decoder's unsupported-instruction failures their own type
(UnsupportedInstructionError): the five family-scoped SOP opcode defaults and
the MIMG opcode lookup (which now names the opcode) raise it, and the decode in
PrepareRegistered defers the shader with that reason when it hits one. Any
other exception still throws, so the corrupt-code contract is unchanged.

PPSA26344 hits this: unsupported MIMG opcode 229 (0xE5), issue boykopovar#1941. The
failing word is reproducible offline from the title's own shader store
(cache_ps5/all_shaders.xpps, code word 260), which is how the fixture was
checked before it was written.

Scope: this defers the scalar-family and MIMG opcode gaps. The vector (VOP*)
and memory (SMEM/MUBUF/MTBUF/FLAT/DS) opcode lookups, the modifier-level
"not supported" throws and the malformed/truncated throws still abort.
Widening the deferral to those is a policy call for the maintainers.

Stacked on boykopovar#1836 (the deferral machinery); re-stacked on its current head so
this diff no longer carries boykopovar#1836's earlier broad catch. Once boykopovar#1836 lands only
the decode change remains.

Written with AI assistance.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* build

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants