Skip to content

perf(agc): pipeline indirect draws behind the packet worker (APS5_PIPELINE_INDIRECT) - #1896

Draft
fisherjoey wants to merge 3 commits into
boykopovar:mainfrom
fisherjoey:perf/pipeline-indirect-draws
Draft

fisherjoey wants to merge 3 commits into
boykopovar:mainfrom
fisherjoey:perf/pipeline-indirect-draws

Conversation

@fisherjoey

@fisherjoey fisherjoey commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

What

Since #1201/#1202, queue 0's direct draws are commit items on @MichonGoddijn231849's committer. Every indirect draw still drains the pipeline and records inline on the packet worker. In #1202 Michon said indirect draws weren't on his list and offered them to us (comment). This PR does that part on top of the committer now in main.

With APS5_PIPELINE_INDIRECT=1 (off by default):

  • An indirect draw no longer drains at the start of Driver::draw.
  • GPU-side indirect draws (the device reads the arguments) are enqueued like direct draws. commitDraw records them in packet order, so the GPU reads the arguments after every earlier commit.
  • CPU-side indirect draws read their argument records on the worker. Before the read, the argument range and the count dword are checked against the writes of pending commit items, label items included (DrawPipeline::DrainIfOverlaps). An overlap drains first. The expanded records go in as one commit item.
  • A draw whose cache hit was validated against guest memory drains and translates again when any of its captured or decoded ranges overlaps a pending write. Without this, a hit could pass validation against bytes that a pending commit is about to change. Fresh captures were already covered by queryPendingWrite.
  • Pipeline depth defaults to 64 instead of 8 when the knob is on. At depth 8 the worker waited for room on most draws.
  • [draw] pipelined also reports committer busy %, indirect commits and argument-overlap drains.

Rect lists, deferred labels and APS5_LOCKED_DRAW_PREPARE keep the old drain-and-inline path.

New test: agc_driver_draw_pipeline_commits (tests/execution/DrawPipelineCommits.cpp). It checks three things. Arguments under a pending draw's write drain, and arguments elsewhere don't. Commits and a label item between two draws run in packet order, and the label can be answered while it is pending. A read over a pending item's writes drains on the queue-0 worker only.

Related: #6931 (opened 2026-10-16) queues CPU-path indirect draws on the pipeline, on by default, and keeps GPU-side indirect draws draining. Its CPU-side part overlaps this PR's CPU-side part, and the two conflict in Draw.cpp. See the last comment for where that stands.

Closes #1895

Tested

Head 8ba9dda20, on main 6a32550fd. It also merges cleanly into main 17cfb1f4c. Nobara 44 x86-64 (Linux 7.2), GCC 16.2, Release, BUILD_TESTING=ON, SPIR-V Tools off. GPU tests ran on lavapipe (llvmpipe, LLVM 22.1.8).

  • ctest -j4 --timeout 30 -R 'agc|pipelined|fill' (633 tests, 86 of them skip on lavapipe), 4 runs with the knob off and 4 with APS5_PIPELINE_INDIRECT=1. The knob-off runs: 633/633 twice, 632/633 twice. The knob-on runs: the same. The only failure in all 8 runs is agc_driver_preparation_scope_settle ("the link did not wait for the vertex shader's registration preparation"). Run alone, it failed 3 of 10 times with the knob off and 5 of 10 with it on, while another build was loading the machine. That test registers shaders and does no draws. The slowest test took 6.3 s.
  • agc_driver_draw_pipeline_commits, agc_driver_pipelined_*, the *fill tests and the indirect tests (18 tests), 10 runs with the knob off and 10 with it on: all pass every time.
  • tools/check_conventions.py --base origin/main and tools/check_test_registration.py pass.

Demon's Souls has not been rerun on this head yet. The numbers below are from the old stack (#1201/#1202 as of 10-07 plus unrelated local changes), measured on an RTX 5070 Ti (driver 595.91.07): Outpost Passage through the debug start menu, 240 s runs, APS5_PROFILE_DRAW=1, two interleaved 3+3 batches, ms per frame over the last 60 s of each run.

base APS5_PIPELINE_INDIRECT=1
batch 1 ms/frame 491.8, 491.8 (one base run booted the wrong route) 434.8, 444.4, 441.2
batch 2 ms/frame 495.9, 483.9, 485.4 434.8, 438.0, 451.1
mean 490 (2.04 fps) 441 (2.27 fps)
committer commits per 10 s, us each, busy 27k, 18 us, 5 % 121-127k, 21-23 us, 27 %
Graphics::Draw on the worker per 10 s 1.8-1.9 s 0

In those runs every indirect draw took the GPU-side path, so only the unit test covers the CPU-side branch.

Checklist

  • Based on current main; no other open PR implements the same functions (based on current main; perf(agc): queue CPU-path indirect draws on the draw pipeline #6931 overlaps the CPU-side part)
  • 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
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Unblocks: none
  • GPUs tested: lavapipe (llvmpipe, LLVM 22.1.8), Nobara 44 x86-64, on this head; RTX 5070 Ti, driver 595.91.07, for the old-stack numbers above
  • AI-assisted: yes

@Mouhand-Kaddo

Copy link
Copy Markdown
Contributor

Additional platform validation on standalone PR head ae7542cde86d1ed7b0aad19cc1865a1899a24aee:

Windows: passed — Windows 11, supported WinLibs GCC 15.2.0 UCRT/POSIX/SEH r7; CMake 4.3.1 / Ninja.

Release / BUILD_TESTING=ON: built the selected test targets; 3 CTest cases, 0 failures, 0 CTest skips.

Tests: agc_driver, agc_driver_pm4, agc_driver_graphics.

Reproduce after building the test targets: ctest --test-dir <build> --verbose --no-tests=error --timeout 30 -R "^agc_driver($|_graphics$|_pm4$|_submit$)".

Ran with APS5_PIPELINE_INDIRECT=1 to exercise indirect submission.

Vulkan: native AMD RX 6900 XT, driver 32.0.21045.5002.

Integration limitation: a local merge against main 948a526c2 has source conflicts; these results validate the standalone PR head, not a resolved merge.

@adriadam10 adriadam10 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for taking indirect draws from #1202. Not mergeable yet, for two reasons:

  • The branch carries the whole #1201/#1202/#1747 stack (55 files, 7 commits that are not yours) and conflicts with main. All the bases are still open drafts, and @MichonGoddijn231849 said on #1201 and #1747 that the stack is parked while it is ported onto the prepared-shader path from #639. This PR can only land after they do.
  • Because that port changes where draws are decoded and recompiled, the drain/overlap checks in Driver::draw may need to move with it.

What would help: keep this as a draft, and once the bases land (or the port is published) rebase onto main so the diff is only Draw.cpp, DrawPipeline.{hpp,cpp}, WriteEvidence.cpp and DrawPipelineCommits.cpp, tick "Based on current main", and rerun your Demon's Souls numbers on that head. We'll review it then.

@fisherjoey
fisherjoey marked this pull request as draft October 12, 2026 03:41
@fisherjoey

Copy link
Copy Markdown
Contributor Author

Thanks, makes sense. I've moved this to draft. Once the #1201/#1202/#1747 stack lands on main (or Michon publishes the port onto #639's prepared-shader path), I'll rebase so the diff is only Draw.cpp, DrawPipeline.{hpp,cpp}, WriteEvidence.cpp and DrawPipelineCommits.cpp, move the drain/overlap checks in Driver::draw if the port needs it, tick "Based on current main", and rerun the Demon's Souls numbers on that head.

AI-assisted: yes

@github-actions

github-actions Bot commented Oct 13, 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 1368 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 1395 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 4472 docs/dev/ENVIRONMENT.md
PR 5021 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 5371 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 5609 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp
PR 5925 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp
PR 6146 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6185 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 6186 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 6187 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/src/Driver/Dispatch/VariantValidation.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPipeline.cpp
PR 6192 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6209 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6280 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6412 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6481 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6501 docs/dev/ENVIRONMENT.md
PR 6615 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/WriteEvidence.cpp
PR 6804 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp
PR 6808 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6905 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6924 docs/dev/ENVIRONMENT.md
PR 7085 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7223 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7275 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7369 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp
PR 7410 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp
PR 7449 docs/dev/ENVIRONMENT.md
PR 7457 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 7508 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7514 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 7604 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 7622 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPipeline.cpp
PR 7642 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/VariantValidation.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPipeline.cpp
docs/dev/ENVIRONMENT.md
PR 7643 docs/dev/ENVIRONMENT.md
PR 7689 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 8004 docs/dev/ENVIRONMENT.md
PR 8089 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 8246 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 8484 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 8592 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 8707 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 8963 docs/dev/ENVIRONMENT.md
PR 9012 docs/dev/ENVIRONMENT.md
PR 9020 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9154 docs/dev/ENVIRONMENT.md
PR 9289 docs/dev/ENVIRONMENT.md
PR 9335 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp
PR 9345 docs/dev/ENVIRONMENT.md
PR 9356 docs/dev/ENVIRONMENT.md
PR 9387 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp
docs/dev/ENVIRONMENT.md
PR 9508 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/VariantValidation.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPipeline.cpp
docs/dev/ENVIRONMENT.md
PR 9671 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9676 docs/dev/ENVIRONMENT.md
PR 9712 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 9852 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 9875 docs/dev/ENVIRONMENT.md
PR 9884 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9898 core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawPipeline.hpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPipeline.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/WriteEvidence.cpp
docs/dev/ENVIRONMENT.md
PR 10043 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 10069 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 10193 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/VariantValidation.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPipeline.cpp
docs/dev/ENVIRONMENT.md
PR 10233 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 10359 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp
PR 10442 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 10443 docs/dev/ENVIRONMENT.md
PR 10472 docs/dev/ENVIRONMENT.md
PR 10668 docs/dev/ENVIRONMENT.md
PR 10678 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp
docs/dev/ENVIRONMENT.md
PR 10757 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 10867 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 10894 docs/dev/ENVIRONMENT.md
PR 10920 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 11351 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 11435 docs/dev/ENVIRONMENT.md
PR 11436 docs/dev/ENVIRONMENT.md
PR 11557 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 11948 docs/dev/ENVIRONMENT.md
PR 12066 docs/dev/ENVIRONMENT.md
PR 12120 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 12132 docs/dev/ENVIRONMENT.md
PR 12323 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 12324 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 12478 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp
docs/dev/ENVIRONMENT.md
PR 12544 docs/dev/ENVIRONMENT.md
PR 12567 docs/dev/ENVIRONMENT.md
PR 12568 docs/dev/ENVIRONMENT.md
PR 12662 docs/dev/ENVIRONMENT.md
PR 12696 docs/dev/ENVIRONMENT.md
PR 12720 docs/dev/ENVIRONMENT.md
PR 12745 docs/dev/ENVIRONMENT.md
PR 12746 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 12764 docs/dev/ENVIRONMENT.md
PR 12828 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 12840 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 12848 docs/dev/ENVIRONMENT.md
PR 12849 docs/dev/ENVIRONMENT.md
PR 12855 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 12863 docs/dev/ENVIRONMENT.md
PR 12895 docs/dev/ENVIRONMENT.md
PR 12921 docs/dev/ENVIRONMENT.md
PR 12930 docs/dev/ENVIRONMENT.md
PR 12970 docs/dev/ENVIRONMENT.md
PR 12980 docs/dev/ENVIRONMENT.md
PR 12994 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 13044 docs/dev/ENVIRONMENT.md
PR 13131 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 13148 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 13159 docs/dev/ENVIRONMENT.md
PR 13169 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 13262 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 13263 docs/dev/ENVIRONMENT.md
PR 13646 docs/dev/ENVIRONMENT.md
PR 13707 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 13793 docs/dev/ENVIRONMENT.md
PR 13809 docs/dev/ENVIRONMENT.md
PR 13858 docs/dev/ENVIRONMENT.md
PR 13863 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp
docs/dev/ENVIRONMENT.md
PR 13920 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp
docs/dev/ENVIRONMENT.md
PR 13928 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/VariantValidation.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPipeline.cpp
docs/dev/ENVIRONMENT.md
PR 13929 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/VariantValidation.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPipeline.cpp
docs/dev/ENVIRONMENT.md
PR 14051 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 14220 docs/dev/ENVIRONMENT.md
PR 14235 docs/dev/ENVIRONMENT.md
PR 14260 docs/dev/ENVIRONMENT.md
PR 14262 docs/dev/ENVIRONMENT.md
PR 14264 docs/dev/ENVIRONMENT.md
PR 14299 docs/dev/ENVIRONMENT.md
PR 14344 docs/dev/ENVIRONMENT.md
PR 14367 docs/dev/ENVIRONMENT.md
PR 14794 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp
docs/dev/ENVIRONMENT.md
PR 14838 docs/dev/ENVIRONMENT.md
PR 14904 docs/dev/ENVIRONMENT.md
PR 14909 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 14943 docs/dev/ENVIRONMENT.md
PR 15004 docs/dev/ENVIRONMENT.md
PR 15015 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 15059 docs/dev/ENVIRONMENT.md
PR 15064 docs/dev/ENVIRONMENT.md

EagleBearerYS pushed a commit to EagleBearerYS/AnyPS5 that referenced this pull request Oct 15, 2026
…ar#1896, picked onto main for a Village pair; local fast tree)

APS5_PIPELINE_INDIRECT=1 (default off) stops draining the draw pipeline
before every indirect draw. The worker reads the argument records (CPU
path) after checking them against pending commit writes and pending
labels, drains only on an overlap, and enqueues the expanded records as
one commit; GPU-side indirect draws are enqueued as they are. Validated
cache hits whose captured ranges overlap a pending write drain and
retranslate. Pipeline depth defaults to 64 with the knob on. The
[draw] pipelined line reports committer busy %, indirect commits and
argument-overlap drains.
@fisherjoey
fisherjoey force-pushed the perf/pipeline-indirect-draws branch from 8ba9dda to 94c7a1a Compare October 22, 2026 22:36
@github-actions

Copy link
Copy Markdown

This pull request is labeled perf, so it needs the summary line of a capture on main and one with the change, from the same title, scene and machine, as the Performance report form asks. Please paste both aps5-summary.jsonl lines, attach the two traces and add the output of tools/trace_digest.py.

@github-actions github-actions Bot added needs rebase Conflicts with main, rebase needed and removed needs rebase Conflicts with main, rebase needed labels Oct 22, 2026
@fisherjoey

Copy link
Copy Markdown
Contributor Author

@adriadam10 Demon's Souls numbers for APS5_PIPELINE_INDIRECT. They don't support taking this out of draft yet.

Setup: PPSA01341 v1.07, RTX 5070 Ti (595.91.07), Ryzen 5 7600X, Linux. Six 900 s runs from boot into Outpost Passage, alternating on and off. One caveat up front: these ran on our DS stack (main plus our open draw-path PRs), and that stack carries its own port of this PR's indirect path rather than the head 94c7a1aec. The port differs in one place: it doesn't pass the per-record QueuedDrawReadRanges. I checked that this difference doesn't affect the timing below. Both arms also had APS5_DRAW_CACHE_INDIRECT=1 APS5_DRAW_CACHE_PUSH_WORD=1 APS5_PIPELINE_RETIRE_ON_WORKER=1.

indirect on pipeline (3 runs) off (3 runs)
presents per 10 s, steady 24.11 26.10
queue 0 worker, ms per present 397.7 360.8
drains, ms per present 66.5 8.5
capture + recompile, us per packet 14.8 7.0

So the indirect path is about 10% slower here. No pipelined draw failed, and every one retired on the worker.

Where the time goes:

  • The drains are not the loss. Both arms drain the same number of times (about 3,300 per 10 s, mostly before dispatches and after WAIT_REG_MEM gates). The cost per drain rises because the queue worker now waits for the long GPU-side indirect draws (DRAW_INDEX_INDIRECT_MULTI with a count buffer, 7 ms on average, up to 25 ms) that it used to run inline. The extra drain time, about 1.28 s per 10 s, matches the inline time removed, about 1.30 s.
  • The real loss is the CPU-path indirect draws. The expanded loop materializes every record's patched stage, and it has no push-word forwarding, which only exists in the per-record path. With the flag on, 0 words were forwarded per 10 s, against 138,592 with it off. That doubles capture and recompile and costs about 0.5 s per 10 s. The head has the same gap, so it isn't specific to our port. This only matters together with perf(agc): forward an indirect record's patched user word as push data #9248. If both land, the expanded loop needs the forwarding as a follow-up.
  • Dispatches that drain also get slower (prologue 5.7 against 2.2 us), which adds about 0.35 s per 10 s.

With about one drain every 50 draws, the worker can't get far ahead of 15 to 25 ms draws sitting on the committer. Even with the forwarding added, I'd expect this to roughly break even on DS, not win. I'm leaving it in draft. If you want a measurement on the actual head after a restack onto main, I can do that.

AI-assisted: yes

@MarcZX10

Copy link
Copy Markdown
Contributor

Integration note: current main prints the read ranges / stores held / decode drains group twice in DrawPipeline::report; issue #10756 records the source evidence and proposed output regression. This PR also rewrites that report function, so please preserve the counter group exactly once when rebasing. I have not opened a parallel PR while the report hunk is part of this branch.

@Amoenus

Amoenus commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

@fisherjoey, following @MarcZX10's note: the focused #10756 candidate only deletes drainText += reads; from production code, plus a regression exercising the real queue/report path. Qualified fork2fc92b2e92, branch codex/fix-pipeline-profile-counters-10756, is based on main bd8e13428a. Its full native build and15 focused checks pass (3CPU,6GPU modes on each of RTX5080/Radeon). The paired duplicate-counter failing control retains its actual f264 pin; full pins and scope are in #10756. Earlier qualified checkpoints remain preserved. No upstream PR yet; the overlap check and proposed order below remain pending.

Your current report rewrite retains that append. I propose landing the small diagnostic correction independently first, then keeping the read-counter group once when this report rewrite is rebased. It does not depend on or change the indirect-draw path. Please flag any integration concern; the defect and qualification status will stay in #10756 rather than duplicate the full report here. Thanks to Marc for the independent confirmation and heads-up.

AI-assisted: yes (Codex).

Source-only follow-up: a three-way check of DrawPipeline::report at your head 94c7a1aece already conflicts with main f26434afa3 in its print/reset block. The single-line correction widens that same conflict hunk to include the read-counter preparation; it is not a clean merge. When reconciling it, retain main's decode-drain counters, lock/wake fields and ProfilePrint_nid_no_patch, add your busy metric, and emit the read-counter group once. This checks only the report function, not a full PR merge, build or title run. The small-fix-first order above remains a proposal.

TheBlenDed pushed a commit to TheBlenDed/AnyPS5 that referenced this pull request Oct 25, 2026
…like direct draws

boykopovar#1896's expanded indirect commit enqueued without the index ranges its
records read, so a deferred write-back over an index buffer could land
before the queued draw read it (bd6d47a orders this for direct draws).
Each expanded indexed record now adds its index range to the commit's reads.
@fisherjoey

Copy link
Copy Markdown
Contributor Author

@MarcZX10 @Amoenus Noted. When I rebase this branch, DrawPipeline::report will print the read ranges / stores held / decode drains group once, using main's side of the hunk, and the report will go through the asynchronous output from #9373. I'm fine with the #10756 fix landing first and will rebase over it. The PR stays in draft: our Demon's Souls numbers from 10-23 still have the pipelined path about 10% slower.

AI-assisted: yes

Under APS5_PROFILE_DRAW a [committer] line every 10 s lists how many queue-0
draws and dispatches recorded inline on the packet worker instead of on the
draw committer, by reason and PM4 opcode, and how many pipeline drains each
opcode caused with the time spent waiting for them.
…LINE_INDIRECT

With the knob on, a GPU-path indirect draw is enqueued like a direct draw and a
CPU-path indirect draw puts all of its expanded records into one commit item
instead of recording every record but the last inline under the GPU lock. The
argument-overlap check at the start of the draw still drains when a queued item
writes the records. The default pipeline depth becomes 64 with the knob on, and
the [draw] pipelined line reports how busy the committer was. The indirect draw
tests gain a variant that runs with the knob on.
A drain made for a capture or a validation read used the default opcode, so
the per-opcode drain counts could not tell which packets wait on queued
items. The drain now carries the opcode of the packet the worker is
executing.
@fisherjoey
fisherjoey force-pushed the perf/pipeline-indirect-draws branch from 94c7a1a to 2850af2 Compare October 28, 2026 09:51
@fisherjoey

Copy link
Copy Markdown
Contributor Author

Rebased onto main 35e7e21af. DrawPipeline::report now takes main's side of the hunk: the decode drain counters, lock wait, committer wakes and ProfilePrint_nid_no_patch stay, the busy percentage is added, and the read ranges / stores held / decode drains group prints once. That's the same one-line removal as #12171, so that PR should rebase over this cleanly. The [committer] line also goes through the async profile output now, per #9373. On lavapipe the indirect, pipelined and draw-pipeline ctests pass, including the eight APS5_PIPELINE_INDIRECT item tests. Still a draft; the Demon's Souls numbers haven't changed.

AI-assisted: yes

@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 28, 2026
@EagleBearerYS

Copy link
Copy Markdown
Contributor

@fisherjoey here are numbers from a second title on your 10-28 head, 2850af2, in case they help. The title, scene and machine all differ from your Demon's Souls runs, so these numbers don't speak to yours. Here the pipelined path came out ahead.

Setup. Resident Evil Village (PPSA01556), content 01.210.000, at 3840x2160.

run fps frame p50 / p90 Driver.Packet, ms per frame its memory_wait
B, run 1 0.909 1.07 / 1.30 s 3,073 1,747
A, run 1 0.657 1.51 / 1.71 s 4,297 2,370
A, run 2 0.751 1.33 / 1.51 s 3,742 2,186
B, run 2 0.859 1.14 / 1.33 s 3,245 1,854

B averages 0.884 fps and A 0.704, so B is 25.6 % faster. Both B runs are above both A runs: B's slower run is 14.4 % over A's faster one. The same-phase pairs give +38.4 % and +14.4 %.

It also composes with other open speed changes. On a tree with #12478, #12745 and #12921, merging this PR with your #9243 and #9248 gave about 24 % more fps on the same menu. All of those merges were clean, and the runs had 0 aborts and 0 failed imports. That pair doesn't separate this PR's share.

@SleglJan

Copy link
Copy Markdown
Contributor

@fisherjoey numbers from a third title on your 10-28 head 2850af2, on Linux, in gameplay rather than a menu, with alternated runs. They don't pool with your Demon's Souls runs or with the Resident Evil Village menu numbers above; the title, scene and machine differ. In the last speed series the switch read faster on both declared headlines (two earlier series read not faster, by a different window and by too few valid runs; both below), a glitch-count series read "no" in both directions, and drain time and the dispatch prologue rose as in your Demon's Souls read while capture + recompile did not change.

Setup

  • Machine: Ryzen 9 7950X3D, 64 GB DDR5-6000, RTX 3080 10 GB, NVIDIA 615.78.08, Vulkan 1.4.351, CachyOS Linux 7.2.10. Swap is off for the title's scope. The first two series ran with three monitors connected and the desktop using 424-434 MiB of GPU memory before each run; the last two ran after a reboot with one 2560×1440 monitor (143.91 Hz) and the desktop at 271-272 MiB. The remote-desktop service was stopped before every run of all four series. Nothing below is compared across that reboot.
  • Tree: main 46e54cf with perf(agc): wait on a flush only for writers over the image layers it stores #8887 (1f15e20) and perf(agc-driver): join a buffer bound from inside a range across the ranges it crosses #5372 (3086873, minus its refresh commit 662a50b) merged; the closed perf(shader): replay an image table column's decoded records when its content recurs #12051 (c5dcde9) is in the tree but switched off (APS5_NO_TABLE_COLUMN_MEMO=1). This PR's head 2850af2 merged on top without conflicts. The shader recompiler sources are untouched by the merge, so both arms ran on the same warm shader disk cache (0 misses at boot on every run; 7 of the 30 runs had 2 shader-cache misses later in the run against 3,178 hits, X 2 of 13, I 5 of 14, P 0 of 3, in series 4 one run per arm).
  • Title: 007 First Light (PPSA11386), the title's two feedback-wait limits widened in its executable as described in 007 First Light (PPSA11386): the terrain crash after Resume is a stale read of a GPU feedback buffer past the title's own wait limits #6172 (local only), scripted pad input, the AGC driver's profile logging (APS5_PROFILE_DRAW=1, APS5_TRACE_SUMMARY_EVERY=60). Each run is one boot of the title, 1,200 s, through the same scripted walk: a menu, a resume, a walk scene, then a prompt.
  • Arms: one build. X = APS5_WRITTEN_SHADOW_MAX_KIB=2048 APS5_NO_WRITTEN_SHADOW=1 APS5_NO_TABLE_COLUMN_MEMO=1 (our base's switches); I = X plus APS5_PIPELINE_INDIRECT=1 (depth 64 by the PR's default). Presents per second are counted as the title's present count over the window divided by its seconds; the (presents / s) pairs are given. Windows: scene = from frame 1621 of the run to its end; prompt FPS = from 60 frames after the prompt appears to the end; gameplay = from the gameplay start to frame 1621.
  • Tests on this build (RTX 3080): the agc suite 1,124 passed / 10 skipped / 0 failed of 1,134 (the base: 1,116 / 10 / 0 of 1,126; the 8 added are this PR's _items re-runs, all passed); the indirect, recorder, pipelined, draw-pipeline and fill tests with APS5_PIPELINE_INDIRECT=1: 121 / 0 / 0; with APS5_PIPELINED_DRAWS=32 as well: 121 / 0 / 0 (20 of those tests set their own depth).

Every series below had its rule written down before its first run, and both outcomes are reported either way.

Speed, series 4 (after the reboot): I faster on both rules

Pairs alternated and rotated, X I / I X / X I, one title boot each, all in one machine boot (22:17-00:28). Rule: "I is faster in the scene only if each I scene presents/s is above the highest X scene presents/s, over the three valid pairs"; the same form on the prompt FPS. Validity: our usual checks plus a desktop-memory bound relative to the series' first run.

run scene presents/s (presents / s) prompt FPS gameplay presents/s (recorded)
X-1 1.351 (1027 / 760) 1.333 1.169
I-1 1.467 (1144 / 780) 1.455* 1.240
I-2 1.480 (1095 / 740) 1.449 1.253
X-2 1.335 (988 / 740) 1.304 1.165
X-3 1.338 (1017 / 760) 1.310 1.125
I-3 1.461 (1096 / 750) 1.426 1.228
  • Scene: I 1.467, 1.480, 1.461 against the highest X 1.351: I faster by the rule (means 1.342 → 1.469).
  • Prompt: I 1.455*, 1.449, 1.426 against the highest X 1.333: I faster by the rule (means 1.316 → 1.443). * = that run's prompt window carried our storage-collapse flag; the figure is reported as read.
  • All six runs valid, no extra runs needed. Recorded: peak memory.used (whole GPU) per run X 9,328, 9,666, 9,260 MiB, I 9,174, 9,692, 9,475; host placements per run, lower bounds from the lines' own counts, X 0, 0, ≥1 (8 MiB), I ≥8 (61 MiB), 0, 0.

Glitches, series 3 (after the reboot): neither arm more, both rules

Same arms and alternation, 3 + 3, with a screenshot of every frame over the same 296-frame stretch of the walk in each run (coverage 1.000 on all six). A glitch frame is any shot over fixed thresholds (magenta share ≥ 1 %, magenta in the top-left quadrant ≥ 1 %, near-white ≥ 10 %, green ≥ 1 %, near-black ≥ 25 %, flat tiles ≥ 50 %, grid-edge ratio ≥ 1.10, a solid black box (≥ 0.5 % of the frame) absent from the shots 25 frames before and after, or a blackout). Rules, both ways: "I glitches more only if each I run's glitch-frame count in the band is above the highest X count", and the reverse.

  • I 1, 3, 0 against the highest X 2: no. X 0, 0, 2 against the highest I 3: no.
  • Every glitch frame in the band, on either arm, was a 1-3-frame magenta episode in the top-left quadrant, 80-94 frames after the menu's Resume (X-3, I-1, I-2). A check added after the data (recorded only, never a headline: the same per-shot rules over Resume + 45 to Resume + 340 in each run, in I-3 from Resume + 62 where its shots began) found one such 1-4-frame episode in 5 of the 6 runs (X 3, I 2), at Resume + 80 to + 95; in X-1 and X-2 it fell before the band's start. No band shot on either arm was over the near-white, green, near-black, flat-tile, grid-edge, black-box or blackout thresholds, and neither was any shot in the added window (where 35 shots near its start in X-1, X-2 and I-3 had no earlier neighbour for the box test).

Earlier series on the same build (before the reboot, stated for completeness, not pooled)

  • Series 1: three arms rotated, X / P / I (P = X plus APS5_PIPELINED_DRAWS=32), ten runs: X-3 was invalid on the exhaustion check and X-4 was its extra run, so three valid runs per arm. One headline, gameplay presents/s from the gameplay start to the scene split, rule "P (or I) is faster only if each of its gameplay presents/s is above the highest X". I 1.100, 1.215, 1.164 against the highest X 1.114: not faster by that rule (one I run below the highest X); P 1.075, 1.136, 1.033: not faster. The recorded scene and prompt figures of that series favoured I (scene means 1.269 → 1.378, prompt 1.235 → 1.349; 8 of the 9 runs' prompt figures carried the collapse flag), which is why series 2 and 4 declared those two windows as headlines.
  • Series 2: the series-4 rules with an absolute desktop-memory bound, eight runs. Three I runs fell outside that bound during the series (a desktop process grew by 10 MiB on its own), so the rule read "I not faster" on both headlines with fewer than three valid I runs; the two valid I runs were above every X on both (scene I 1.384, 1.428 vs X 1.324, 1.288, 1.207; prompt I 1.357*, 1.391* vs X 1.300*, 1.269, 1.183). The three excluded I runs, recorded: scene 1.388, 1.283, 1.396; prompt 1.368*, 1.245*, 1.371. Series 4 is the redo with the bound made relative.

Mechanism (series 4, the gameplay window, per-arm mean over the three runs, I − X; recorded, not tests)

counter X I I − X
[committer] queued per frame 1,676 1,929 +254
[committer] inline "indirect gpu" per frame 249 0 −249
queue 0x0 DRAW_INDEX_INDIRECT ms per frame 284 120 −164
room wait ms per frame 137 67 −70
committer busy % 30.9 53.8 +23.0
us per commit on the committer 160 226 +66
drains per frame / drain ms per frame 125.9 / 80 127.6 / 240 +1.7 / +160
queue 0 direct dispatch prologue, us per dispatch 327 1,162 +835
queue 0 capture + recompile, us per dispatch 157 155 −1
[lock] draw holds per frame, queue 0x0 / untagged threads (the committer) 250 / 1,052 0 / 1,292 −250 / +240
template-hit % 80.7 80.5 −0.2
validation memo misses per run 19, 19, 19 19, 25, 22
[draw] pipelined … failed (commit exceptions, counted after the series) 0 on every run 0 on every run

So on this title the switch takes about 250 GPU-side indirect draws per frame off queue 0's worker (−164 ms of DRAW_INDEX_INDIRECT packet time per frame; room waits −70 ms, at depth 64 against 8) and puts them on the committer (+23 points busy, +66 us per commit). As in your Demon's Souls read, drains per frame stay the same while their time rises (3.0× here), and the direct-dispatch prologue is longer (3.6× here; your read: 5.7 against 2.2 us). Capture + recompile per dispatch did not change here (157 → 155 us), where your read had it doubled per packet. Template-hit share 80.7 % against 80.5 %; validation memo misses 19, 19, 19 against 19, 25, 22 per run, about 8 per 100,000 lookups on both arms.

One 10-s window of each arm from the walk (its [draw] pipelined and [committer] lines), same build, same machine; X first, then I:

[draw] pipelined (10 s, depth 8): 18467 commits at 142.5 us each on the committer (26.3 % busy), 0 failed; 18472 enqueued behind 2.77 on average, waiting 67.7 us per draw for room, 0.04 us for the lock; 1009 committer wakes; 9785291 read ranges, 0 stores held, 0 decode drains; drains: packet 1368 (735.9 ms) flush 0 (0.0 ms) labels 0 (0.0 ms) capture 0 (0.0 ms) indirect 35 (61.0 ms) submission 10 (0.9 ms); packet drains by opcode: 0x15 x1085 0x93 x181 0x16 x82 0x10 x10 0xff x10
[committer] (10.0 s) queued 18323, inline 7065: indirect gpu 2433: 0x25 x2433; dispatch 2013: 0x15 x2013; dispatch indirect 2619: 0x16 x2619; drains 1386 (803.8 ms): packet 1341: 0x15 x1061 0x93 x177 0x16 x83 0x10 x10; indirect 35: 0x100 x35; submission 10: 0x100 x10;
[draw] pipelined (10 s, depth 64): 26521 commits at 202.0 us each on the committer (53.6 % busy), 0 failed; 26521 enqueued behind 16.43 on average, waiting 26.7 us per draw for room, 0.03 us for the lock; 1092 committer wakes; 11165069 read ranges, 59 stores held, 0 decode drains; drains: packet 1689 (2911.0 ms) flush 0 (0.0 ms) labels 0 (0.0 ms) capture 0 (0.0 ms) indirect 0 (0.0 ms) submission 13 (0.1 ms); packet drains by opcode: 0x15 x1382 0x93 x199 0x16 x82 0x10 x13 0xff x13
[committer] (10.0 s) queued 26122, inline 5499: dispatch 2244: 0x15 x2244; dispatch indirect 3255: 0x16 x3255; drains 1698 (2932.6 ms): packet 1685: 0x15 x1378 0x93 x198 0x16 x83 0x10 x13; submission 13: 0x100 x13;

Limits

  • We planned an exact correctness A/B through agc_frame_replay: capture ten frames of the walk with APS5_FRAME_CAPTURE, replay them with and without the switch, compare the replayed frames pixel by pixel. The two-frame feasibility capture before it stopped inside its first frame (0 frames captured): the recorder accepts a submission only after every earlier submission has finished executing, and this title's compute queues wait mid-frame on labels that a later queue-0 submission releases, so the 10 s wait expires. That is a limit of the capture on titles with such cross-queue waits, not something this PR changes; the eight _items tests and the switched test selection above are the exact checks we have.
  • One code-read observation from the diff, for you to check; we have no evidence of it on any title, and a second, independent read of the code came to the same place. A queued GPU-side indirect draw declares its index range, its guest buffers and its vertex-attribute extents as reads (QueuedDrawReadRanges) but not its argument buffer or count dword, which the GPU reads in place when the batch runs. The packet-level drain checks (keepsDrawPipeline, DrawPipeline::Overlaps) compare only against queued writes, and the early drain in Driver::draw covers writers queued ahead of the draw. So a DMA_DATA that enqueueDmaPacket declines to queue (for example a predicated one, one with a GDS source, or an unaligned copy or fill) and whose destination is the arguments or the count but overlaps no queued write keeps the pipeline; if the committer has not reached the draw yet, it runs on the worker ahead of the draw, which then reads the new values. The base never had this window for GPU-side indirect draws (they drained and drew inline); its queued direct draws have the same exposure for their index and vertex ranges, but those declared reads also hold back pending storage-image write-backs, which the arguments do not get. None of the eight _items tests places a writer after a queued draw. A test with a blocker item, a GPU-side indirect draw and then an unaligned DMA_DATA over the record would show whether the draw uses the old or the new record. Adding the arguments and the count to the item's read set would not change that path by itself, since only HeldReads reads that set; the DMA_DATA case of keepsDrawPipeline would also have to check queued reads. The second read also notes that with the arguments in the read set, HeldReads would refuse the draw's own commit-time FlushPending over them, because the item stays queued while it commits.

Our base will run with the switch on from here. AI-assisted: yes (Claude Code).

@SleglJan

SleglJan commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

@fisherjoey With this PR merged onto main d1ae628 (fetched 2026-11-03), your APS5_PIPELINE_INDIRECT=1 registrations agc_driver_pipelined_indirect_draw_items and agc_driver_indirect_patched_stage_items fail under ASan with a heap-use-after-free on the draw committer, and abort without ASan. They passed on your 10-28 head, as you reported, so the break came in from main. On the same merge without ASan, the other six _items registrations and the two executables' default registrations pass. We found this while moving our tree, which carries this PR (our 10-30 numbers), to current main.

Setup

  • main d1ae628 + this PR's head 2850af2, merged locally. There were four conflicts, one hunk each. None is in your expanded-records block (your head's Draw.cpp:1107-1217), which is byte-identical in the merge. The four:

    • the _items foreach in libSceAgcDriver/CMakeLists.txt. It sits where main moved the neighbouring registrations into tests/cmake fragments. I resolved it with tools/agc_test_split.py resolve, and the foreach stays in CMakeLists.txt;
    • the pipelined-path condition line in Draw.cpp;
    • the [draw] pipelined print in DrawPipeline.cpp;
    • the APS5_PIPELINE_INDIRECT row in docs/dev/ENVIRONMENT.md.
  • Nothing merged since, up to main 4f41f4c, touches registeredImageViews, materializeDrawStage, buildPrepare, ExpandedDraw or enqueueExpanded.

  • Arch Linux container, Linux 6.12.91, GCC 16.2.1, CMake 4.4.4, Ninja; Mesa 26.2.4 with lavapipe as the only Vulkan driver (VK_DRIVER_FILES and VK_ICD_FILENAMES set to its lvp_icd.json). Built and run with:

    cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON -DANYPS5_ENABLE_ASAN=ON
    cmake --build build
    ctest --test-dir build -R 'agc_driver_(pipelined_indirect_draw|indirect_patched_stage)_items' --output-on-failure
    

ASan (abridged; agc_driver_indirect_patched_stage_items is shown, and the other test gives the same stacks).

  • Function names are ASan's. The line numbers are the statements inside them, taken from reading the code: main d1ae628's for main's files, and your head 2850af2's inside your block. Paths are under core/libs/prx/libSceAgcDriver/.
  • READ of size 8 on the draw committer, 24 bytes into a 40-byte block: the make_shared control block that holds a std::vector<RegisteredImageView>. The stack:
    • ShaderResources::buildPrepare (Graphics/src/ShaderResources.cpp:2126, the range insert of each stage's registeredImageViews);
    • <- ShaderResources::build <- ShaderResources::ShaderResources <- resolveDrawResources <- prepareDraw <- Graphics::Draw <- VulkanDevice::Draw <- Driver::commitDraw;
    • <- your commit lambda and its [held] wrapper in finishDraw (Execution/src/Driver/Draw/Draw.cpp:1133-1139) <- DrawPipeline::run.
  • Freed by the queue worker: Driver::materializeDrawStage (Execution/src/Driver/Draw/DrawCapture.cpp:120, where StageCapture::registeredViews is replaced) <- Driver::finishDraw (your per-record call, Draw.cpp:1186) <- Driver::draw.
  • Allocated by the same worker at DrawCapture.cpp:120 (the make_shared).

Without ASan, on the same merge, both abort: [gpu] driver failure: vector::_M_range_insert, then terminate called after throwing an instance of 'std::length_error'.

What changed under the PR (from reading the code; not a tested fix):

  • e7b003a ("fix(agc): prepare owned registered window candidate", merged with feat(agc): sample 1D arrays past maxImageArrayLayers as rows of a 2D image #12246 on 11-02) added CompiledShader::registeredImageViews. It is a raw pointer to a vector owned by StageCapture::registeredViews, and every materializeDrawStage replaces that owner.
  • main's CPU record loop re-points the stages after each re-materialisation (main's Draw.cpp:1182) and draws each record before the next one. It queues the last one with its stageCaptures moved along (:1193-1213). That is why the executables pass without the switch.
  • Your expanded-records block copies stages into each ExpandedDraw (Draw.cpp:1181) and re-points only stage.program (:1195-1199). So every item carries the pointer from when the stages were built (main's Draw.cpp:961). The first record's materializeDrawStage already frees that vector, before any item is queued. The committer then reads it in buildPrepare.
  • I tried copying each item's view vectors when the items are queued (in enqueueExpanded), on an older merge of this PR (main 5cb015c, with perf(agc-driver): join a buffer bound from inside a range across the ranges it crosses #5372 less its commit 662a50b also merged). It did not help. By then the first record's materializeDrawStage had already freed the vector every item points to, so ASan's read moved onto the copy, on the worker inside finishDraw.
  • What looks needed (untested): after each record's materializeDrawStage, point that item's stage at stageCaptures[programIndex].registeredViews, and hold that shared_ptr in the ExpandedDraw. The commit lambda's moved stageCaptures keeps only the last record's.

AI-assisted: yes (Claude Code)

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* build 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 shader core/shader

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Indirect draws drain the queue-0 draw pipeline (#1202)

7 participants