Skip to content

perf(agc): dispatch-cache recipe hits as ordered commit items - #1747

Merged
adriadam10 merged 3 commits into
boykopovar:mainfrom
MichonGoddijn231849:perf/pipelined-dispatch
Oct 16, 2026
Merged

adriadam10 merged 3 commits into
boykopovar:mainfrom
MichonGoddijn231849:perf/pipelined-dispatch

Conversation

@MichonGoddijn231849

@MichonGoddijn231849 MichonGoddijn231849 commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

What

With pipelined draws (#1201, on by default since #1202), every compute dispatch on queue 0 still drains the pipeline before it runs, so the committer goes idle and the worker waits. Titles with many dispatches between draws pay that on each one: Demon's Souls issues about 3,800 queue-0 dispatches per frame in its tutorial level, most of them one chain of small ones (measured by @fisherjoey on #1202).

APS5_PIPELINE_DISPATCH=1 (off by default; needs pipelined draws) makes a DISPATCH_DIRECT whose dispatch-cache entry hits with a recipe an ordered commit item, like the draws around it. The capture and the cache lookup run on the worker; the recording runs on the committer in order. A dispatch still drains, as today, when:

  • its cache lookup misses or the entry has no recipe;
  • what it reads overlaps a write still pending in the pipeline (the read-side rule: its captured inputs would be checked against memory a queued item is about to change);
  • the hit is a verified data hit, or its writes cannot be ranged.

DrawPipeline::DrainRequestsByOpcode counts drains per opcode and DrawPipeline::QueuedItems reports the queue size, both for the tests. DispatchWriteRanges gives the commit item the dispatch's write ranges, so later draws that read them wait for it.

Tests (tests/execution/PipelinedDispatch.cpp):

  • agc_driver_pipelined_dispatch (switch off): a dispatch after an immediate fill of its input, in one DCB, over several rounds; each dispatch drains once and reads the fill.
  • agc_driver_pipelined_dispatch_item (switch on): repeated dispatches that read nothing pending do not drain; a round whose descriptor table a pending fill retargets drains and reads the new buffer. That round holds the committer behind a gate item, waits until the fills are queued, and only then submits the dispatch, so the order is exact rather than timed. With the read-side check removed it reads the buffer the table named before the fill (0x6a6a0002 for 0x6b6b0003), every run; with the commit-item path removed, the first round fails ("a dispatch that reads nothing pending drained the pipeline").

Tested

Windows 11 Pro, AMD Ryzen 5 7600, NVIDIA GeForce RTX 5080 (driver 32.0.16.1074), 32 GB, MinGW-w64 GCC 15.2.0.

  • On this branch: builds; all agc_ tests pass with the switch off and on.
  • GTA V PPSA04264 (01.005), Performance mode, the prologue bank, on my integration branch with the same change: 26.2 fps with the switch against 26.4 without, i.e. no change. GTA V issues few dispatches between draws; the expected gain is in titles like Demon's Souls, which I have not measured yet.

Checklist

🤖 Generated with Claude Code

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

Copy link
Copy Markdown
Contributor

@MichonGoddijn231849 here are the Demon's Souls numbers for APS5_PIPELINE_DISPATCH. On this title it doesn't change anything, and the reason looks like the dispatch cache rather than the pipelining itself.

Setup: Nobara 44 x86-64, RTX 5070 Ti (driver 595.91.07), Ryzen 5 7600X, Demon's Souls PPSA01341 v1.07, tutorial level (Outpost Passage) through the debug start menu. Base is main df16c4c2 + #1202 (incl. #1201, the revision before 569252b) + our local patches: #1184, #1071, a job-worker idle backoff, a higher APS5_BINDLESS_SLOTS clamp, a per-device entry point cache, shared/resident staging shadows, a pending-image overlap index, a Linux page-table cache and an NVIDIA dma-buf guard. I cherry-picked only the last commit (c7cc7b7). The one real conflict was in Dispatch.cpp, against a resolve-ahead path we carry; I kept your candidate check outside the resolve-only branch and took the rest as in the PR. All agc_driver tests pass (173/173), including both new ones.

Method: one build, the switch unset vs =1, 3 + 3 runs interleaved, 240 s each, last 8 level windows (t+150 to t+230), base env APS5_PROFILE_DRAW=1 APS5_BINDLESS_SLOTS=128. The game window was on-screen in every run.

off (3 runs) APS5_PIPELINE_DISPATCH=1 (3 runs)
level fps (batch median) 1.9 1.9
ms/frame, per run 580*, 516, 519 513, 526, 548*
ms/frame, pooled windows (excluding windows under 17 presents) 513 520
q0 DISPATCH_DIRECT us per dispatch 43.0, 42.5, 42.9 44.5, 45.6, 45.0
q0 direct dispatches / 10 s ~84-88k ~82-85k
pipeline drains for 0x15 / 10 s (pipeline non-empty) 1496, 1739, 1716 1704, 1694, 1653
committer busy 5.4 %, 5.0 %, 5.1 % 5.7 %, 5.7 %, 5.2 %
dispatch commit items / 10 s - 2001, 2545, 841
[dispatch-item] drained instead / 10 s - no recipe 81-83k, miss 3.6-4.0k, reads 0, writes 0, verify 0; 0 recipes rebuilt
GPU busy per present 207-212 ms 206-208 ms

* Run 345 lost three windows to unrelated CPU load on the box (16, 8 and 14 presents). Run 350's first window was the tail of the level load (12).

No new errors: 0/6 fatal, no GPU hangs or timeouts, and both sides show the same boot-time notes (missing debug-menu concommands, one ALSA underrun).

Why it doesn't help here: only about 2.5k of the ~85k queue-0 direct dispatches per 10 s are dispatch-cache hits. The rest miss by-value validation because the captured data differs every frame, so they never get a recipe and drain as before (~2k items per 10 s against ~82k "no recipe"). The pre-lookup scan of the cache entry runs for every candidate, which probably accounts for most of the +2 us per dispatch. The drain wait isn't where the time goes either. Dispatches here mostly come back to back, so the pipeline is already empty for all but ~1.7k of them per 10 s, and the cost is capture plus the device call on the worker. One suggestion: split "no recipe" into "validation miss" and "hit without a recipe". On this title those are very different numbers.

AI-assisted: yes.

@MichonGoddijn231849

Copy link
Copy Markdown
Contributor Author

Parked as a draft with the rest of the pipelined stack while it is ported onto #639. @fisherjoey thanks for the Demon's Souls numbers: if the dispatch cache rather than the pipelining is what limits that title, that is where I'll look after the port.

@github-actions

github-actions Bot commented Oct 13, 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
PR 6538 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPipeline.cpp L168
PR 6641 core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawPipeline.hpp L34
PR 6656 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp L87
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp L317-318

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 1341 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 1368 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 1395 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 1896 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawPipeline.hpp
core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp
core/libs/prx/libSceAgcDriver/Execution/include/Driver/Memory/DrawWriteRanges.hpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPipeline.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/WrittenBuffers.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp
PR 3891 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 4156 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 4396 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 4397 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 4422 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 4423 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 4424 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 4448 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp
PR 4520 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 4521 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 4522 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 4523 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 4566 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 4599 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 4650 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp
PR 4718 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 4740 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 4749 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 4760 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 4813 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 4891 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 5021 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 5115 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp
PR 5371 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 5426 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 5431 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp
PR 5497 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp
PR 5499 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 5656 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp
PR 5867 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawPipeline.hpp
core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp
core/libs/prx/libSceAgcDriver/Execution/include/Driver/Memory/DrawWriteRanges.hpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPipeline.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/WrittenBuffers.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp
PR 5875 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 5925 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp
PR 6044 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6054 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6188 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawPipeline.hpp
core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPipeline.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp
PR 6208 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawPipeline.hpp
core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp
core/libs/prx/libSceAgcDriver/Execution/include/Driver/Memory/DrawWriteRanges.hpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPipeline.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/WrittenBuffers.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp
PR 6215 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6292 core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp
PR 6412 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6486 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6507 core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp
PR 6529 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPipeline.cpp
PR 6615 core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp
PR 6660 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp

@MichonGoddijn231849

Copy link
Copy Markdown
Contributor Author

Rebased onto current main (92e5041); with #1201 and #1202 merged it is one commit (da1833f). What changed against main's rework:

  • main's dispatch now resolves dispatch groups ahead (resolving, fromAhead). A dispatch being resolved or adopted from a group resolved ahead keeps the drained path. The candidate check sits in main's non-resolving branch, after its fill HLE, and also excludes main's stream-copy kernel next to the copy kernel.
  • main drains the pipeline at every dispatch group to resolve it ahead (PacketExecution.cpp, the gateOpened branch). With APS5_PIPELINE_DISPATCH on, queue 0 skips that resolve, since it would drain before every group and leave nothing to pipeline. Off by default, so main's path is unchanged without the switch.
  • The item path drains only a busy pipeline, so a dispatch whose inputs main's own checks already drained for is not counted twice.
  • The test: Mutation::Add takes main's gpu flag (false, as before), and the buffer descriptors set OOB_SELECT 3 (raw), since main bounds-checks structured buffers with a zero stride.

agc|shader ctest on Windows (MinGW): 600 of 601, both agc_driver_pipelined_dispatch tests pass. The failure, agc_driver_image_plane_address_on_line, also fails on main 92e5041.

@fisherjoey, your Demon's Souls numbers pointed at the dispatch cache rather than the pipelining. On this base the queue-0 resolve-ahead skip is a new trade, so a before/after on that title would show whether the items or main's group resolve wins there.

@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 15, 2026
@adriadam10 adriadam10 self-assigned this Oct 16, 2026
MichonGoddijn231849 and others added 3 commits October 16, 2026 12:53
…IPELINE_DISPATCH)

With pipelined draws, a DISPATCH_DIRECT on queue 0 whose dispatch-cache entry hits with a
recipe runs as an ordered commit item instead of draining the pipeline first. Misses, reads
that overlap writes still pending in the pipeline, verified data hits and writes that cannot
be ranged still drain. A dispatch that is resolved ahead (or adopted from a group resolved
ahead) and copy kernels keep the drained path; with the switch on, queue 0 does not resolve
dispatch groups ahead, since that drains the pipeline at every group. Off by default.
agc_driver_pipelined_dispatch counts drains per opcode (each dispatch drains with the switch
off); agc_driver_pipelined_dispatch_item checks that repeated dispatches do not drain, and that
a dispatch whose descriptor table a pending fill retargets drains and reads the new buffer.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@adriadam10
adriadam10 force-pushed the perf/pipelined-dispatch branch from da1833f to 89333e0 Compare October 16, 2026 10:58
@adriadam10

Copy link
Copy Markdown
Collaborator

Checked on this branch merged onto current main: AgcDriver build + ctest pass (the one failure, agc_driver_tessellation, segfaults inside libvulkan_radeon.so and is unrelated to the change), and the agc tests pass on lavapipe. Both mutations you describe (read-side check removed, commit-item path removed) make agc_driver_pipelined_dispatch_item fail.

Added on your branch as our own commits:

  • test(agc): wait for the pipeline to empty between descriptor rounds: the test asserted QueuedItems() == 0 right after AgcDriverWaitIdle, which flaked under load on lavapipe (3 of 6 parallel runs failed before, 180 parallel runs pass after).
  • docs(architecture): describe dispatches queued as pipeline items: APS5_PIPELINE_DISPATCH and its drain conditions in the queue-0 pipeline paragraph.

Merging once CI is green.

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

Labels

agc libSceAgc* build shader core/shader

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants