Skip to content

feat(shader): link compute shaders' user-data callees at dispatch - #3294

Merged
adriadam10 merged 2 commits into
boykopovar:mainfrom
kr4y4:feat/shader-compute-user-data-calls
Oct 11, 2026
Merged

adriadam10 merged 2 commits into
boykopovar:mainfrom
kr4y4:feat/shader-compute-user-data-calls

Conversation

@kr4y4

@kr4y4 kr4y4 commented Oct 11, 2026

Copy link
Copy Markdown
Contributor

What

Closes #3271.

A compute shader that calls through a user-data pointer pair with s_swappc_b64 can't be built today. #1836 defers it at registration, but a dispatch of it still throws computed/data-dependent s_swappc_b64 call target ... is not statically resolvable. These shaders come from Ghost of Yōtei, Astro Bot, Epic Mickey and PPSA15186 (#1817, #2207), Stray (#2749, 371 shaders) and the free Unity catalog title PPSA28319 (68 of its 250 compute shaders, #3271).

This links each callee at dispatch, which is when its address is known.

  1. Finding the calls. FindUserDataCalls (GraphBuilder) returns each s_swappc_b64 whose source pair:

    • is a user-data pair that nothing writes before the call, or
    • was copied from such a pair by s_mov_b32 × 2 or s_mov_b64 in the same block.

    A user-data register written later counts only if no branch from after the write leads back to the copy.

  2. Linking, in InvocationFor. The driver reads each callee from guest memory at the user-data address, up to its s_setpc_b64 on the link pair (UserDataCalleeLength). LinkUserDataCalls then:

    • cuts the caller after its last instruction;
    • appends one copy of the callee per call;
    • rewrites each call to s_call_b64 to its copy (same link value, pc + 4);
    • ends the code with s_code_end.

    The prepared source is the linked code, so a different callee gets a different prepared entry; InvocationFor checks the prepared code before reusing an entry. Shaders without such calls are unchanged.

  3. Calls sharing a link pair (GraphBuilder). These shaders make up to 13 calls, all through s[14:15], writing it with s_mov before each one. Calls may now share a link pair when their regions are disjoint, each closed by the first return after its target. Outside every region the pair may be overwritten whole (s_mov_b32/s_mov_b64, an SOP2 result, a scalar load), but not read. A single call keeps its current rules, except that those overwrites are allowed outside its region once it has exactly one return.

Refused, with a message that names the shader and the call:

  • a callee that reads its program counter, makes a call, ends the program, jumps through another pair, returns before code it branches to, is unreadable, or is longer than 64 KiB;
  • a caller that reads its program counter, since the words after its last instruction are dropped. In the Unity shaders those words are metadata starting with sl00.

FindUserDataCalls, UserDataCalleeLength and LinkUserDataCalls are added to shaderCacheReviewedNames. They only build the code that goes into the RecompileRequest, and the disk cache already keys on that code. TechnicalDebt gets an entry for the user-data callees and an update to the static scalar call rules.

Tested

Windows 11 (26200), WinLibs GCC 15.2.0 r7, CI configure flags, main 304c745.

New tests:

  • scalar_call gains CheckUserDataCalls:

    • finding the calls, and the cases it must not find: a user-data register overwritten before the copy or inside the loop, a branch target between the copy and the call, a source pair equal to the link;
    • the callee length and each of its refusals;
    • the linked words, the control-flow graph of the linked shape (two callees through a shared s[14:15], inside a loop) and its translation;
    • the GraphBuilder refusals: the link read outside the regions, the link written inside a region, two calls into one copy.
  • agc_driver_compute_user_data_calls (new, and _registered) dispatches through the driver (PM4 SET_SH_REG and DISPATCH_DIRECT). The compute shader's loop calls two callees through s[0:1] and s[2:3] three times. It checks all 64 lanes:

    • for 3 × (5 + 64);
    • then with the first callee replaced, for 3 × (1 + 64);
    • then with one callee behind both pointers.

    It runs unregistered at wave64 and wave32, and registered at wave32, where the shader is deferred at registration as on main. Last, an unmapped callee address must fail with is not readable.

Results:

Each of these mutations makes a test fail on the RTX 4070:

mutation failing tests
no linking in InvocationFor both new GPU tests: not statically resolvable
every call linked to the first callee GPU tests (lane 0 is 30, expected 207) and scalar_call
InvocationFor reuses a prepared entry without comparing the linked code GPU tests: the first callee replaced ... lane 0 is 207, expected 195
calls sharing a link pair refused GPU tests and scalar_call
no check that a user-data register is unchanged until its copy scalar_call
no same-block check for the copy scalar_call

Conflict check against the 10 open PRs that touch ShaderRegistry.cpp: this adds no conflicting file to any of them. None of them touch GraphBuilder or ScalarCall.cpp.

tools/check_conventions.py --base origin/main passes.

Not run: a title. I have no PS5 dumps, and the catalog title isn't run here. The 68 Unity shaders are only known to reach this call: their callees come from the title at run time.

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 (none added)
  • Generic behaviour, not specific to one title
  • Depends on: none
  • AI-assisted: yes

🤖 Generated with Claude Code

@AchrafReyani

Copy link
Copy Markdown
Contributor

Windows run, since the PR has no Windows results yet. Windows 11 (10.0.26200), WinLibs GCC 15.2.0 r7, Release, NVIDIA RTX 4070 Laptop GPU (driver 616.92), head 62214968 merged onto current main, with VK_LOADER_DRIVERS_DISABLE=*dzn* (this machine's Dozen ICD crashes on its own, #3255).

  • It builds cleanly. The only warnings are nodiscard ones that main already has (tests/Graphics.cpp, AttributeInstructions.cpp).
  • ctest -R "^agc_|video_out|^shader|recompiler": 359/359 pass. The VideoOut flip tests skip as usual on this machine.

@github-actions github-actions Bot added build shader core/shader docs agc libSceAgc* labels Oct 11, 2026
@Mouhand-Kaddo

Copy link
Copy Markdown
Contributor

Additional platform validation of standalone PR head 62214968eb1a3a2c0de93f5bf897ac412148a55f (2026-10-11):

Linux: passed — Arch Linux in WSL2 (kernel 6.6.87.2), GCC 16.2.1 20260810; CMake 4.3.1 / Ninja; Release, BUILD_TESTING=ON.

6 checks, 0 failures, 0 CTest skips. Built the selected targets and dependencies; this is focused validation, not a full build or full-suite run.

Checks: agc_shader_disk_cache, agc_shader_control_flow, agc_driver_compute_user_data_calls, agc_driver_compute_user_data_calls_registered, scalar_swappc, scalar_call.

Command after building: ctest --test-dir <build> --verbose --no-tests=error --timeout 30 -R "^(scalar_call|scalar_swappc|agc_driver_compute_user_data_calls.*|agc_shader_control_flow|agc_shader_disk_cache)$".

Vulkan: Mesa lavapipe 26.2.4 / LLVM 23.1.1 software Vulkan; no native Linux hardware-GPU result.

No title/gameplay validation. These results cover the standalone head, not a merge with current main.

@adriadam10
adriadam10 force-pushed the feat/shader-compute-user-data-calls branch from 6221496 to f84cee1 Compare October 11, 2026 07:29
@adriadam10

Copy link
Copy Markdown
Collaborator

Checked the build and the new tests (scalar_call, agc_driver_compute_user_data_calls and _registered, plus the prepared-shader, swappc, control-flow and disk-cache tests) on a lavapipe/RADV host: all pass.

Two things added in our own commit on top of your branch:

  • removed the code comment above LinkUserDataCallees in ShaderRegistry.cpp (CONVENTIONS: no code comments);
  • added a bullet to docs/dev/ARCHITECTURE.md describing that user-data callees are linked at dispatch in InvocationFor.

Merging once CI is green.

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 11, 2026
@adriadam10

Copy link
Copy Markdown
Collaborator

Rebased onto current main (only conflict: our docs/dev/ARCHITECTURE.md bullet next to the new pipeline-library one) and re-ran scalar_call, agc_driver_compute_user_data_calls (+ _registered), scalar_swappc, agc_shader_control_flow, agc_shader_disk_cache: all pass. Merging once CI is green.

@adriadam10
adriadam10 force-pushed the feat/shader-compute-user-data-calls branch from f84cee1 to 34107df Compare October 11, 2026 07:51
@github-actions github-actions Bot added needs rebase Conflicts with main, rebase needed and removed needs rebase Conflicts with main, rebase needed labels Oct 11, 2026
kr4y4 and others added 2 commits October 11, 2026 09:59
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
@adriadam10

Copy link
Copy Markdown
Collaborator

Rebased onto current main again (only conflict: docs/dev/TechnicalDebt.md, next to the new font writing-line entry) and re-ran scalar_call, scalar_swappc, agc_driver_compute_user_data_calls (+ _registered), agc_driver_scalar_swappc (+ _fetch), agc_shader_control_flow, agc_shader_disk_cache: all pass. Merging once CI is green.

@adriadam10
adriadam10 force-pushed the feat/shader-compute-user-data-calls branch from 34107df to 8c619f1 Compare October 11, 2026 08:03
@adriadam10
adriadam10 merged commit f60923b into boykopovar:main Oct 11, 2026
14 checks passed
0xcrstn added a commit to 0xcrstn/AnyPS5 that referenced this pull request Oct 14, 2026
…s dispatches link

A compute program whose s_swappc_b64 calls a function address held in user
data cannot be prepared at registration, so each one was recompiled on the
queue thread at its first dispatch. PPSA23566 (UFC 6) defers 104 such
programs, all calling from the same program counter, at 0.3 to 2 s each,
and the title's 1 s frame wait expired behind them.

Such a program now keeps its registration plan. When a dispatch links a
callee (boykopovar#3294), the callee is remembered by user data and link register
(at most four per pair), and every waiting program whose calls all have a
remembered callee is linked with the newest ones and prepared on the
registration pool. A dispatch of a program being prepared waits for it as
for any registration preparation; a callee that differs at the dispatch is
linked and prepared there as before. APS5_NO_CALLEE_PREPARE=1 turns it off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
0xcrstn added a commit to 0xcrstn/AnyPS5 that referenced this pull request Oct 14, 2026
…s dispatches link

A compute program whose s_swappc_b64 calls a function address held in user
data cannot be prepared at registration, so each one was recompiled on the
queue thread at its first dispatch. PPSA23566 (UFC 6) defers 104 such
programs, all calling from the same program counter, at 0.3 to 2 s each,
and the title's 1 s frame wait expired behind them.

Such a program now keeps its registration plan. When a dispatch links a
callee (boykopovar#3294), the callee is remembered by user data and link register
(at most four per pair), and every waiting program whose calls all have a
remembered callee is linked with the newest ones and prepared on the
registration pool. A dispatch of a program being prepared waits for it as
for any registration preparation; a callee that differs at the dispatch is
linked and prepared there as before. APS5_NO_CALLEE_PREPARE=1 turns it off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* build docs needs rebase Conflicts with main, rebase needed shader core/shader

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Recompiler: compute s_swappc_b64 calls through a user-data pointer can't be built (free repro: Unity catalog title, 68 of 250 shaders)

4 participants