Skip to content

fix(agc): wait for queued registration preparations before accepting a submission - #5007

Merged
adriadam10 merged 1 commit into
boykopovar:mainfrom
gravelas:fix/agc-await-registration-preparation-at-submit
Oct 14, 2026
Merged

adriadam10 merged 1 commit into
boykopovar:mainfrom
gravelas:fix/agc-await-registration-preparation-at-submit

Conversation

@gravelas

Copy link
Copy Markdown
Contributor

What

Goat Simulator 3 (PPSA04159) stops at startup with a write of 0 at a compiled-out debug hook (app.exe+0x2511b7b): the engine's garbage collector waits for a render command fence, gives up after 10 s and reaches a fatal path. With another program loading the CPU it happened on every launch (11 of 11), without load about one launch in four.

Since registered shaders are prepared in the background (6db5c13), a draw whose shader the title registered just before its submission waits for that preparation inside the queue worker, in AwaitRegisteredPreparation. A GPU trace (APS5_TRACE_GPU=1) of a failing launch shows:

  • the third submission (1786 dwords) arrives at 1.2 s and is done at 5.1 s, after a 3.7 s wait for two registration preparations of about 1.4 s each;
  • both end-of-pipe interrupts of the submission are delivered;
  • the title never submits again; the timeout fires about 10 s later with the queue worker, the RHI thread and every task thread idle.

The title takes a submission of seconds for a hung GPU. On the console shaders are compiled ahead of time, so a submission never waits for one.

Driver::Submit now waits, on the submitting thread and before it accepts the commands, until the registration preparations queued before the call have run (RegistrationPreparePool::AwaitSubmitted: each queued task gets a sequence number, and the call returns once no task numbered at or before the newest one at the call is queued or running, so a later task finishing first does not end the wait). The queue worker then finds every such preparation settled, and the submission's GPU side stays short: the same submission is done in about 180 ms.

agc_driver_prepared_shaders gains cases: a task queued on the registration pool that sleeps 50 ms has run when AwaitRegistrationPreparation returns; and with a 300 ms task queued before the wait and a fast one queued after it began, the wait does not return before the slow task is done (a count of finished tasks fails this, because the fast task finishes first).

Tested

  • Windows 11, NVIDIA RTX 4080, AMD Ryzen 7 7800X3D, MinGW-w64 GCC 15.2.0, Release with BUILD_TESTING=ON: ctest -R "agc|shader" -j6 passes 473 of 473, as on main, and agc_driver_recorder_tests passes its default run.
  • tools/check_conventions.py --base origin/main passes.
  • Goat Simulator 3 (PPSA04159), on a local development branch with this change: with another game loading the CPU, 0 of 5 launches crashed (11 of 11 before), two 6-minute runs stayed up, and the main menu ran at the same 2.4-3.0 FPS as before. With APS5_SYNC_SHADER_PREPARE=1 instead, 0 of 3 crashed, which isolates the background wait as the cause. On current main the title stops earlier, at its first draw with clip distances (clip distances, layer, viewport or auxiliary vertex exports is unsupported (register 0x207 = 0x00400001), which feat(agc): vertex clip and cull distances #423 and feat(agc): render layered draws into 3D color targets #1077 cover), so these numbers are from that local branch: main of 2026-10-12 with feat(agc): render layered draws into 3D color targets #1077 merged.

Checklist

  • Based on current main; no other open PR implements the same functions (fix(agc): prioritize demanded shader preparation #4404 prioritizes queued preparations; see above)
  • 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

🤖 Generated with Claude Code

@xXPBSXx

xXPBSXx commented Oct 13, 2026

Copy link
Copy Markdown
Contributor

Windows test (MinGW GCC 15.2, Release, Windows 10, RTX 4080 SUPER). Built on main 2a2a3dc65 together with nine other open GPU PRs (their own commits applied, no merge commits; one further PR, #5024, conflicted with them and was left out), so only the tests that belong to this PR say something about it:

  • tests/execution/PreparedShaders.cpp, which ctest runs as agc_driver_prepared_shaders and its variants _indirect, _uncached, _debug, _debug_failure, _deferred, _deferred_undecodable, _registered_workgroup and _registered_workgroup_sync: 3 of 3 runs passed for each.

Not tested: no game run. The batch was built once, not per PR; nothing failed, so no bisect was needed.

AI-assisted (Claude Code).

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

Copy link
Copy Markdown
Contributor

Rebase handoff — rebased onto current main, authorship unchanged. This one was a real architectural merge of the preparation worker pool:

  • main's demanded-preparation prioritization (PreparedShaders* on tasks, Prioritize rotating the queue) and this PR's sequence-based await (AwaitSubmitted/AwaitRegistrationPreparation, the unfinished set) are both kept — the task struct carries sequence, function and prepared together, the demanded tasks still jump the queue with their sequence intact, and the await predicate is unchanged.
  • One subtlety worth knowing: the worker's post-run task reset must keep sequence alive for the unfinished.erase that follows — resetting the whole task would deadlock every await. That's how it's merged.

Verified on Linux (CachyOS, kernel 7.2.9, GCC 16.2.1, Ninja, Release, BUILD_TESTING=ON, AMD RX 9070 XT / RADV):

To pick it up:

git fetch https://github.com/blawesom/AnyPS5 rebase/5007
git checkout <your branch>
git merge --ff-only FETCH_HEAD

Head is f1982baa3.

@gravelas
gravelas force-pushed the fix/agc-await-registration-preparation-at-submit branch from 3ffc557 to fc32ebe Compare October 14, 2026 05:53
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 14, 2026
@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 14, 2026
@adriadam10 adriadam10 self-assigned this Oct 14, 2026
…a submission

Since registered shaders are prepared in the background, a draw whose shader
was registered just before could wait seconds for its preparation inside the
queue worker. Under CPU load Goat Simulator 3's first submissions took ~3.7 s;
the title treated the GPU as hung, stopped submitting, and its 10 s garbage
collection timeout crashed it on every launch. Driver::Submit now waits for
the preparations queued before it, on the submitting thread, before the
commands reach the GPU.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@adriadam10

Copy link
Copy Markdown
Collaborator

Rebased onto current main (conflict in ShaderRegistry.cpp with the demanded-preparation work: both kept, sequence is read before the task is reset so unfinished.erase still gets it).

Checked on Linux (RADV, Release): scoped libSceAgcDriver build + ctest 550/551, the only failure is agc_driver_tessellation (segfault, also on main). All agc_driver_prepared_shaders* pass, 15 repeats without a hang. With AwaitSubmitted turned into a no-op, agc_driver_prepared_shaders fails on "a submission did not wait for the registration preparation queued before it". Game behaviour (Goat Simulator 3) not reproduced here; relying on the reported runs. Merging once the full Build is green.

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

Labels

agc libSceAgc* docs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants