Skip to content

perf(agc): wait at submit only for the preparations of the programs a submission binds - #6867

Open
Pipodi wants to merge 2 commits into
boykopovar:mainfrom
Pipodi:perf/referenced-preparation-wait
Open

Pipodi wants to merge 2 commits into
boykopovar:mainfrom
Pipodi:perf/referenced-preparation-wait

Conversation

@Pipodi

@Pipodi Pipodi commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #6846.

What

Since #5007, Driver::Submit has waited for every registration preparation queued before the call, even those of shaders its commands never use. On a cold shader cache this held Ghost of Yōtei's first submission for 76 s, waiting on one compute shader (0x8000083400, 55,144 code dwords). That shader spent 74 s in the recompiler front end on one thread and was never needed during boot.

With this change, a submission waits only for the programs it binds. While it copies the command stream, Driver::Submit already follows indirect buffers and reads the indirect register lists. ReferencedPrograms scans those copied commands for the program address registers the submission writes:

  • SPI_SHADER_PGM_LO/HI for the pixel, geometry-back, export/vertex, hull and local stages (SH 0x008, 0x088, 0x0c8, 0x108, 0x148);
  • COMPUTE_PGM_LO/HI (SH 0x20c).

These come from SET_SH_REG packets and indirect SH register lists (0x63). The address is built the way DecodeGraphicsPrograms and the dispatch path build it.

AwaitReferencedPreparations then looks up the registered snapshots that cover each address. For each one whose preparation is still pending, it moves the preparation to the front of the queue (#4404) and waits for it on the submitting thread. The Goat Simulator 3 case from #5007 stays covered, because there the submission binds the shaders it registered just before.

A program the scan does not see still waits in the queue worker at first use, through the existing AwaitRegisteredPreparation. That covers programs bound by an earlier submission, a submission that writes only one half of an address, and an address reached another way.

The pool's sequence tracking (AwaitSubmitted, unfinished) was used only by the old wait, so it is removed.

Measured

Ghost of Yōtei (PPSA26344), RTX 4090 (driver 617.42), Windows 11 Pro 26200.

Run Submission waits for preparations First frame Frame 254 Frame 619 Frame at 240 s
run184, main 76.3 s (two submitting threads at once, until 79 s), 39.4 s at 211 s, 1.2 s at 221 s 101 s 121 s 181 s 643
run187, this PR 5, 6.1 s (longest 2.6 s, for 83 bound programs) 40 s 60 s 101 s 700
run188, this PR 5, 6.2 s (longest 2.6 s) 40 s 60 s 101 s 672
run181, main + APS5_LAZY_SHADER_PREPARE=1 0 40 s 40 s 100 s 686
run183, main + APS5_LAZY_SHADER_PREPARE=1 0 40 s 40 s 100 s 764
  • This PR vs main: frame 619 comes 80 s sooner (101 s against 181 s), the same as lazy preparation, and preparation stays eager.
  • Run-to-run variance: on main the first frame came at 181 s (run180), 80 s (run182) and 101 s (run184) in three cold runs, so the main row is one sample of a wide spread. Both runs of this PR drew their first frame at 40 s.
  • Not compared: time to the loading spinner. All cold two-lane runs of this title on this machine later hit a separate NVIDIA driver fault while creating a graphics pipeline at frames 795–812, and that is still being narrowed down.
Where main's two long waits come from (run184)
[prepare-slow] 79.3 s shader 0x8000083400 type 0: 74072 ms, 2 entries, 55144 code dwords, demanded 0
[prepare-wait] 79.3 s submission waited 76294 ms for queued preparations
[prepare-wait] 79.3 s submission waited 76300 ms for queued preparations
[prepare-wait] 210.6 s submission waited 39378 ms for queued preparations
  • The 76 s wait: a thread dump at 45 and 70 s showed the render thread and one job worker in Driver::Submit → AwaitRegistrationPreparation, one prepare thread in Structurizer::computeDominatorTree, and the other 16 idle.
  • The 39 s wait at 211 s: the level's registration burst, thousands of 0.5–2.5 s preparations across the 16 threads between 166 and 210 s. None of them was demanded.
A bounded wait, tried first (not part of this PR)

Ending the submit wait at 2 s per call (run185) still left 51 waits totaling 100 s: every submission paid the full 2 s while the giant was running. Ending it 2 s after the newest queued preparation (run186) left 16 waits totaling 20 s, with frame 619 at 101 s. That version's 2 s is a guess, and a burst of registrations still costs up to 2 s per submission. This PR waits only for what the commands bind, as suggested in #6846.

Tests

PreparedShaders.cpp replaces the old AwaitedRegistrationPreparation case with two new ones.

ReferencedRegistrationPreparation:

  • ReferencedPrograms finds a compute program bound with SET_SH_REG and a vertex program bound through an indirect register list, and ignores an address whose high register is not written.
  • Every preparation worker is held busy, then an unrelated program's preparation and a bound program's preparation are queued in that order through SubmitRegistrationPreparation. One worker is freed once AwaitReferencedPreparations has demanded the bound preparation.
  • The bound task must run first, so the wait must have moved it ahead of the unrelated one, and the unrelated one must still be pending when the wait returns. It is held by a gate, so a wrong order fails within 5 s instead of hanging. Waiting on the unrelated program's address then settles it.
  • With the PrioritizeRegistrationPreparation call removed from AwaitReferencedPreparations, this case fails.

UnrelatedPreparationAtSubmit:

  • sceAgcDriverSubmitDcb is called with a 1.5 s unrelated preparation queued, and must return in under 1 s, before that preparation runs. With the old wait it would block for the full 1.5 s. I haven't run this test against the old code.

Tested

Windows 11 Pro 26200, RTX 4090, driver 617.42. This branch at 20cc5ae on main e0e3ced:

  • Full suite: 1320 of 1320 pass, including agc_driver_prepared_shaders (4.2 s) and its indirect, deferred, lazy and registered-workgroup variants. misaligned_writer_ahead is not built: main's GpuIndirectArgumentsMisalignedWriterAhead.cpp needs 65536-byte alignment, which MinGW's object format does not allow.
  • Test update (7241b05): after the fixture change, the 13 agc_driver_prepared_shaders tests pass, and the base test passed 10 runs in a row. With the prioritization call removed, the base test fails with "the queued preparation of a bound program was not moved ahead of unrelated queued work".
  • In game: run187 and run188 above.

Technical debt

The entry "Registration preparation at submit" in docs/dev/debt/libSceAgcDriver.md now records:

  • which registers and packets the scan reads;
  • that programs it does not see wait in the queue worker at first use, so that submission's GPU side can again take as long as the preparation;
  • that programs the commands write but never draw are still waited for.

Related

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 under Silent stubs in their library's technical debt file
  • 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
  • Unblocks: PPSA26344 Ghost of Yōtei (PPSA26344) #675 (cold boot)
  • GPUs tested: NVIDIA GeForce RTX 4090, driver 617.42, Windows 11
  • AI-assisted: yes

🤖 Generated with Claude Code

… submission binds

Driver::Submit waited for every registration preparation queued before it.
On a cold shader cache, Ghost of Yotei's first submission waited 76 s for
one compute shader it never used. The submission now reads the program
address registers its commands write (SET_SH_REG and indirect SH register
lists) and waits only for those programs' pending preparations, moving
them to the front of the queue. Programs it cannot see still wait in the
queue worker at first use.

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

Copy link
Copy Markdown
Contributor

I traced the new scan against the driver paths: readRegisterLists keys each indirect list by packet offset, and draw/dispatch resolve the same program address encoding from the SH register pairs. The per-snapshot wait still prioritizes and settles a demanded registration; work not found by the scan falls back to the existing first-use wait in the queue worker. The regression exercises direct compute binding, indirect vertex binding, half-written addresses, bound-vs-unrelated preparation, and a submission behind unrelated work.

I did not find a concrete correctness issue in the diff. I attempted the focused Linux build locally, but GCC 12 stops earlier in unchanged SrtDescriptorEvaluation.cpp:183 (read.read->Flags<MemoryFlags>()), so I could not run agc_driver_prepared_shaders here. The platform CI jobs are currently skipped; I am not treating the reported Windows/Yōtei runs as my own validation.

@MarcZX10

Copy link
Copy Markdown
Contributor

One small regression-coverage gap: in ReferencedRegistrationPreparation, boundSnapshot->prepared is marked pending and then settled by SettleLater on a separate std::async thread. It does exercise the wait on a referenced snapshot while the unrelated pool task is pending, but it does not exercise the newly integrated path where the bound snapshot's own registration task is queued in RegistrationPreparePool and PrioritizeRegistrationPreparation moves that task ahead of unrelated queued work (the pool only receives the unrelated snapshot in this fixture).

Could the fixture queue the bound preparation through SubmitRegistrationPreparation(..., boundSnapshot->prepared.get()) as well, then verify that the bound task settles before a blocked/unrelated task completes? The separate queue-priority test is useful, but this would connect registry lookup, task identity, prioritization and the submission wait in one regression. I see this as coverage hardening rather than a code defect.

@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 2434 docs/dev/debt/libSceAgcDriver.md
PR 4448 docs/dev/debt/libSceAgcDriver.md
PR 4449 docs/dev/debt/libSceAgcDriver.md
PR 5058 docs/dev/debt/libSceAgcDriver.md
PR 5115 core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp
PR 5372 docs/dev/debt/libSceAgcDriver.md
PR 5431 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
PR 5925 docs/dev/debt/libSceAgcDriver.md
PR 6146 docs/dev/debt/libSceAgcDriver.md
PR 6186 docs/dev/debt/libSceAgcDriver.md
PR 6188 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
PR 6194 docs/dev/debt/libSceAgcDriver.md
PR 6218 docs/dev/debt/libSceAgcDriver.md
PR 6223 docs/dev/debt/libSceAgcDriver.md
PR 6273 docs/dev/debt/libSceAgcDriver.md
PR 6280 docs/dev/debt/libSceAgcDriver.md
PR 6300 docs/dev/debt/libSceAgcDriver.md
PR 6412 docs/dev/debt/libSceAgcDriver.md
PR 6481 docs/dev/debt/libSceAgcDriver.md
PR 6498 docs/dev/debt/libSceAgcDriver.md
PR 6548 docs/dev/debt/libSceAgcDriver.md
PR 6549 docs/dev/debt/libSceAgcDriver.md
PR 6612 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6639 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6727 docs/dev/debt/libSceAgcDriver.md
PR 6741 docs/dev/debt/libSceAgcDriver.md
PR 6743 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6801 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6804 docs/dev/debt/libSceAgcDriver.md
PR 6808 docs/dev/debt/libSceAgcDriver.md
PR 6868 docs/dev/debt/libSceAgcDriver.md
PR 6926 docs/dev/debt/libSceAgcDriver.md
PR 6946 core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6959 docs/dev/debt/libSceAgcDriver.md
PR 6973 docs/dev/debt/libSceAgcDriver.md
PR 6977 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6987 docs/dev/debt/libSceAgcDriver.md
PR 6989 docs/dev/debt/libSceAgcDriver.md
PR 6995 docs/dev/debt/libSceAgcDriver.md
PR 7002 docs/dev/debt/libSceAgcDriver.md
PR 7085 docs/dev/debt/libSceAgcDriver.md
PR 7256 docs/dev/debt/libSceAgcDriver.md
PR 7307 core/libs/prx/libSceAgcDriver/Execution/include/Driver/Queues/Submission.hpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp
docs/dev/debt/libSceAgcDriver.md
PR 7310 docs/dev/debt/libSceAgcDriver.md
PR 7446 docs/dev/debt/libSceAgcDriver.md
PR 7449 docs/dev/debt/libSceAgcDriver.md
PR 7505 docs/dev/debt/libSceAgcDriver.md
PR 7604 docs/dev/debt/libSceAgcDriver.md
PR 7618 docs/dev/debt/libSceAgcDriver.md
PR 7622 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
PR 7642 core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
docs/dev/debt/libSceAgcDriver.md
PR 7644 core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp
PR 7659 docs/dev/debt/libSceAgcDriver.md
PR 7668 docs/dev/debt/libSceAgcDriver.md
PR 7682 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
PR 7717 docs/dev/debt/libSceAgcDriver.md
PR 7800 docs/dev/debt/libSceAgcDriver.md
PR 7803 docs/dev/debt/libSceAgcDriver.md
PR 7844 docs/dev/debt/libSceAgcDriver.md
PR 7893 docs/dev/debt/libSceAgcDriver.md
PR 7910 docs/dev/debt/libSceAgcDriver.md
PR 7927 docs/dev/debt/libSceAgcDriver.md
PR 7948 docs/dev/debt/libSceAgcDriver.md
PR 7965 docs/dev/debt/libSceAgcDriver.md
PR 7978 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp
docs/dev/debt/libSceAgcDriver.md
PR 8014 docs/dev/debt/libSceAgcDriver.md
PR 8070 docs/dev/debt/libSceAgcDriver.md
PR 8083 docs/dev/debt/libSceAgcDriver.md
PR 8099 docs/dev/debt/libSceAgcDriver.md
PR 8104 docs/dev/debt/libSceAgcDriver.md
PR 8115 docs/dev/debt/libSceAgcDriver.md
PR 8121 docs/dev/debt/libSceAgcDriver.md
PR 8123 docs/dev/debt/libSceAgcDriver.md
PR 8140 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
PR 8145 docs/dev/debt/libSceAgcDriver.md
PR 8180 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp
docs/dev/debt/libSceAgcDriver.md
PR 8213 docs/dev/debt/libSceAgcDriver.md
PR 8223 docs/dev/debt/libSceAgcDriver.md
PR 8261 docs/dev/debt/libSceAgcDriver.md
PR 8281 docs/dev/debt/libSceAgcDriver.md
PR 8283 docs/dev/debt/libSceAgcDriver.md
PR 8319 docs/dev/debt/libSceAgcDriver.md
PR 8336 docs/dev/debt/libSceAgcDriver.md
PR 8365 docs/dev/debt/libSceAgcDriver.md
PR 8395 docs/dev/debt/libSceAgcDriver.md
PR 8401 docs/dev/debt/libSceAgcDriver.md
PR 8412 docs/dev/debt/libSceAgcDriver.md
PR 8419 docs/dev/debt/libSceAgcDriver.md
PR 8420 docs/dev/debt/libSceAgcDriver.md
PR 8423 docs/dev/debt/libSceAgcDriver.md
PR 8424 docs/dev/debt/libSceAgcDriver.md
PR 8426 docs/dev/debt/libSceAgcDriver.md
PR 8527 docs/dev/debt/libSceAgcDriver.md
PR 8538 docs/dev/debt/libSceAgcDriver.md
PR 8542 docs/dev/debt/libSceAgcDriver.md
PR 8563 docs/dev/debt/libSceAgcDriver.md
PR 8564 docs/dev/debt/libSceAgcDriver.md
PR 8592 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
docs/dev/debt/libSceAgcDriver.md
PR 8639 docs/dev/debt/libSceAgcDriver.md
PR 8658 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
PR 8672 docs/dev/debt/libSceAgcDriver.md
PR 8678 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
PR 8707 docs/dev/debt/libSceAgcDriver.md
PR 8735 core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp
PR 8740 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp
docs/dev/debt/libSceAgcDriver.md
PR 8871 docs/dev/debt/libSceAgcDriver.md
PR 8928 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp
PR 8932 docs/dev/debt/libSceAgcDriver.md
PR 8999 core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9017 docs/dev/debt/libSceAgcDriver.md
PR 9020 docs/dev/debt/libSceAgcDriver.md
PR 9022 docs/dev/debt/libSceAgcDriver.md
PR 9068 docs/dev/debt/libSceAgcDriver.md
PR 9077 docs/dev/debt/libSceAgcDriver.md
PR 9084 docs/dev/debt/libSceAgcDriver.md
PR 9086 docs/dev/debt/libSceAgcDriver.md
PR 9185 docs/dev/debt/libSceAgcDriver.md
PR 9186 docs/dev/debt/libSceAgcDriver.md
PR 9275 docs/dev/debt/libSceAgcDriver.md
PR 9317 docs/dev/debt/libSceAgcDriver.md
PR 9318 docs/dev/debt/libSceAgcDriver.md
PR 9335 docs/dev/debt/libSceAgcDriver.md
PR 9348 docs/dev/debt/libSceAgcDriver.md
PR 9352 docs/dev/debt/libSceAgcDriver.md
PR 9361 docs/dev/debt/libSceAgcDriver.md
PR 9387 docs/dev/debt/libSceAgcDriver.md
PR 9401 docs/dev/debt/libSceAgcDriver.md
PR 9402 docs/dev/debt/libSceAgcDriver.md
PR 9406 docs/dev/debt/libSceAgcDriver.md
PR 9412 docs/dev/debt/libSceAgcDriver.md
PR 9424 docs/dev/debt/libSceAgcDriver.md
PR 9431 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9432 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9440 docs/dev/debt/libSceAgcDriver.md
PR 9450 docs/dev/debt/libSceAgcDriver.md
PR 9508 core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9514 docs/dev/debt/libSceAgcDriver.md
PR 9533 docs/dev/debt/libSceAgcDriver.md

@github-actions github-actions Bot added docs agc libSceAgc* labels Oct 16, 2026

@jotitadev jotitadev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A Linux run of the regression, which the description (Windows) and the review above (GCC 12 stopped before it) do not have.

Machine: NVIDIA GeForce RTX 3070, driver 595.99.02, Vulkan 1.4.329, plus lavapipe (Mesa 26.0.3, LLVM 21.1.8) on the same machine. Ubuntu 26.04, i7-8700, GCC 15.2, Release, BUILD_TESTING=ON. Head 20cc5ae56 merged into main 452494439 (clean merge). The build of PreparedShaders.cpp and the changed driver sources goes through on GCC 15.2.

  • agc_driver_prepared_shaders: 3/3 pass on the RTX 3070 (Selected GPU name=NVIDIA GeForce RTX 3070) and 3/3 on lavapipe, about 3.4 to 3.9 s per run (1.1 s on main with main's test).
  • ^agc_driver_(prepared|registered), 18 tests, -j1: 18/18 on both devices, no skips.
  • No negative control: with main's Submission.* and ShaderRegistry.* the new test does not compile (ReferencedPrograms is not a member of AgcDriver::DriverDetail).

This does not address the coverage gap raised above (the bound snapshot's own task queued in RegistrationPreparePool); I did not try that variant.

Not run: Windows, AMD, the full suite, any title.

GPUs tested: NVIDIA GeForce RTX 3070 (595.99.02), lavapipe (Mesa 26.0.3, LLVM 21.1.8), Ubuntu 26.04

Written with AI assistance (Claude Code). I ran the builds and tests above on my machine.

…-wait test

ReferencedRegistrationPreparation settled the bound snapshot from a side
thread, so the pool never held its task and the prioritization in
AwaitReferencedPreparations went untested. The test now occupies every
preparation worker, queues the unrelated preparation and then the bound
one through SubmitRegistrationPreparation, and frees a single worker once
the wait has demanded the bound preparation. The bound task must run
first, the unrelated one must still be pending when the wait returns, and
a later wait on the unrelated address must settle it. Without the
PrioritizeRegistrationPreparation call the test fails.

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

Pipodi commented Oct 16, 2026

Copy link
Copy Markdown
Contributor Author

Thanks, good catch. 7241b05 now queues the bound preparation through SubmitRegistrationPreparation as well. The test occupies every preparation worker, queues the unrelated preparation ahead of the bound one, and frees a single worker once the submit wait has demanded the bound one. The bound task must run first and the unrelated one must still be pending when the wait returns. With the PrioritizeRegistrationPreparation call removed from AwaitReferencedPreparations, the test fails ("the queued preparation of a bound program was not moved ahead of unrelated queued work").

@Dextroid17

Copy link
Copy Markdown
Contributor

Timed this on Ghost of Yōtei (PPSA26344) on Linux with lavapipe, cold shader cache on every run, base and PR runs interleaved:

run  build   first frame
1    base    231 s
2    PR      181 s
3    base    211 s
4    PR      161 s

Base averages 221 s and the PR 171 s (2 runs each), so the first frame came about 50 s (23%) earlier. The ranges do not overlap (base 211–231 s, PR 161–181 s). The time comes from a memory probe that logs every 10 s, so each value can be up to 10 s after the actual first frame.

Setup: both builds are main 5d9dcc4 plus the same 11 local commits that are not in main. These are the sceNetCtlGetInfo code 12 fix (the same fix as #6227), sRGB and min/max texture copies this title needs to get past three early stops, two shader-cache checks, and the env-gated memory probe used for the timing. The PR build adds 20cc5ae and 7241b05, which cherry-picked cleanly. The two trees differ only by those two commits, and libSceAgcDriver.prx is the only library built from the files they change. -O2 build (CMAKE_BUILD_TYPE empty), lavapipe from Mesa 26.2.4 (LLVM 23.1.1), same game files, same host.

For comparison, your Windows runs had the first frame at 40 s with this PR against 101 s on main (61 s), and main ranged 80–181 s across your three runs. A 50 s gain here is in the same range. I did not measure the submit waits themselves.

AI-assisted: yes

@parl0v

parl0v commented Oct 16, 2026

Copy link
Copy Markdown
Contributor

Data point: RTX 3060 12 GB (NVIDIA 617.42), i5-12400F, Windows 10 19045, MinGW GCC Release. Minecraft Dungeons II (PPSA16064 01.100.100), title screen, no input, 200 s rows, APS5_PROFILE_DRAW=1 APS5_PROFILE_GPU=1; main 93e54ce vs main + this PR, interleaved with three other PRs in one batch, two rounds. FPS is the mean of the last 10 [present] windows, GPU busy and idle gaps per frame over the last 8.

libs FPS GPU busy idle gaps
main 6.61 / 6.61 104 / 105 ms 46 / 46 ms
+ this PR (7241b05) 6.70 / 6.60 106 / 104 ms 44 / 47 ms

Neutral on this scene (mean +0.7 %, within the row spread); no regression. 0 fatal.

AI-assisted: yes (Claude Code)

@github-actions github-actions Bot added the fix error Closes an issue (Fixes #N) label Oct 17, 2026
@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 17, 2026
@github-actions github-actions Bot added perf Performance change perf numbers missing perf pull request without a before and after summary line unblocks title Names a title ID on the Unblocks line labels Oct 18, 2026
@adriadam10 adriadam10 self-assigned this Oct 19, 2026
@MarcZX10

Copy link
Copy Markdown
Contributor

I have an independent follow-up in progress for #8470: at shutdown, finish the running library prebuild but discard queued speculative prebuilds, leaving registration preparation and device-replacement waits unchanged. The candidate touches RegistrationPreparePool::Close and the ticket/wait bookkeeping that this PR also refactors, so the current-main branch may need a rebase/adaptation if #6867 lands first. I have not opened or pushed a PR; the candidate still needs a runtime test. Please let me know if you prefer a particular merge order or want me to wait for this pool refactor before preparing the follow-up.

@CnRJay CnRJay left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The result is large and measured (Ghost of Yotei, cold cache, first frame 101 s to 40 s, first submission wait 76.3 s to under 3 s), and moving a bound program's preparation to the front of the queue (#4404) is the right use of the scan. The part I would not merge as is is the fallback.

  • #5007 made Driver::Submit wait for every queued preparation for one reason: a title that sees a submission take seconds treats the GPU as hung. Goat Simulator 3 (PPSA04159) failed a 10 s timeout when the wait sat in the queue worker. Waiting "in the queue worker at first use" is what this PR leaves for any program the scan misses: bound by an earlier submission, a half-written address, an address reached another way, or an indirect buffer the scan didn't follow. Those cases reproduce the Goat Simulator situation (the GPU side of a submission blocked on a long preparation), and the PR does not say how long such a wait can be. The cold-cache 55,144-dword shader this was found on took 74 s on one thread. If a program the scan misses is bound by a draw, the queue worker blocks for that long.
  • Please add a test where the program is reachable only through a path the scan doesn't see (address written in two halves across submissions; address in an indirect buffer), with a slow preparation, and state the resulting wait. If it is long, the scan needs the missing paths, or the fallback needs a bound (after N seconds, report and continue).
  • The 2.6 s longest wait for 83 bound programs is still a seconds-long submit. It is under the 10 s limit that failed, but the margin is a property of this title on a 4090; a slower machine's cold cache would be longer. State the worst wait by machine, or the time the scan assumes.
  • The numbers are from a tree with #6227 and two local diagnostics. The runs are cold-cache only. Post a warm-cache pair as well to show the scan costs nothing when preparations are already done (ReferencedPrograms walks every copied command on every submit).
  • Marked conflicting with main; rebase before it can be tested again.

This branch has not been deployed

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

Labels

agc libSceAgc* docs fix error Closes an issue (Fixes #N) needs rebase Conflicts with main, rebase needed perf numbers missing perf pull request without a before and after summary line perf Performance change unblocks title Names a title ID on the Unblocks line

Projects

None yet

7 participants