Skip to content

fix(recompiler): skip window narrowing when it adds too many switches - #10053

Merged
oneandonlydean merged 1 commit into
boykopovar:mainfrom
klosixgaming-arch:fix/window-narrowing-switch-cap
Oct 24, 2026
Merged

oneandonlydean merged 1 commit into
boykopovar:mainfrom
klosixgaming-arch:fix/window-narrowing-switch-cap

Conversation

@klosixgaming-arch

Copy link
Copy Markdown
Contributor

What

Window narrowing (30fab07) puts every vector window slot the bound analysis did not reach into its own variable. Each relative access that is not a constant index then emits a switch over all of those variables, whether or not its index could be bounded. A shader with many relative accesses therefore grew by one switch per access.

IndexedWindowSlots now counts the dynamic accesses and keeps the whole window in the array when the switches would pass 4096 cases in all (accesses, times slots outside the array, times lanes). Shaders with a few relative accesses are narrowed as before. APS5_NO_WINDOW_NARROWING=1 still turns narrowing off.

Why: TEKKEN: DARK RESURRECTION (PPSA31371) has a mesh shader with many relative accesses. With narrowing on, its cached SPIR-V was 4.2 MB, and the game stopped advancing at frame 23 for more than 10 minutes. Backtraces taken during the stall showed the NVIDIA driver's shader compiler working on that one shader. See discussion #10051.

Tested

  • New cases in agc_vector_window_tests: a 32-register window with 2 relative reads from a user data index stays narrowed (more than 8 function variables, switch cases present), and the same window with 300 reads does not grow a switch for each (at most 4096 cases, fewer function variables than the 2-read shader).
  • Without the fix the 300-read case produces 38528 switch cases and the new test fails. With the fix it passes.
  • agc_vector_window, agc_vector_window_stage_inputs and the GPU execution tests agc_driver_vector_window_narrowing, agc_driver_relative_indexing, agc_driver_relative_indexing_known_bits, agc_driver_relative_stage_inputs, agc_driver_relative_ancillary, agc_driver_vector_window_direct_access, agc_driver_vector_window_liveness, agc_driver_window_lane_spill and agc_driver_scalar_relative_indexing all pass (11 of 11).
  • PPSA31371 on a fresh shader cache, without APS5_NO_WINDOW_NARROWING, with fix(libSceFont): render a glyph into a zero-extent surface to measure it #9992 and fix(libSceFont): resolve a default string direction at creation #10021 applied: it runs past frame 2677, where it previously stopped at frame 23.
  • GPU tested: RTX 5090 (NVIDIA 610.47), Windows 11. Not tested on other GPUs.

The 4096 limit is a judgment call, not a measured optimum. I did not measure the speed effect on the title that narrowing was written for (PPSA08427), so a shader like that with many unproven accesses would now keep its whole window in local memory.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR
  • No comments in code
  • Unimplemented paths throw; no silent stubs
  • No notes, investigation .md files or images added to the repository
  • No third-party code added
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Fixes: none
  • Unblocks: PPSA31371 TEKKEN: DARK RESURRECTION (PPSA31371) #10051 (runs past frame 23 without APS5_NO_WINDOW_NARROWING)
  • GPUs tested: NVIDIA RTX 5090, driver 610.47, Windows 11
  • AI-assisted: yes

AI-assisted: yes. Written with AI assistance and reviewed by me.

@github-actions github-actions Bot added the unblocks title Names a title ID on the Unblocks line label Oct 23, 2026
Narrowing the vector window (30fab07) puts every slot the bound analysis
did not reach into its own variable. Each relative access that is not a
constant index then emits a switch over all of those variables, whether or not
the analysis could bound its index. A shader with many relative accesses grew
by one switch per access: a mesh shader of PPSA31371 came out at 4.2 MB of
SPIR-V, and the NVIDIA driver spent minutes compiling it while the game
waited at frame 23.

IndexedWindowSlots now counts the dynamic accesses and keeps the whole window
in the array when the switches would pass 4096 cases in all (accesses times
slots outside the array times lanes). Shaders with a few relative accesses
are narrowed as before.

The vector window tests recompile a 32-register window with 2 and with 300
relative reads from a user data index, and check the narrowed shape and the
switch case count.
@klosixgaming-arch
klosixgaming-arch force-pushed the fix/window-narrowing-switch-cap branch from 65499ed to 8eec91e Compare October 23, 2026 07:06
@github-actions

github-actions Bot commented Oct 23, 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 9356 core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvModuleSetup.cpp
PR 9387 core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvModuleSetup.cpp
PR 9748 core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvModuleSetup.cpp

@github-actions github-actions Bot added shader core/shader agc libSceAgc* labels Oct 23, 2026
@fogydes

fogydes commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

OK. The cap logic, its wiring, and the test shape all check out from the branch:

  1. SpirvModuleSetup.cpp:195 adds MaxNarrowedSwitchCases = 4096; IndexedWindowSlots now takes a laneCount parameter, counts dynamicAccesses while walking the program, and returns 0 when reached == 0 || dynamicAccesses * (window - reached) * laneCount > 4096 (:212). The caller passes state.laneCount, which exists (SpirvEmitterState.hpp) and is already used in the same function. The APS5_NO_WINDOW_NARROWING off-switch is retained. [verified]
  2. Tests were added in VectorWindow.cpp: a small program (bounded reads, few unbounded reads) must stay narrowed, and a synthesized program with ~300 independent unbounded reads from the window must produce at most 4096 accumulated switch cases while keeping function variables low. The tests assert on the emitted SPIR-V words, not on internal counters. [verified from the test code]
  3. Without the fix the 300-read case would cost roughly reads × windowSize × lanes accumulated cases across recompiles — the author's headline figure (301 × 128 = 38528) is arithmetically consistent but the concrete hang attribution on the affected title is [author-reported only]. No speed claim is made anywhere in the body or commits, so no trace_digest comparison is owed under the repo rules; the trade-off (keeping a heavily dynamic window in local memory instead of narrowing) is disclosed in the description. [author-reported only]
  4. Registration: the vector window tests are registered in the existing ctest loop; no new test target is introduced. [read-only]
  5. Single topic, no comments added, Conventional Commit subject matches. [verified]

One design note, non-blocking: 4096 is a judgment call rather than a measured threshold — it's documented inline next to the constant, which is the right place for it, but if a future title regresses compile times with windows just above the cap, this constant is the first knob to revisit.

Envelope: current main is 664648356c; base 0a0ed72167 (1 ahead, 24 behind); no main commits since the base touch SpirvModuleSetup.cpp, VectorWindow.cpp, or their CMake registration; merge-tree against origin/main is clean.

Disclosure: Windows host; I read the full diff and the touched functions' surroundings but did not build the shader backend or execute the narrowed/narrowed-off comparison — the fix's effect on the triggering shader is author-reported.

@BKlaasWerkman

Copy link
Copy Markdown
Contributor

Windows run on a second NVIDIA GPU and on Intel.

Windows 11 Pro 10.0.26300, WinLibs GCC 15.2.0 r7, Release. NVIDIA GeForce RTX 3060 Ti (driver 617.42) and Intel Arc Graphics (Meteor Lake, driver 101.9150), selected with ANYPS5_GPU. Head 8eec91e merged into main 15e71db (clean merge). ANYPS5_REQUIRE_VULKAN=1, ANYPS5_NO_SHADER_CACHE=1, VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation (1.4.363); no was not found but was requested line.

AI-assisted: yes (Claude Code)

@oneandonlydean oneandonlydean self-assigned this Oct 23, 2026
@oneandonlydean
oneandonlydean merged commit 1e2d0e9 into boykopovar:main Oct 24, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* shader core/shader unblocks title Names a title ID on the Unblocks line

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants