WIP: perf(vk): scan pending uniform updates in place #472

Draft
zeldaootr wants to merge 5 commits from zeldaootr/Longshot:scan-uniform-updates into main
First-time contributor

Why

Each uncached lookup for pending uniform-buffer writes copies two arrays of addresses and then walks them again. Both descriptor reads and uniform-buffer copies pay for this work.

What changed

Scan the existing deque over the requested iterator range. Stop on the first overlap and otherwise cache the same gap. The epoch check still guards publication of a cached answer. A background thread (ub_limit == 0, an empty range) builds no iterator into the deque at all, as before: it must not read even its start while the replay thread pushes and prunes.

Flags and settings

None.

Measurements

main @ cf605c85 and the scan @ cc0e3ef1 (the empty-range guard, 80d41562, adds no work on the measured path). A differential benchmark compares answers and cache contents across 100,000 randomized spans and explicit boundaries. On the Deck OLED, in a mixed workload of 80 pending updates, the two-pass calculation took 424 ns per query and the direct-scan prototype took 110 ns (medians of five interleaved samples per arm). The benchmark inputs deliberately exercise the slow path. A separate interleaved game comparison uses one instrumented RC3 renderer (665aef4f4718) and a corrected accounting host. Each run settled at child Kokiri spawn, teleported to the Lost Woods bridge entrance, warmed for 20 s, then stood for 45 s. SteamOS 3.9.5, RADV VANGOGH, Mesa 26.2.0-devel, 1280×720 output, frame generation off, same copied warm caches. Stable-window counters show about 63 scans per present. Median FPS across runs was 20.19 with the original scan and 20.19 with direct scanning: no change in fps, as expected while the Deck is GPU-bound here (about 27 ms of GPU time a frame). The gain is CPU time on the replay thread, which the microbenchmark above measures.

Arm and run FPS p95 frame interval (ms) Median GPU clock (MHz)
scan-old1 20.19 52.82 1040
scan-new1 20.28 52.74 1040
scan-old2 20.19 53.27 1040
scan-new2 20.10 53.28 1040

The GPU timing breakdown of this scene is in the Deck evidence PR (#470).

Testing

Tested: Focused gap, word-index, memo and threading checks pass; test_ub_gap.py also checks the empty range with no deque behind it. Source-extracted original and candidate agree for overflow, empty queries, subranges, boundary equality and overlapping writes. Full suite on this branch: 472 passed, 5 failed, 82 skipped. Failures: test_bake_portability, test_lift_cache, test_menu_keys, test_module_ids, test_pad_layout. All of these also failed on unmodified main (cf605c85) in this environment.

Not tested: Windows and other GPUs.

Needs other testers: Windows, any GPU; request to open.

Screenshots

Nothing on screen changes.

Risk and rollback

A false gap would let a descriptor read stale data. The test exercises exact boundaries and restricted update ranges. Reverting the commit restores array materialization.

Reviewer notes

The game runs used an instrumented RC3 renderer (665aef4f4718) with RX_VK_TEST_UNIFORM_GAP choosing the scan; the switch is not in this PR. The Measured box stays unticked until a run on this commit against main.

Issues

Found by profiling Hyrule Field on Steam Deck.

Checklist

For everyone

  • One topic.
  • The title is a Conventional Commit (type(scope): description).
  • Rebased on current main. One commit behind: main gained dc390e59 (crash records, unrelated); rebased before ready.
  • python -I -B tests/run_all.py run and passing (any failure that also fails on main is named above).
  • Tested in the game, or what wasn't is named under Testing.
  • Testing filled in: what was tested, what wasn't, and what needs other testers (with test requests).
  • Measured: main and this PR at named commits, on the same PC, scene and settings, at least 2 interleaved runs each, the median reported.
  • No game files, dumps, build output or generated files.
  • third_party/ untouched (a new folder with a PROVENANCE.md instead).
  • The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled.

If applicable

  • AI assistance recorded with Assisted-by: / Co-Authored-By: trailers.

  • A check under tests/ that reads changed source updated in this PR, not weakened.

  • A file in docs/tested/ for each new measured run.

## Why Each uncached lookup for pending uniform-buffer writes copies two arrays of addresses and then walks them again. Both descriptor reads and uniform-buffer copies pay for this work. ## What changed Scan the existing deque over the requested iterator range. Stop on the first overlap and otherwise cache the same gap. The epoch check still guards publication of a cached answer. A background thread (`ub_limit == 0`, an empty range) builds no iterator into the deque at all, as before: it must not read even its start while the replay thread pushes and prunes. ## Flags and settings None. ## Measurements `main` @ cf605c85 and the scan @ cc0e3ef1 (the empty-range guard, 80d41562, adds no work on the measured path). A differential benchmark compares answers and cache contents across 100,000 randomized spans and explicit boundaries. On the Deck OLED, in a mixed workload of 80 pending updates, the two-pass calculation took 424 ns per query and the direct-scan prototype took 110 ns (medians of five interleaved samples per arm). The benchmark inputs deliberately exercise the slow path. A separate interleaved game comparison uses one instrumented RC3 renderer (665aef4f4718) and a corrected accounting host. Each run settled at child Kokiri spawn, teleported to the Lost Woods bridge entrance, warmed for 20 s, then stood for 45 s. SteamOS 3.9.5, RADV VANGOGH, Mesa 26.2.0-devel, 1280×720 output, frame generation off, same copied warm caches. Stable-window counters show about 63 scans per present. Median FPS across runs was 20.19 with the original scan and 20.19 with direct scanning: no change in fps, as expected while the Deck is GPU-bound here (about 27 ms of GPU time a frame). The gain is CPU time on the replay thread, which the microbenchmark above measures. | Arm and run | FPS | p95 frame interval (ms) | Median GPU clock (MHz) | |---|---:|---:|---:| | scan-old1 | 20.19 | 52.82 | 1040 | | scan-new1 | 20.28 | 52.74 | 1040 | | scan-old2 | 20.19 | 53.27 | 1040 | | scan-new2 | 20.10 | 53.28 | 1040 | The GPU timing breakdown of this scene is in the Deck evidence PR (https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/470). ## Testing **Tested:** Focused gap, word-index, memo and threading checks pass; `test_ub_gap.py` also checks the empty range with no deque behind it. Source-extracted original and candidate agree for overflow, empty queries, subranges, boundary equality and overlapping writes. Full suite on this branch: 472 passed, 5 failed, 82 skipped. Failures: `test_bake_portability`, `test_lift_cache`, `test_menu_keys`, `test_module_ids`, `test_pad_layout`. All of these also failed on unmodified `main` (cf605c85) in this environment. **Not tested:** Windows and other GPUs. **Needs other testers:** Windows, any GPU; request to open. ## Screenshots Nothing on screen changes. ## Risk and rollback A false gap would let a descriptor read stale data. The test exercises exact boundaries and restricted update ranges. Reverting the commit restores array materialization. ## Reviewer notes The game runs used an instrumented RC3 renderer (665aef4f4718) with `RX_VK_TEST_UNIFORM_GAP` choosing the scan; the switch is not in this PR. The Measured box stays unticked until a run on this commit against `main`. ## Issues Found by profiling Hyrule Field on Steam Deck. ## Checklist ### For everyone - [x] One topic. - [x] The title is a Conventional Commit (`type(scope): description`). - [ ] Rebased on current `main`. One commit behind: `main` gained dc390e59 (crash records, unrelated); rebased before ready. - [x] `python -I -B tests/run_all.py` run and passing (any failure that also fails on `main` is named above). - [x] Tested in the game, or what wasn't is named under Testing. - [x] Testing filled in: what was tested, what wasn't, and what needs other testers (with test requests). - [ ] Measured: main and this PR at named commits, on the same PC, scene and settings, at least 2 interleaved runs each, the median reported. - [x] No game files, dumps, build output or generated files. - [x] `third_party/` untouched (a new folder with a `PROVENANCE.md` instead). - [x] The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled. ### If applicable - [x] AI assistance recorded with `Assisted-by:` / `Co-Authored-By:` trailers. - [x] A check under `tests/` that reads changed source updated in this PR, not weakened. - [x] A file in `docs/tested/` for each new measured run.
Assisted-by: OpenAI Codex
Assisted-by: OpenAI Codex
Assisted-by: OpenAI Codex
Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 <[email protected]>
move the Field GPU timing row to the Deck evidence PR
Some checks are pending
CI / Conventional Commit title (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Blocked by required conditions
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Blocked by required conditions
CI / Workflow lint (actionlint) (pull_request) Blocked by required conditions
Third-party parts / As their notes say (pull_request) Blocked by required conditions
Third-party parts / The same as upstream (pull_request) Blocked by required conditions
Website / Website on Cloudflare (pull_request) Blocked by required conditions
PR description / Description follows the template (pull_request_target) Successful in 17s
Website / Website on Cloudflare (pull_request_target) Has been skipped
6a0727c9ae
Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Some checks are pending
CI / Conventional Commit title (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Blocked by required conditions
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Blocked by required conditions
CI / Workflow lint (actionlint) (pull_request) Blocked by required conditions
Third-party parts / As their notes say (pull_request) Blocked by required conditions
Third-party parts / The same as upstream (pull_request) Blocked by required conditions
Website / Website on Cloudflare (pull_request) Blocked by required conditions
PR description / Description follows the template (pull_request_target) Successful in 17s
Website / Website on Cloudflare (pull_request_target) Has been skipped
This pull request is marked as a work in progress.
Some workflows are waiting to be reviewed.
This branch is out-of-date with the base branch
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u 'scan-uniform-updates:zeldaootr-scan-uniform-updates'
git switch 'zeldaootr-scan-uniform-updates'
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
MagicBeanDev/Longshot!472
No description provided.