Skip to content

perf(shader): store multi-dword global writes with one BDA lookup - #2305

Merged
adriadam10 merged 1 commit into
boykopovar:mainfrom
TheAggie:perf/shader-wide-bda-stores
Oct 10, 2026
Merged

adriadam10 merged 1 commit into
boykopovar:mainfrom
TheAggie:perf/shader-wide-bda-stores

Conversation

@TheAggie

@TheAggie TheAggie commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

What

A multi-dword global or flat store (global_store_dwordx2/x3/x4) was translated as one StoreAddressU32 per dword. Each dword got its own write-pointer lookup and its own 4-byte store. Across a wave those are strided partial writes, every lane writing one dword out of each 16 bytes per instruction, and partial writes to host memory are very slow.

RADV's clear_htile_mask kernel (17 instructions: one global_load_dwordx4, v_and_or_b32 ×4, one global_store_dwordx4 per thread) took ~9 ms for 640 KiB on an RTX 5070 Ti. In vkQuake (PPSA99010) it runs three times per frame, which made it 4.6 s of every 10 s of GPU time. Loads already avoid this: a multi-dword load is one LoadAddressU32xN that resolves the span once (EmitBdaDwordReads).

  • Translation emits StoreAddressU32x2/x3/x4 (Void, address resource, U32xN data) for a multi-dword store outside scratch, with the same group memory metadata as the wide loads. APS5_BDA_DWORD_STORES=1 keeps one StoreAddressU32 per dword.
  • EmitBdaDwordWrites probes the whole span once with a new non-faulting probe_bda_write_pointer (write permission). When the span is in one writable range and both the guest and device addresses are 4-byte aligned, it stores the dwords from that one pointer: one vector store when the device address is 16-byte (x4) or 8-byte (x2) aligned, otherwise consecutive dword stores. It then notes the written page, and the last dword's page too when that differs.
  • Anything else (unmapped, read-only, spanning two ranges, unaligned) takes the existing per-dword EmitBdaStore path, so faults and their order, byte stores for unaligned addresses and range-spanning stores behave exactly as before.

Tests:

  • RunBdaDwordWriteTests (agc_driver_bda_device), through the emitter: 16-byte-aligned x4, 4-byte-aligned x4, aligned and unaligned x2, x3, x4 and x2 across a 4 KiB written-page boundary, an unaligned x4, an x4 across two writable ranges (no fault), and an x4 whose last three dwords are read-only (first dword stored, Permission fault as before). Each checks the stored bytes, the neighbouring bytes, the fault state and the noted pages. Dropping the second page note, or storing the separate dwords one slot off, fails it.
  • New tests/execution/MaskedGlobalStore.cpp: RADV's 17-word clear_htile_mask kernel, translated and dispatched (64 groups, wave64 and wave32), checks every dword of the buffer for the depth-clear and the expand masks. It passes with the wide path and with APS5_BDA_DWORD_STORES=1.

Tested

Windows 11 Pro (26200), MinGW-w64 GCC 15.2.0 (WinLibs r7), Release, NVIDIA GeForce RTX 5070 Ti (driver 610.88).

  • Full local suite: 508 tests, 506 passed, 1 skipped; guest_sce_net timed out (it times out locally on unrelated branches too, likely my network filtering). It was run from the generated CTestTestfile.cmake commands because antivirus blocks ctest.exe on this machine.
  • tools/check_conventions.py --base origin/main passes.
  • The clear_htile_mask kernel alone (640 groups over 640 KiB, a local timing harness): 9.1 ms per dispatch before, 0.54 ms after.
  • vkQuake E1M1 demo with APS5_PROFILE_GPU=1, on my local test branch (current main plus my open RADV PRs and local stubs): GPU time went from ~5,100 ms to ~290 ms per 10 s, GPU busy from 34 ms to 2.9 ms per frame, and ~19 to ~22 FPS (now CPU-bound). The RADV fill kernel also dropped from ~755 ms to ~40 ms per 10 s.

Not tested on Linux or macOS.

#1265 (loops the BDA byte stores) and my #1280 (stacked on it; changes note_bda_write) touch the same store helpers in SpirvBdaRead.cpp. I'll rebase onto whichever lands first.

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 in TechnicalDebt
  • 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
  • AI-assisted: yes

🤖 Generated with Claude Code

A global_store_dwordx2/x3/x4 was split into one StoreAddressU32 per
dword, each with its own write-pointer lookup and its own 4-byte store.
Across a wave those are strided partial writes, which are very slow to
host memory: RADV's clear_htile_mask kernel (one dwordx4 load and store
per thread over 640 KiB) took ~9 ms. Multi-dword stores are now one
StoreAddressU32xN; the emitter probes the whole span once and, when it
is mapped writable and 4-byte aligned, stores it as one vector (16- or
8-byte aligned) or as consecutive dwords, noting each written page.
Anything else takes the per-dword path as before, so faults and
range-spanning stores behave as they did. APS5_BDA_DWORD_STORES=1
restores per-dword stores.

AI-assisted (Claude Code).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@TheAggie
TheAggie force-pushed the perf/shader-wide-bda-stores branch from 0db6558 to b958425 Compare October 10, 2026 06:00
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 10, 2026
@adriadam10

Copy link
Copy Markdown
Collaborator

Checked on Linux (RADV): scoped build and ctest of libSceAgc + libSceAgcDriver (326 tests) green, including agc_driver_bda_device and agc_driver_masked_global_store. Without the change the BDA tests fail to build (EmitBdaDwordWrites missing). Reviewed the fallback paths (unmapped, read-only, spanning ranges, unaligned) and the written-page notes against the per-dword path; they match. Also ran the full Build workflow for this PR; merging once it is green.

@adriadam10
adriadam10 merged commit a828da9 into boykopovar:main Oct 10, 2026
12 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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants