Skip to content

perf(agc): draws that only store to a buffer continue one render pass - #7340

Merged
oneandonlydean merged 4 commits into
boykopovar:mainfrom
teepean:perf/write-only-pass-continuation
Oct 20, 2026
Merged

oneandonlydean merged 4 commits into
boykopovar:mainfrom
teepean:perf/write-only-pass-continuation

Conversation

@teepean

@teepean teepean commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

What

A guest buffer the recompiler classifies as only stored to (bufferWritten, not bufferRead, not bufferAtomic), which no other allocation of the draw overlaps, is now a write-only range of the draw (PassAccess::writeOnly). It conflicts with every read and every other write of the open render pass, but not with the write-only stores of other draws to the same range. The guest GPU leaves stores of different draws to such a buffer unordered as well, since no barrier separates them.

Before, such a buffer counted as both read and written, so consecutive draws storing to it each began a new render pass with two full barriers. Unreal Engine 4 stores virtual texture feedback from every base-pass material that samples a virtual texture into one buffer per frame. In Stray (PPSA02100) this change takes the render passes begun from 33.8 % to 7.5 % of draws.

  • PassHazards: a write-only range conflicts with reads (read after write), with read-write uses (write after write) and with unknown reads or writes, in both directions; write-only ranges of different draws over the same bytes do not conflict.
  • Queue 0 skips ACQUIRE_MEM and the partial- and cache-flush EVENT_WRITE packets without ending the open render pass, while on the guest GPU such a flush orders the stores before it against the stores after it. At such a packet after a draw, PassHazards::SealWriteOnly() (through VulkanDevice::SealPassWriteOnly and Recorder::SealPassWriteOnly) moves the open pass's write-only ranges into its writes, so a later write-only store to the same bytes begins a new pass. While the draw pipeline has draws in flight, the seal is queued behind them.
  • ShaderResources marks a written guest buffer allocation write-only when its binding element is stored to, neither read nor atomic, and no other allocation of the build overlaps it (WriteOnlyRanges).
  • Draw.cpp takes those ranges out of the draw's own buffer reads and writes that it hands to PassHazards, and passes them as the draw's write-only ranges, unless the draw has unknown memory accesses. Indirect argument ranges and any other read added to the pass reads stay reads.
  • APS5_NO_WRITE_ONLY_PASSES=1 restores the previous behaviour (listed in docs/dev/ENVIRONMENT.md).
  • Test: agc_pass_hazards_tests covers write-only stores continuing the pass, and a read, a read-write use, unknown reads and unknown writes after them, in either order. For the seal, it checks that a write-only store over sealed write-only stores is a write after write, that write-only stores elsewhere still continue the pass, and that Clear removes the seal.

Based on main 0bc3003, which has #7184 (a draw that only reads through the BDA table continues its pass); the two changes share the PassAccess line of recordDraw. The rebase from f1af6c5 had one conflict, next to the packet counter that #6012 added after landPendingWaits in PacketExecution.cpp; both lines stay. A draw with unknown memory accesses, BDA reads included, keeps its stored buffers as read and written, as before, and unknown reads conflict with write-only ranges in either order. No open PR changes PassHazards. tools/pr_overlap.py at this head finds changed lines shared with #5431 (two lines of Draw.cpp next to the pass hazard check), #6566 (the passReads line of Draw.cpp) and #7618 (two lines of ShaderResources.cpp). #7618 stages write-only buffer elements of sweeping dispatches; it doesn't touch render passes or PassHazards. It couldn't compare 60 open PRs that share the changed files, because they conflict with main; none of them implements write-only passes or a flush seal. #6175 adds in-place draw inputs to the pass reads; they stay reads with this change.

Tested

Linux (CachyOS, kernel 7.2.10), Ryzen 9 9950X, RTX 3090 (NVIDIA 615.78.08), GCC 16.2.1, Release, 3840x2160.

On main f1af6c5 with the scx_lavd scheduler, in two batches: main against 95e7314 (the head before the seal, with two builds of other PRs in the same rotation), then 95e7314 against this head.

main f1af6c5, scx_lavd frame p50 (ms) GPU busy per present (ms)
main 205.5, 171.3, 217.7, 212.2 91.9, 91.8, 98.2, 91.1
95e7314, first batch 191.8, 150.6, 189.2 69.3, 64.5, 66.4
95e7314, second batch 211.7, 224.2, 186.0 66.9, 73.7, 62.3
this head (d92108270), second batch 205.2, 196.9 68.5, 68.7

All builds are slower on f1af6c5 than on b51c65d here, main included. That comes from #6632's hand-off spin on this machine, not from this change: in alternating runs, main f1af6c5 took 201.0 ms with the spin and 165.8 ms with APS5_HANDOFF_SPIN_US=0, and main b51c65d 167.2 ms.

Earlier runs, from before the last rebase: this change at 64dd0ed1d on main b51c65d with the scx_lavd scheduler, and at 8250eec36 on main 864729d with the EEVDF scheduler. 95e7314 differs from 64dd0ed1d only by the rebase and by keeping the indirect argument ranges as reads.

main b51c65d, scx_lavd frame p50 (ms) GPU busy per present (ms)
main 173.1, 170.6, 166.0 98.3, 98.7, 99.8
this change (64dd0ed1d) 161.6, 165.0, 167.7, 162.3 62.6, 65.8, 66.8, 63.6
main 864729d, EEVDF frame p50 (ms) GPU busy per present (ms)
main 163.4, 163.1, 164.3 112.5, 112.3, 111.9
this change (8250eec36) 162.8, 160.7, 161.8 75.8, 79.3, 75.7

Left out: runs that hit the #5372 import race (about 1 fps for the whole window: one run of 95e7314 and one of this head on f1af6c5, one main run on b51c65d, and one run of each build on 864729d), and the warm-up runs (on f1af6c5 main 188.7 ms / 92.5 ms, 95e7314 175.3 / 62.0 and this head 214.1 / 70.6; main 157.4 / 89.5 and this change 165.1 / 64.8 on b51c65d; 164.0 / 115.1 and 152.5 / 69.5 on 864729d). Stray is CPU-bound in this scene (queue worker 0 is 97 to 98 % busy in every run), so the GPU busy time per present falls by about a third while the frame time falls by 1 to 3 %, which is close to the spread between runs.

Render passes, from one more run of each with APS5_PROFILE_DRAW=1 on b51c65d (mean of the last 8 windows of 10 s): main began a pass for 76,058 of 224,986 draws per 10 s (33.8 %), this change for 17,200 of 228,238 (7.5 %). On f1af6c5 (mean of the windows in the scene): main 66,978 of 197,656 (33.9 %), 95e7314 15,990 of 212,286 (7.5 %), this head 15,688 of 206,704 (7.6 %). In this title the seal begins no extra pass: a build with counters (not part of this PR) found no write-only stores in the open pass at any of the about 54,000 seals per 10 s in the scene.

Frame dumps (APS5_DUMP_FRAMES=40 APS5_DUMP_FRAMES_EVERY=104, 960x540, 30 frames per run) from one run of main b51c65d, one of this change (64dd0ed1d) and a second one of main: this change and the second main run differ by 0.15 per 8-bit channel on average over frames 5 to 29, and by at most 0.86 in one frame, spread over edges and lights like TAA noise. The first main run reached the scene earlier, so its frames differ from both of the others by much more.

Traces, summaries and digests: https://github.com/teepean/AnyPS5/releases/tag/perf-write-only-passes-20261018 (each run's trace, aps5-summary.jsonl and digest.md, the [draws] lines of the profiled runs, and frame 28 of the dumps with its difference image; the f1af6c5 runs are in summaries-f1af.zip). The tools/trace_digest.py output, with its summary line, for the first scored run of main f1af6c5 and for d92108270 in the second batch (the traces are aps5-trace-f1af6c591588-1386863.json.gz and aps5-trace-d92108270794-1494022.json.gz on the release):

`main` f1af6c5 (run xm1)

seconds 185:20 from present 2772: 105 presents, frame p50 205.5 / p90 220.7 / p99 247.1 / max 255.6 ms, GPU busy 91.9 ms per present
NVIDIA GeForce RTX 3090, NVIDIA 615.78.08, Linux 7.2.10-1-cachyos, 3840x2160, commit f1af6c5, dropped: yes

{"commit":"f1af6c591588","dirty":false,"env":{"APS5_PAD_INPUT_FILE":"<path>","APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"185:20"},"gpu":"NVIDIA GeForce RTX 3090","driver":"NVIDIA 615.78.08","os":"Linux 7.2.10-1-cachyos","resolution":"3840x2160","window":{"spec":"seconds 185:20","first_present":2772,"ms":20008.453},"presents":105,"frame_ms":{"p50":205.544,"p90":220.71,"p99":247.085,"max":255.632},"gpu_busy_ms_per_present":91.86,"worker_busy_pct":{"queue worker 0x0":97.939,"queue worker 0x20":98.998},"top_phases_ms_per_frame":{"Driver.Packet":374.453,"Driver.Packet.memory_wait":188.129,"Driver.Packet.draw":131.618,"Driver.Draw":130.811,"Driver.Draw.shader_compile_and_link":79.509,"Shader.DrawStage":69.18,"Shader.DrawStage.materialize":38.748,"Driver.Draw.draw_enqueued":36.661},"trace_dropped":true,"trace_dropped_threads":{"queue worker 0x0":{"events_full_after_s":4.136,"events_lost":1001669,"totals_full":false}},"trace":"aps5-trace-f1af6c591588-1386863.json.gz"}

Slowest frames (largest slices inside each, inclusive)

  • present 2817: 255.6 ms at 9310 ms: Driver.Packet (queue worker 0x20) 255.6, Driver.Packet.memory_wait (queue worker 0x20) 255.4, batch (gpu) 92.3, VideoOut.Flip (presenter) 4.4
  • present 2789: 247.1 ms at 3354 ms: Driver.Packet (queue worker 0x20) 247.0, Driver.Packet.memory_wait (queue worker 0x20) 246.8, Driver.Packet (queue worker 0x0) 198.9, Driver.Packet.draw (queue worker 0x0) 160.9
  • present 2828: 225.3 ms at 11396 ms: Driver.Packet (queue worker 0x20) 225.3, Driver.Packet.memory_wait (queue worker 0x20) 224.8, batch (gpu) 94.8, VideoOut.Flip (presenter) 4.4
  • present 2813: 224.7 ms at 8452 ms: Driver.Packet (queue worker 0x20) 224.7, Driver.Packet.memory_wait (queue worker 0x20) 224.2, batch (gpu) 95.1, VideoOut.Flip (presenter) 3.9
  • present 2773: 223.8 ms at 221 ms: Driver.Packet (queue worker 0x20) 223.7, Driver.Packet.memory_wait (queue worker 0x20) 223.4, Driver.Packet (queue worker 0x0) 170.9, Driver.Packet.draw (queue worker 0x0) 145.4

Threads (ms per frame)

thread busy wait untraced in slices < 20 us largest
queue worker 0x0 29.8 (16 %) 0.1 (0 %) 160.7 46.6 Driver.Packet 29.7, Driver.EndSubmission 0.1
presenter 0.6 (0 %) 4.3 (2 %) 185.7 0.1 VideoOut.Flip 4.8, Driver.Present 0.1
queue worker 0x20 0.4 (0 %) 188.2 (99 %) 2.0 0.1 Driver.Packet 188.6, Driver.EndSubmission 0.0
gpu 91.8 (48 %) 0.0 (0 %) 98.7 0.0 batch 91.8

Largest slices (ms per frame, inclusive, all threads): Driver.Packet 218.3, Driver.Packet.memory_wait 188.1, batch 91.8, Driver.Packet.draw 24.4, Driver.Draw 24.3, Driver.Draw.shader_compile_and_link 12.0, Shader.DrawStage 7.2, Driver.Present 4.8, VideoOut.Flip 4.8, VideoOut.Flip.present 4.8

Longest waits

  • Driver.Packet.memory_wait (queue worker 0x20) 250.2 ms at 9228 ms, present 2816
  • Driver.Packet.memory_wait (queue worker 0x20) 247.5 ms at 3269 ms, present 2788
  • Driver.Packet.memory_wait (queue worker 0x20) 222.9 ms at 11529 ms, present 2828
  • Driver.Packet.memory_wait (queue worker 0x20) 222.7 ms at 8364 ms, present 2812
  • Driver.Packet.memory_wait (queue worker 0x20) 222.2 ms at 6656 ms, present 2804
  • Driver.Packet.memory_wait (queue worker 0x20) 222.2 ms at 133 ms, present 2772
  • Driver.Packet.memory_wait (queue worker 0x20) 221.3 ms at 18121 ms, present 2867
  • Driver.Packet.memory_wait (queue worker 0x20) 221.2 ms at 355 ms, present 2773
  • Driver.Packet.memory_wait (queue worker 0x20) 221.2 ms at 579 ms, present 2774
  • Driver.Packet.memory_wait (queue worker 0x20) 218.5 ms at 17686 ms, present 2865

Largest GPU batches (GPU ranges inside, and the CPU slices that recorded each, inclusive)

  • batch 7512: 4.0 ms at 11006 ms, present 2825: GPU no ranges; recorded by no CPU slices
  • batch 10695: 4.0 ms at 14849 ms, present 2848: GPU no ranges; recorded by no CPU slices
  • batch 7786: 3.9 ms at 11339 ms, present 2827: GPU no ranges; recorded by no CPU slices
  • batch 808: 3.9 ms at 1180 ms, present 2777: GPU no ranges; recorded by no CPU slices
  • batch 13565: 3.9 ms at 18571 ms, present 2869: GPU no ranges; recorded by no CPU slices
d92108270, this change before the rebase (run ss1)

seconds 185:20 from present 3322: 102 presents, frame p50 205.2 / p90 233.6 / p99 261.4 / max 272.3 ms, GPU busy 68.5 ms per present
NVIDIA GeForce RTX 3090, NVIDIA 615.78.08, Linux 7.2.10-1-cachyos, 3840x2160, commit d92108270794, dropped: yes

{"commit":"d92108270794","dirty":false,"env":{"APS5_PAD_INPUT_FILE":"<path>","APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"185:20"},"gpu":"NVIDIA GeForce RTX 3090","driver":"NVIDIA 615.78.08","os":"Linux 7.2.10-1-cachyos","resolution":"3840x2160","window":{"spec":"seconds 185:20","first_present":3322,"ms":20041.866},"presents":102,"frame_ms":{"p50":205.168,"p90":233.637,"p99":261.432,"max":272.275},"gpu_busy_ms_per_present":68.535,"worker_busy_pct":{"queue worker 0x0":98.017,"queue worker 0x20":99.137},"top_phases_ms_per_frame":{"Driver.Packet":386.518,"Driver.Packet.memory_wait":193.758,"Driver.Packet.draw":129.48,"Driver.Draw":128.672,"Driver.Draw.shader_compile_and_link":82.301,"Shader.DrawStage":71.904,"Shader.DrawStage.materialize":40.375,"Driver.Draw.draw_enqueued":32.249},"trace_dropped":true,"trace_dropped_threads":{"queue worker 0x0":{"events_full_after_s":4.285,"events_lost":950241,"totals_full":false}},"trace":"aps5-trace-d92108270794-1494022.json.gz"}

Slowest frames (largest slices inside each, inclusive)

  • present 3344: 272.3 ms at 4321 ms: Driver.Packet (queue worker 0x20) 272.1, Driver.Packet.memory_wait (queue worker 0x20) 268.9, batch (gpu) 78.6, VideoOut.Flip (presenter) 7.0
  • present 3378: 261.4 ms at 11561 ms: Driver.Packet (queue worker 0x20) 261.4, Driver.Packet.memory_wait (queue worker 0x20) 261.1, batch (gpu) 66.4, VideoOut.Flip (presenter) 5.2
  • present 3398: 253.4 ms at 15384 ms: Driver.Packet (queue worker 0x20) 253.4, Driver.Packet.memory_wait (queue worker 0x20) 253.2, batch (gpu) 61.0, VideoOut.Flip (presenter) 4.4
  • present 3357: 241.9 ms at 7043 ms: Driver.Packet (queue worker 0x20) 241.8, Driver.Packet.memory_wait (queue worker 0x20) 238.9, batch (gpu) 80.9, VideoOut.Flip (presenter) 6.9
  • present 3349: 238.4 ms at 5340 ms: Driver.Packet (queue worker 0x20) 238.3, Driver.Packet.memory_wait (queue worker 0x20) 235.6, batch (gpu) 74.4, VideoOut.Flip (presenter) 6.0

Threads (ms per frame)

thread busy wait untraced in slices < 20 us largest
queue worker 0x0 31.8 (16 %) 0.1 (0 %) 164.6 49.7 Driver.Packet 31.7, Driver.EndSubmission 0.2
presenter 0.6 (0 %) 5.3 (3 %) 190.7 0.1 VideoOut.Flip 5.8, Driver.Present 0.1
queue worker 0x20 0.4 (0 %) 194.4 (99 %) 1.7 0.1 Driver.Packet 194.7, Driver.EndSubmission 0.1
gpu 68.4 (35 %) 0.0 (0 %) 128.1 0.0 batch 68.4

Largest slices (ms per frame, inclusive, all threads): Driver.Packet 226.4, Driver.Packet.memory_wait 193.7, batch 68.4, Driver.Packet.draw 24.3, Driver.Draw 24.1, Driver.Draw.shader_compile_and_link 12.5, Shader.DrawStage 7.5, Driver.Present 5.8, VideoOut.Flip 5.8, VideoOut.Flip.present 5.7

Longest waits

  • Driver.Packet.memory_wait (queue worker 0x20) 266.8 ms at 11471 ms, present 3377
  • Driver.Packet.memory_wait (queue worker 0x20) 264.6 ms at 4238 ms, present 3343
  • Driver.Packet.memory_wait (queue worker 0x20) 254.0 ms at 15298 ms, present 3397
  • Driver.Packet.memory_wait (queue worker 0x20) 235.2 ms at 9518 ms, present 3368
  • Driver.Packet.memory_wait (queue worker 0x20) 235.0 ms at 6959 ms, present 3356
  • Driver.Packet.memory_wait (queue worker 0x20) 234.6 ms at 4506 ms, present 3344
  • Driver.Packet.memory_wait (queue worker 0x20) 234.5 ms at 15959 ms, present 3400
  • Driver.Packet.memory_wait (queue worker 0x20) 233.4 ms at 12194 ms, present 3380
  • Driver.Packet.memory_wait (queue worker 0x20) 233.3 ms at 5255 ms, present 3348
  • Driver.Packet.memory_wait (queue worker 0x20) 231.8 ms at 3129 ms, present 3338

Largest GPU batches (GPU ranges inside, and the CPU slices that recorded each, inclusive)

  • batch 8805: 6.4 ms at 12982 ms, present 3384: GPU no ranges; recorded by no CPU slices
  • batch 8659: 6.4 ms at 12749 ms, present 3383: GPU no ranges; recorded by no CPU slices
  • batch 7245: 6.3 ms at 10462 ms, present 3373: GPU no ranges; recorded by no CPU slices
  • batch 6677: 6.3 ms at 9614 ms, present 3369: GPU no ranges; recorded by no CPU slices
  • batch 11467: 6.3 ms at 16449 ms, present 3403: GPU no ranges; recorded by no CPU slices

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
  • Fixes: none
  • Unblocks: none (performance of PPSA02100, Stray (PPSA02100) on Linux #2461)
  • GPUs tested: NVIDIA GeForce RTX 3090, driver 615.78.08, Linux (CachyOS, kernel 7.2.10)
  • AI-assisted: yes

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Oct 18, 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 7689 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp L1963
PR 7978 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp L18
PR 8047 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp L592

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 1191 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 1368 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 1395 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 2434 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 4040 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 4072 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 4448 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp
PR 4472 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 5021 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 5115 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp
PR 5431 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp
docs/dev/ENVIRONMENT.md
PR 5441 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 5499 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 5609 core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp
PR 5925 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 6146 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6177 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6185 core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp
PR 6186 core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp
PR 6188 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp
core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6192 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6194 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6202 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6223 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6290 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6412 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6481 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6498 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6501 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp
PR 6539 core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp
PR 6636 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/ENVIRONMENT.md
PR 6658 core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp
docs/dev/ENVIRONMENT.md
PR 6682 core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp
PR 6683 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 6727 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 6788 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6804 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6946 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/ENVIRONMENT.md
PR 6947 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp
core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/ENVIRONMENT.md
PR 6951 docs/dev/ENVIRONMENT.md
PR 6960 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6977 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 7002 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 7085 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 7154 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 7201 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 7256 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 7292 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 7371 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 7449 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 7457 docs/dev/ENVIRONMENT.md
PR 7464 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp
docs/dev/ENVIRONMENT.md
PR 7484 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/ENVIRONMENT.md
PR 7604 docs/dev/ENVIRONMENT.md
PR 7642 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp
core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/ENVIRONMENT.md
PR 7647 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 7668 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 7800 core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 7922 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 7948 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 7963 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp
PR 8004 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/ENVIRONMENT.md
PR 8014 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 8105 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 8206 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp
docs/dev/ENVIRONMENT.md

@github-actions github-actions Bot added the agc libSceAgc* label Oct 18, 2026
@oneandonlydean oneandonlydean self-assigned this Oct 18, 2026
@oneandonlydean

Copy link
Copy Markdown
Collaborator

Thanks, the tracker change itself looks right. Write-only still conflicts with reads, read-write uses and unknown accesses in both directions, and the classification leaves out atomic and read bindings and ranges shared with other allocations. agc_pass_hazards passes here on the merge with current main.

One ordering case needs handling before this can go in. On this driver, guest sync packets never reach the recorder. Driver::execute skips EVENT_WRITE (0x46) and ACQUIRE_MEM (0x58), and nothing ends the open render pass on them. The pass hazard tracker is the only thing that orders two draws inside one pass. So with this change: draw A stores to a buffer, the guest issues PS_PARTIAL_FLUSH (or ACQUIRE_MEM), and draw B stores to the same bytes. Today that splits the pass, because both count as read-write, and B's values win. With this change, both stay in one Vulkan pass with no barrier, and the final bytes are undefined. Without a flush between them, the GPU doesn't order the two draws either, so your case (VT feedback) is fine. It's the flushed case that changes.

Could you seal the pass's write-only set when queue 0 sees a partial or cache flush EVENT_WRITE or an ACQUIRE_MEM while a pass is open? Any later write-only store to those ranges would then be a WriteAfterWrite. For example, a PassHazards::SealWriteOnly() called from the 0x46/0x58 branch. Please add a testWriteOnly case: Add(write-only), seal, then Check of the same write-only range returns WriteAfterWrite, and after Clear it returns None.

If you have it, a pass-count or write-only-draw counter (or the frame numbers from the UE4 title) would also show how often this triggers.

@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
teepean and others added 2 commits October 19, 2026 00:48
A guest buffer the recompiler classifies as only stored to (bufferWritten, not bufferRead, not
bufferAtomic), which no other allocation of the draw overlaps, is a write-only range of the draw.
It conflicts with every read and every other write of the open render pass, but not with the
write-only stores of other draws to the same range: the guest GPU leaves stores of different draws
unordered as well, since no barrier separates them. Before, such a buffer counted as both read
and written, so consecutive draws storing to it each began a new pass with two full barriers.
APS5_NO_WRITE_ONLY_PASSES=1 restores that.

Unreal Engine 4 stores virtual texture feedback from every base pass material that samples a
virtual texture into one buffer per frame: in Stray (PPSA02100) about 90 % of the pass restarts.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Queue 0 skips partial and cache flush EVENT_WRITE packets and ACQUIRE_MEM, and nothing ends the
open render pass on them, so the pass hazard tracker is all that orders two draws inside one
pass. A write-only store, a flush and a second write-only store to the same bytes stayed in one
pass with no barrier between them, although the flush orders them on the guest GPU.

At such a packet, the write-only ranges of the open pass now count as written, so a later
write-only store to them is a WriteAfterWrite and begins a new pass. The seal is queued behind
the draws in the draw pipeline (or made at once when it is idle), once per run of flushes after
a draw.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@teepean
teepean force-pushed the perf/write-only-pass-continuation branch from 95e7314 to 12d720f Compare October 18, 2026 21:54
@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.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@teepean

teepean commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor Author

@oneandonlydean thanks, done in 12d720f. The branch is rebased onto main 0bc3003; on f1af6c5 the same commit was d92108270, which the runs below use. d840b10 adds the APS5_NO_WRITE_ONLY_PASSES row to docs/dev/ENVIRONMENT.md, which the conventions check now asks for.

  • PassHazards::SealWriteOnly() moves the open pass's write-only ranges into its writes. A later write-only store to them is then a WriteAfterWrite and begins a new pass, as a read-write store would; write-only stores elsewhere still continue the pass.
  • Queue 0 seals the open pass (VulkanDevice::SealPassWriteOnly → Recorder::SealPassWriteOnly) at ACQUIRE_MEM and at the EVENT_WRITE types that Pm4::Validate accepts as partial flushes (0x07, 0x0f, 0x10) or cache flushes (0x16, 0x2a, 0x2c, 0x2e, 0x31). It seals once per run of such packets after a draw. While the draw pipeline has draws in flight, the seal is queued behind them; otherwise it is made at once under the GPU mutex.
  • testWriteOnly has the case you asked for: Add (write-only), seal, then Check of the same range → WriteAfterWrite, and after Clear → None. It also checks that a write-only range elsewhere after the seal continues the pass.
  • 721 of the 723 agc tests pass at 12d720f (agc_driver_stencil_ref_export is skipped; agc_driver_bda_device loses the device with an NVIDIA Xid 109 here, and does the same on main 0bc3003 without this change, while it passed on f1af6c5 (agc_driver_bda_device times out in vkWaitForFences since the BDA anchor lookup (#5859, d422c760c) #7584, from perf(shader): look up the BDA range of a user-data pointer once at program entry #5859's d422c76; fix(shader): leave the BDA lookup functions through one return #7571 fixes it)), and tools/check_conventions.py --base origin/main passes.

How often it triggers, Stray (PPSA02100) on main f1af6c5, standing still at the same save, one APS5_PROFILE_DRAW=1 run each, mean of the windows of 10 s in the scene: with the seal (d92108270), 15,688 of 206,704 draws per 10 s began a pass (7.6 %, 8 windows); without it (95e7314), 15,990 of 212,286 (7.5 %, 6 windows; that run reached the scene later). A build with counters (local only, not part of this PR) shows why it changes nothing there. In the scene, queue 0 gets about 82,500 sealing packets per 10 s: ACQUIRE_MEM 64,000, CS and PS partial flushes 6,600 each, EVENT_WRITE 0x2e 3,900 and 0x2c 1,350. About 54,000 of them follow a draw, so they seal the open pass. None of those passes held write-only stores at the seal, and no later write-only store hit a sealed range, so the seal began no extra pass. In the whole 240 s run, 387 seals found write-only stores, all while the scene was loading.

Frame p50, alternating runs on the same save (APS5_TRACE_SECONDS=185:20 only): d92108270 205.2 and 196.9 ms, 95e7314 211.7, 224.2 and 186.0 ms (one run of d92108270 hit the #5372 import race and is left out). That is within the spread. The traces and summaries of these runs are now on the release linked in the description, and the description has the trace digests, with their summary lines, of a main run and a d92108270 run.

AI-assisted: yes (implementation, analysis and text with Claude Code; the runs are from my machine).

@github-actions github-actions Bot added docs needs rebase Conflicts with main, rebase needed and removed perf numbers missing perf pull request without a before and after summary line labels Oct 18, 2026
@blawesom

Copy link
Copy Markdown
Contributor

Rebase handoff — rebased onto current main (all three commits, authorship unchanged). Two conflicts: Draw.cpp keeps main's ThreadOwned pass-reads form with your device-reads size capture, and ShaderResources.cpp keeps main's extent-aware addGuestBuffer call with your writeOnly classification layered after it.

Verified on Linux (CachyOS, kernel 7.2.9, GCC 16.2.1, Ninja, Release, BUILD_TESTING=ON, AMD RX 9070 XT / RADV): builds; conventions pass; full suite green apart from the four known main regressions (#5444 Mesa, #7584 BDA, #7766, #7784), none from this PR.

To pick it up:

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

Head is bab5c1ab0.

# Conflicts:
#	core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
#	core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 19, 2026
@teepean

teepean commented Oct 19, 2026

Copy link
Copy Markdown
Contributor Author

@blawesom thanks for the rebase and the full suite run. @oneandonlydean merged main into the branch in the meantime (8a77927), and it's mergeable again, so I'll keep that head rather than force-push over it.

AI-assisted: yes (text with Claude Code).

@github-actions github-actions Bot removed the unblocks title Names a title ID on the Unblocks line label Oct 20, 2026
@teepean

teepean commented Oct 20, 2026

Copy link
Copy Markdown
Contributor Author

A data point at the stock settings, since this has been open for a few days. On my local stack (main 64e2a81 plus my other local changes, all of them on showcase/stray-stack), turning this change off with APS5_NO_WRITE_ONLY_PASSES=1 takes Stray from 16.25 to 12.40 FPS, and the GPU goes from ~56 % to ~76 % busy. With the change, render passes are begun for 7.4 % of draws, against 33.8 % without it in the runs in the description. Setup: the start of the Dead City chapter, Linux, Ryzen 9 9950X, RTX 3090, stock engine.ini (4K), four rounds per build in alternating order. The gain at these settings is larger than with the lowered settings of my first runs.

The branch still merges with main. Is anything else needed from my side?

AI-assisted: yes (text with Claude Code; the runs are from my machine).

@oneandonlydean
oneandonlydean merged commit 5f7e302 into boykopovar:main Oct 20, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* docs perf Performance change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants