Skip to content

perf(shader): drop window stores no relative read can see - #7560

Merged
oneandonlydean merged 2 commits into
boykopovar:mainfrom
parl0v:perf/window-store-liveness
Oct 20, 2026
Merged

oneandonlydean merged 2 commits into
boykopovar:mainfrom
parl0v:perf/window-store-liveness

Conversation

@parl0v

@parl0v parl0v commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

What

A program that uses relative VGPR addressing (v_movrels* / v_movreld*) gets a per-invocation vector window, and every write to a register inside the window is also stored into it so relative reads see the value (IrBuilder::SetVectorReg). In Minecraft Dungeons II's hottest compute programs almost none of those stores can be read: program 0x1c00843000 has 2,196 window stores and 3 relative reads, and 2,117 of the stores are overwritten or reach the end of the program before any window load could see them. They still cost GPU time. MaskedSelectEliminator counts a window store as a reader of every lane, so the EXEC merge it stores stays (95 % of the merges left in that program are kept only by a window store), and DenormalFlushEliminator then keeps the input flushes behind those merges.

  • perf: VectorWindowStoreEliminator runs after the read-lane step and before MaskedSelectEliminator. It is a backward liveness over the window slots: a constant-index LoadVectorWindow reads its slot, a dynamic one reads every slot (dynamic indexes are clamped into the window), a constant-index StoreVectorWindow overwrites its slot, and a dynamic store overwrites nothing and is never removed. A constant-index store whose slot is not live after it is deleted. The window is per-invocation Function memory that only LoadVectorWindow reads, and a window store is an unconditional OpStore of the already merged value, so no loaded value changes in any lane. The existing passes then remove the merges and flushes those stores kept alive.
  • test: agc_vector_window_tests gets a case where a window register is written twice before a relative read; it must leave as many window stores as writing it once. Without the pass it fails with "window stores: written twice 4, written once 3".

Tested

Windows 10 19045, MinGW GCC, Release.

  • Minecraft Dungeons II (PPSA16064) title screen, RTX 3060 12 GB (i5-12400F, NVIDIA 617.42), default settings, 215 s rows alternating, main 1c523bd against main plus this commit: 8.14 / 8.10 / 8.10 / 8.08 FPS against 8.28 / 8.29 / 8.25 / 8.28 (+2.1 %). GPU time per frame of the three hottest window programs: 7.9 → 7.0 ms, 6.2 → 5.3 ms, 2.55 → 2.41 ms.
  • The same requests recompiled offline (agc_shader_replay --spv): program 0x1c00843000 goes from 238,094 to 212,781 SPIR-V words and from 7,509 to 5,921 OpSelect; 0x1bc05c4000 from 212,409 to 190,772 words.
  • Sword of the Sea title screen, same card and settings, 3 rounds: 10.00 / 10.10 / 10.10 against 10.09 / 10.09 / 10.10 FPS. None of its hot compute programs use relative addressing, so nothing changes there.
  • RTX 4060 8 GB (i5-14400F, NVIDIA 617.42), APS5_TEXTURE_CACHE_MIB=3072, 4 rounds: the two hottest window programs go from 5.6 to 5.0 ms and from 4.5 to 4.0 ms per frame, but FPS stays within this box's noise (8.13 / 8.23 / 8.23 / 8.32 against 8.33 / 8.23 / 8.20 / 8.23): GPU busy drops from 96 to 95 ms and the idle gaps grow by about as much, so the frame there is set by more than the GPU.
  • RTX 4060 8 GB, rebased on main 385ed71: the 44 recompiler, window, relative-addressing, lane, DPP, denormal and masked-select tests pass, among them agc_vector_window, agc_vector_window_stage_inputs, agc_driver_relative_indexing, agc_driver_scalar_relative_indexing, agc_driver_relative_stage_inputs, agc_driver_relative_ancillary, agc_driver_vector_window_direct_access, agc_driver_vector_window_narrowing, agc_masked_select, agc_denormal_flush_elimination, agc_driver_read_lane, agc_shader_memory, the agc_structurizer variants and the DPP and denormal-mode execution tests. With the pass disabled, the new case in agc_vector_window fails.
  • agc_driver_vector_window_liveness (new, tests/execution/VectorWindowLiveness.cpp): a window register written before a scalar branch and overwritten on one side only, then read by a constant-index v_movrels after the join (both sides run), and a register read at the top of a three-iteration loop and overwritten after the read; every lane's loaded value is checked in wave32 and wave64. With liveOut ignoring the successors it fails.
  • tools/check_conventions.py --base origin/main passes.

Performance capture (as the Performance report form describes): Minecraft Dungeons II (PPSA16064) title screen, RTX 3060 12 GB (i5-12400F), NVIDIA 617.42, Windows 10.0.19045, 3840x2160 output. Only APS5_TRACE_SECONDS=120:12 set, caches warm (the title ran once on the build and was restarted), stderr not redirected. main 48d61f5 against this PR's head ca01351:

Frame and GPU times (ms):

Metric Baseline Candidate Change Change (%)
frame_ms.p50 117.162 114.665 -2.497 -2.131
frame_ms.p90 145.047 143.229 -1.818 -1.253
frame_ms.p99 158.422 164.200 +5.778 +3.647
frame_ms.max 158.422 165.107 +6.685 +4.220
mean_frame_ms 121.758 118.989 -2.768 -2.274
gpu_busy_ms_per_present 100.711 97.535 -3.176 -3.154

Top phases (ms per frame):

Metric Baseline Candidate Change Change (%)
Driver.Dispatch 85.879 82.809 -3.070 -3.575
Driver.Packet 242.204 236.692 -5.512 -2.276
Driver.Packet.device_lock 34.172 34.858 +0.686 +2.007
Driver.Packet.dispatch 86.382 83.295 -3.087 -3.574
Driver.Packet.memory_wait 68.067 66.238 -1.829 -2.687
Driver.Packet.prepare_memory 30.126 28.812 -1.314 -4.362
Vulkan.Dispatch 40.265 38.789 -1.476 -3.666
Vulkan.Dispatch.shader_resources 38.025 36.507 -1.518 -3.992
{"commit":"48d61f53b46f","dirty":false,"env":{"APS5_TRACE_SECONDS":"120:12"},"gpu":"NVIDIA GeForce RTX 3060","driver":"NVIDIA 617.42","os":"Windows 10.0.19045","resolution":"3840x2160","window":{"spec":"seconds 120:12","first_present":2158,"ms":12053.995},"presents":99,"frame_ms":{"p50":117.162,"p90":145.047,"p99":158.422,"max":158.422},"gpu_busy_ms_per_present":100.711,"worker_busy_pct":{"queue worker 0x0":99.421,"queue worker 0x20":99.581},"top_phases_ms_per_frame":{"Driver.Packet":242.204,"Driver.Packet.dispatch":86.382,"Driver.Dispatch":85.879,"Driver.Packet.memory_wait":68.067,"Vulkan.Dispatch":40.265,"Vulkan.Dispatch.shader_resources":38.025,"Driver.Packet.device_lock":34.172,"Driver.Packet.prepare_memory":30.126},"trace_dropped":false,"trace":"aps5-trace-48d61f53b46f-9720.json.gz"}
{"commit":"ca01351ff882","dirty":false,"env":{"APS5_TRACE_SECONDS":"120:12"},"gpu":"NVIDIA GeForce RTX 3060","driver":"NVIDIA 617.42","os":"Windows 10.0.19045","resolution":"3840x2160","window":{"spec":"seconds 120:12","first_present":2169,"ms":12017.926},"presents":101,"frame_ms":{"p50":114.665,"p90":143.229,"p99":164.200,"max":165.107},"gpu_busy_ms_per_present":97.535,"worker_busy_pct":{"queue worker 0x0":99.409,"queue worker 0x20":99.595},"top_phases_ms_per_frame":{"Driver.Packet":236.692,"Driver.Packet.dispatch":83.295,"Driver.Dispatch":82.809,"Driver.Packet.memory_wait":66.238,"Vulkan.Dispatch":38.789,"Vulkan.Dispatch.shader_resources":36.507,"Driver.Packet.device_lock":34.858,"Driver.Packet.prepare_memory":28.812},"trace_dropped":false,"trace":"aps5-trace-ca01351ff882-11512.json.gz"}

Traces: aps5-trace-48d61f53b46f-9720.json.gz (main) and aps5-trace-ca01351ff882-11512.json.gz (this PR), attached below.

trace_digest.py, main

seconds 120:12 from present 2158: 99 presents, frame p50 117.2 / p90 145.0 / p99 158.4 / max 158.4 ms, GPU busy 100.7 ms per present
NVIDIA GeForce RTX 3060, NVIDIA 617.42, Windows 10.0.19045, 3840x2160, commit 48d61f5, dropped: no

{"commit":"48d61f53b46f","dirty":false,"env":{"APS5_TRACE_SECONDS":"120:12"},"gpu":"NVIDIA GeForce RTX 3060","driver":"NVIDIA 617.42","os":"Windows 10.0.19045","resolution":"3840x2160","window":{"spec":"seconds 120:12","first_present":2158,"ms":12053.995},"presents":99,"frame_ms":{"p50":117.162,"p90":145.047,"p99":158.422,"max":158.422},"gpu_busy_ms_per_present":100.711,"worker_busy_pct":{"queue worker 0x0":99.421,"queue worker 0x20":99.581},"top_phases_ms_per_frame":{"Driver.Packet":242.204,"Driver.Packet.dispatch":86.382,"Driver.Dispatch":85.879,"Driver.Packet.memory_wait":68.067,"Vulkan.Dispatch":40.265,"Vulkan.Dispatch.shader_resources":38.025,"Driver.Packet.device_lock":34.172,"Driver.Packet.prepare_memory":30.126},"trace_dropped":false,"trace":"aps5-trace-48d61f53b46f-9720.json.gz"}

Slowest frames (largest slices inside each, inclusive)

  • present 2199: 158.4 ms at 4990 ms: Driver.Packet (queue worker 0x20) 156.8, Driver.Packet (queue worker 0x0) 152.3, batch (gpu) 105.5, Driver.Packet.dispatch (queue worker 0x20) 75.5
  • present 2183: 157.2 ms at 3021 ms: Driver.Packet (queue worker 0x20) 155.6, Driver.Packet (queue worker 0x0) 150.7, batch (gpu) 105.6, Driver.Packet.dispatch (queue worker 0x20) 74.6
  • present 2246: 153.0 ms at 10688 ms: Driver.Packet (queue worker 0x20) 151.5, Driver.Packet (queue worker 0x0) 146.6, batch (gpu) 108.3, Driver.Packet.dispatch (queue worker 0x20) 74.8
  • present 2166: 149.2 ms at 933 ms: Driver.Packet (queue worker 0x20) 147.7, Driver.Packet (queue worker 0x0) 142.9, batch (gpu) 107.0, Driver.Packet.dispatch (queue worker 0x20) 68.2
  • present 2198: 148.9 ms at 4841 ms: Driver.Packet (queue worker 0x20) 147.2, Driver.Packet (queue worker 0x0) 142.6, batch (gpu) 106.4, Driver.Packet.dispatch (queue worker 0x20) 69.5

Threads (ms per frame)

thread busy wait untraced in slices < 20 us largest
queue worker 0x0 85.8 (70 %) 29.6 (24 %) 6.3 34.1 Driver.Packet 115.4, Driver.EndSubmission 0.0
queue worker 0x20 39.7 (33 %) 80.2 (66 %) 1.8 7.3 Driver.Packet 119.8, Driver.EndSubmission 0.1
presenter 0.6 (0 %) 0.0 (0 %) 121.1 0.1 VideoOut.Flip 0.6, Driver.Present 0.0
gpu 100.7 (83 %) 0.0 (0 %) 21.1 0.0 batch 100.7

Largest slices (ms per frame, inclusive, all threads): Driver.Packet 235.3, batch 100.7, Driver.Packet.dispatch 86.1, Driver.Dispatch 85.2, Driver.Packet.memory_wait 68.0, Vulkan.Dispatch 39.4, Vulkan.Dispatch.shader_resources 37.1, Driver.Packet.device_lock 32.2, Driver.Packet.prepare_memory 27.8, Recorder.Sync 20.7

Longest waits

  • Driver.Packet.memory_wait (queue worker 0x20) 45.5 ms at 10797 ms, present 2246
  • Driver.Packet.memory_wait (queue worker 0x20) 45.1 ms at 5104 ms, present 2199
  • Driver.Packet.memory_wait (queue worker 0x20) 44.3 ms at 1038 ms, present 2166
  • Driver.Packet.memory_wait (queue worker 0x20) 43.9 ms at 8841 ms, present 2230
  • Driver.Packet.memory_wait (queue worker 0x20) 43.0 ms at 3137 ms, present 2183
  • Driver.Packet.memory_wait (queue worker 0x20) 41.8 ms at 6905 ms, present 2214
  • Driver.Packet.memory_wait (queue worker 0x20) 41.5 ms at 4949 ms, present 2198
  • Driver.Packet.memory_wait (queue worker 0x20) 41.1 ms at 8989 ms, present 2231
  • Driver.Packet.memory_wait (queue worker 0x20) 41.1 ms at 2981 ms, present 2182
  • Driver.Packet.memory_wait (queue worker 0x20) 40.4 ms at 7050 ms, present 2215

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

  • batch 4140: 11.6 ms at 7393 ms, present 2218: GPU no ranges; recorded by Recorder.WaitSerial (queue worker 0x20) 11.7, Driver.Packet (queue worker 0x20) 1.9, Driver.Packet.dispatch (queue worker 0x20) 1.4, Driver.Dispatch (queue worker 0x20) 1.3
  • batch 163: 11.5 ms at 303 ms, present 2160: GPU no ranges; recorded by Driver.Packet (queue worker 0x20) 1.5, Driver.Packet.dispatch (queue worker 0x20) 1.0, Driver.Dispatch (queue worker 0x20) 0.9, Driver.Packet.prepare_memory (queue worker 0x20) 0.5
  • batch 2466: 11.5 ms at 4439 ms, present 2194: GPU no ranges; recorded by Recorder.WaitSerial (queue worker 0x20) 13.9, Driver.Packet (queue worker 0x20) 1.5, Driver.Packet.dispatch (queue worker 0x20) 1.3, Driver.Dispatch (queue worker 0x20) 1.2
  • batch 3269: 11.5 ms at 5830 ms, present 2205: GPU no ranges; recorded by Driver.Packet (queue worker 0x20) 1.8, Driver.Packet.dispatch (queue worker 0x20) 1.2, Driver.Dispatch (queue worker 0x20) 1.1, GpuMutex.wait.label (queue worker 0x20) 0.8
  • batch 988: 11.4 ms at 1761 ms, present 2172: GPU no ranges; recorded by Driver.Packet (queue worker 0x20) 1.6, Driver.Packet.dispatch (queue worker 0x20) 1.1, Driver.Dispatch (queue worker 0x20) 1.1, GpuMutex.wait.label (queue worker 0x20) 0.6
trace_digest.py, this PR

seconds 120:12 from present 2169: 101 presents, frame p50 114.7 / p90 143.2 / p99 164.2 / max 165.1 ms, GPU busy 97.5 ms per present
NVIDIA GeForce RTX 3060, NVIDIA 617.42, Windows 10.0.19045, 3840x2160, commit ca01351, dropped: no

{"commit":"ca01351ff882","dirty":false,"env":{"APS5_TRACE_SECONDS":"120:12"},"gpu":"NVIDIA GeForce RTX 3060","driver":"NVIDIA 617.42","os":"Windows 10.0.19045","resolution":"3840x2160","window":{"spec":"seconds 120:12","first_present":2169,"ms":12017.926},"presents":101,"frame_ms":{"p50":114.665,"p90":143.229,"p99":164.2,"max":165.107},"gpu_busy_ms_per_present":97.535,"worker_busy_pct":{"queue worker 0x0":99.409,"queue worker 0x20":99.595},"top_phases_ms_per_frame":{"Driver.Packet":236.692,"Driver.Packet.dispatch":83.295,"Driver.Dispatch":82.809,"Driver.Packet.memory_wait":66.238,"Vulkan.Dispatch":38.789,"Vulkan.Dispatch.shader_resources":36.507,"Driver.Packet.device_lock":34.858,"Driver.Packet.prepare_memory":28.812},"trace_dropped":false,"trace":"aps5-trace-ca01351ff882-11512.json.gz"}

Slowest frames (largest slices inside each, inclusive)

  • present 2212: 165.1 ms at 5069 ms: Driver.Packet (queue worker 0x20) 163.5, Driver.Packet (queue worker 0x0) 158.9, batch (gpu) 105.9, Driver.Packet.dispatch (queue worker 0x20) 82.6
  • present 2180: 164.2 ms at 1256 ms: Driver.Packet (queue worker 0x20) 162.6, Driver.Packet (queue worker 0x0) 158.2, batch (gpu) 104.0, Driver.Packet.dispatch (queue worker 0x20) 69.0
  • present 2229: 157.8 ms at 7135 ms: Driver.Packet (queue worker 0x20) 156.1, Driver.Packet (queue worker 0x0) 151.4, batch (gpu) 103.9, Driver.Packet.dispatch (queue worker 0x20) 78.6
  • present 2244: 157.3 ms at 8892 ms: Driver.Packet (queue worker 0x20) 155.8, Driver.Packet (queue worker 0x0) 151.0, batch (gpu) 106.1, Driver.Packet.dispatch (queue worker 0x20) 71.8
  • present 2260: 150.7 ms at 10796 ms: Driver.Packet (queue worker 0x20) 149.1, Driver.Packet (queue worker 0x0) 144.4, batch (gpu) 105.2, Driver.Packet.dispatch (queue worker 0x20) 70.2

Threads (ms per frame)

thread busy wait untraced in slices < 20 us largest
queue worker 0x0 85.1 (72 %) 27.6 (23 %) 6.3 34.0 Driver.Packet 112.7, Driver.EndSubmission 0.0
queue worker 0x20 38.9 (33 %) 78.3 (66 %) 1.8 7.2 Driver.Packet 117.1, Driver.EndSubmission 0.1
presenter 0.6 (1 %) 0.0 (0 %) 118.4 0.1 VideoOut.Flip 0.6, Driver.Present 0.0
gpu 98.5 (83 %) 0.0 (0 %) 20.5 0.0 batch 98.5

Largest slices (ms per frame, inclusive, all threads): Driver.Packet 229.8, batch 98.5, Driver.Packet.dispatch 83.0, Driver.Dispatch 82.1, Driver.Packet.memory_wait 66.2, Vulkan.Dispatch 37.9, Vulkan.Dispatch.shader_resources 35.5, Driver.Packet.device_lock 32.9, Driver.Packet.prepare_memory 26.5, Recorder.WaitSerial 19.9

Longest waits

  • Driver.Packet.memory_wait (queue worker 0x20) 45.2 ms at 5190 ms, present 2212
  • Driver.Packet.memory_wait (queue worker 0x20) 44.2 ms at 3276 ms, present 2196
  • Driver.Packet.memory_wait (queue worker 0x20) 44.2 ms at 9005 ms, present 2244
  • Driver.Packet.memory_wait (queue worker 0x20) 43.7 ms at 10904 ms, present 2260
  • Driver.Packet.memory_wait (queue worker 0x20) 43.4 ms at 7092 ms, present 2228
  • Driver.Packet.memory_wait (queue worker 0x20) 42.9 ms at 1525 ms, present 2181
  • Driver.Packet.memory_wait (queue worker 0x20) 42.4 ms at 11053 ms, present 2261
  • Driver.Packet.memory_wait (queue worker 0x20) 41.9 ms at 1379 ms, present 2180
  • Driver.Packet.memory_wait (queue worker 0x20) 41.7 ms at 7252 ms, present 2229
  • Driver.Packet.memory_wait (queue worker 0x20) 41.4 ms at 5337 ms, present 2213

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

  • batch 1851: 12.4 ms at 3244 ms, present 2196: GPU no ranges; recorded by Recorder.WaitSerial (queue worker 0x20) 15.4, Driver.Packet (queue worker 0x20) 0.9, GpuMutex.wait.other (queue worker 0x20) 0.7, Driver.Dispatch (queue worker 0x20) 0.5
  • batch 3979: 11.2 ms at 7061 ms, present 2228: GPU no ranges; recorded by Driver.Packet (queue worker 0x20) 1.1, Driver.Packet.dispatch (queue worker 0x20) 0.6, Driver.Dispatch (queue worker 0x20) 0.5, Driver.Packet.prepare_memory (queue worker 0x20) 0.5
  • batch 98: 11.0 ms at 175 ms, present 2170: GPU no ranges; recorded by no CPU slices
  • batch 6368: 10.7 ms at 11282 ms, present 2263: GPU no ranges; recorded by Driver.Packet (queue worker 0x20) 1.6, Driver.Packet.dispatch (queue worker 0x20) 1.3, Driver.Dispatch (queue worker 0x20) 1.1, GpuMutex.wait.label (queue worker 0x20) 0.5
  • batch 1387: 10.6 ms at 2430 ms, present 2189: GPU no ranges; recorded by Recorder.WaitSerial (queue worker 0x20) 10.9, Driver.Packet (queue worker 0x20) 1.9, Driver.Packet.dispatch (queue worker 0x20) 1.3, Driver.Dispatch (queue worker 0x20) 1.3

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
  • GPUs tested: NVIDIA GeForce RTX 4060 8 GB, driver 617.42, Windows 10 (tests); NVIDIA GeForce RTX 3060 12 GB, driver 617.42, Windows 10 (title runs)
  • AI-assisted: yes

@github-actions

github-actions Bot commented Oct 18, 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 4072 core/shader/recompiler/Recompiler.cpp
PR 5021 core/shader/recompiler/Recompiler.cpp
PR 5609 core/shader/recompiler/Recompiler.cpp
PR 5925 core/shader/recompiler/CMakeLists.txt
PR 6412 core/shader/recompiler/Recompiler.cpp
PR 6488 core/shader/recompiler/Recompiler.cpp
PR 6539 core/shader/recompiler/Recompiler.cpp
PR 6879 core/shader/recompiler/CMakeLists.txt
core/shader/recompiler/Recompiler.cpp
PR 6924 core/shader/recompiler/Recompiler.cpp
PR 6946 core/shader/recompiler/CMakeLists.txt
PR 6947 core/shader/recompiler/CMakeLists.txt
core/shader/recompiler/Recompiler.cpp
PR 6977 core/shader/recompiler/CMakeLists.txt
core/shader/recompiler/Recompiler.cpp
PR 7201 core/shader/recompiler/CMakeLists.txt
core/shader/recompiler/Recompiler.cpp
PR 7395 core/shader/recompiler/Recompiler.cpp
PR 7484 core/shader/recompiler/Recompiler.cpp
PR 7642 core/shader/recompiler/CMakeLists.txt
core/shader/recompiler/Recompiler.cpp
PR 7816 core/shader/recompiler/Recompiler.cpp
PR 7888 core/shader/recompiler/Recompiler.cpp
PR 7922 core/shader/recompiler/CMakeLists.txt
PR 8004 core/shader/recompiler/CMakeLists.txt
core/shader/recompiler/Recompiler.cpp
PR 8697 core/shader/recompiler/Recompiler.cpp

@github-actions github-actions Bot added build shader core/shader agc libSceAgc* labels Oct 18, 2026
@jotitadev

Copy link
Copy Markdown
Contributor

Apple GPU / MoltenVK run, as a second vendor next to the RTX 3060/4060 runs. The pass changes the SPIR-V that SPIRV-Cross turns into Metal for every program with a vector window.

Machine: Apple M2 Max, macOS 27.2, x86-64 build under Rosetta. Head c7a652e. Method: macos-port @ b7eb6b6 + ImportWatched + PR diff, not main. The pass, its header and the Recompiler.cpp hook applied cleanly. Two conflicts came from code the port doesn't have. In CMakeLists.txt I added only VectorWindowStoreEliminator.cpp. In VectorWindow.cpp I added only your new "written twice" case, without main's v_readlane sampler case next to it. Drivers: MoltenVK 1.4.2 (SDK) and MoltenVK main + SPIRV-Cross #2704.

Test set: the 31 tests matching vector_window|relative|masked_select|denormal|readlane|read_lane|dpp|lane|movrel. They include agc_vector_window, agc_vector_window_stage_inputs, agc_driver_relative_indexing, agc_driver_scalar_relative_indexing, agc_driver_relative_stage_inputs, agc_masked_select, agc_denormal_flush_elimination and the read/write lane and DPP tests.

Tree MoltenVK 1.4.2 MoltenVK main + #2704
PR 28/31 29/31
PR with only the Recompiler.cpp hook removed (pass not run) 27/31: agc_vector_window also fails with window stores: written twice 4, written once 3 not run
  • The new case is red without the pass, with your message, and green with it.
  • The failures on the PR tree are all port-baseline device limits, not this PR. agc_driver_relative_ancillary fails on both drivers because it needs geometry shaders (SPIR-V requires unsupported device capability 2). agc_driver_f32_denormal_flush fails on both because there is no f32 denormal preservation. agc_driver_dpp16_operands fails only on the SDK driver, from the signed-zero issue that Ratchet & Clank: Rift Apart: SIGSEGV since #861, an address-less 64 GiB reservation now takes the addresses the title hints next #2704 fixes. On the SDK driver the GPU tests in the set give the same result with and without the pass.

The port doesn't have agc_driver_vector_window_direct_access or agc_driver_vector_window_narrowing, so those two were not run here. No timing measured on Apple.

GPUs tested: Apple M2 Max (MoltenVK 1.4.2 and MoltenVK main + SPIRV-Cross #2704)

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

@parl0v

parl0v commented Oct 18, 2026

Copy link
Copy Markdown
Contributor Author

Frame check on the RTX 3060 12 GB, MCD2 title screen, default settings, every 200th frame saved, two alternating rounds of main 1c523bd and main plus this commit. Mean absolute difference per channel (0–255) at the same frame index:

frame this PR vs main (round 1) main round 1 vs round 2 this PR vs main (round 2)
0 0.00 2.06 3.28
1 0.00 0.00 0.00
2 0.05 0.05 0.11
3 0.04 0.04 0.08
4 2.29 2.29 2.55
5 1.69 1.69 1.73
6 2.29 2.29 2.57
7 2.05 1.19 3.12

The differences with the change match the run-to-run differences of main itself (the title's animation), so nothing visible changes. The same rows ran at 8.19 / 8.10 FPS on main and 8.28 / 8.27 with this commit.

@oneandonlydean oneandonlydean self-assigned this Oct 18, 2026
@github-actions github-actions Bot added perf Performance change perf numbers missing perf pull request without a before and after summary line labels Oct 18, 2026
@oneandonlydean

Copy link
Copy Markdown
Collaborator

Thanks, the liveness looks right to me, and the reaching-store walks in the emitter keep their stores, because a store a load can reach is never dead.

One test gap: the new agc_vector_window case compares store counts within one straight-line block. It can't catch a live store dropped across blocks, for example a slot written before a branch or loop and read by a constant-index v_movrels in a later block or the next iteration, which is where the fixpoint and the liveOut merge matter. Could you add one execution case that checks a value: write a window register, branch or loop, then read it relatively in another block, and require the loaded value? The existing relative-indexing tests may already cover this, but none is aimed at this pass.

@parl0v
parl0v force-pushed the perf/window-store-liveness branch from c7a652e to ca01351 Compare October 18, 2026 20:10
@parl0v

parl0v commented Oct 18, 2026

Copy link
Copy Markdown
Contributor Author

Added agc_driver_vector_window_liveness (tests/execution/VectorWindowLiveness.cpp), which checks loaded values rather than store counts:

  • a window register written before a scalar branch and overwritten on one side of it only, then read by a constant-index v_movrels in the block after the join. User data picks the side, so both paths run;
  • a window register read at the top of a three-iteration loop and overwritten after the read, so only the next iteration can see that store (the back edge).

Every lane's loaded value is checked, in wave32 and wave64. As a mutation check I made liveOut ignore the successors: the test then fails with "the window register written before the branch read 0x00000000, expected 0x00000001". With the pass as it is, it passes on my RTX 4060. I also rebased onto current main.

@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 removed the perf numbers missing perf pull request without a before and after summary line label Oct 18, 2026
@parl0v

parl0v commented Oct 18, 2026

Copy link
Copy Markdown
Contributor Author

Traces of the performance capture in the description (they open in ui.perfetto.dev):

@savvaniss

Copy link
Copy Markdown
Contributor

Intel and AMD Windows data: agc_vector_window fails on main and passes with this PR, on an Intel UHD 770 and an AMD RX 9070 XT.

Windows 11 Pro 26300, WinLibs GCC 15.2.0 r7, Release, every execution test built, ANYPS5_NO_SHADER_CACHE=1, serial CTest. Intel UHD Graphics 770 (8086:a780), driver 32.0.101.7088. AMD Radeon RX 9070 XT (1002:7550), driver 32.0.31041.1004, with VkCache emptied first. This head ca01351ff merged into main 07377573f. "Without" means this PR's test files on main 07377573f, with none of its other changes.

Tests, ×3 UHD 770 RX 9070 XT
This head: agc_driver_vector_window_liveness, agc_vector_window, agc_vector_window_stage_inputs 3 of 3 pass 3 of 3 pass
Without agc_vector_window fails same

This is a correctness check only; I measured no speed.

I also ran full CTest with this PR and the 13 other open AGC and shader PRs I tested in the same batch merged together (#7668, #7659, #7504, #7432, #7426, #7413, #7480, #7465, #7466, #7464, #7449, #7489, #7673). Neither GPU showed a failure beyond main's own: the five image-table tests (#6647) and agc_driver_sample_multisample_target (#6957) on the UHD 770, and agc_driver_layered_volume_vertex_no_gpl (#4768) on the RX 9070 XT.

AI-assisted: yes (Claude Code)

@DavidAmsellem

Copy link
Copy Markdown
Contributor

First AMD-on-Linux (RADV) run of this PR. Merged onto main 0fbfd91e5 (clean merge), built in full and compared with the same main, on the same machine.

AMD Radeon RX 7900 XT (RADV NAVI31, gfx1100), Mesa 25.2.8, Linux Mint 22.3 (Ubuntu 24.04 base), kernel 7.0.0-38, GCC 13.3, Release.

Full suite (ANYPS5_REQUIRE_VULKAN=1 ctest -j8): 1638/1640 with this PR, against 1637/1639 on main. The failures are the same two on both, so this PR adds none: agc_driver_image_table_vouched_lookups (#7766, fixed by #7916) and agc_driver_guest_memory_lifetime (#7784). The new agc_driver_vector_window_liveness ran (not skipped) and passed.

GPUs tested: AMD Radeon RX 7900 XT (RADV NAVI31, gfx1100), Mesa 25.2.8, Linux Mint 22.3 / kernel 7.0.0-38

AI-assisted: yes

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

Labels

agc libSceAgc* build perf Performance change shader core/shader

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants