perf(vk): scan pending uniform updates in place #472
No reviewers
Labels
No labels
area/accessibility
area/audio
area/input
area/install
area/menu
area/renderer
area/saves
area/upscaling
beanstalk: approved @ 1697603
beanstalk: approved @ 2c5a62e
beanstalk: approved @ 4fd82b7
beanstalk: approved @ 64d6d09
beanstalk: approved @ 6f91fae
beanstalk: approved @ 70a78f9
beanstalk: approved @ 8d361dc
beanstalk: approved @ 9d8356e
beanstalk: approved @ abf2227
beanstalk: approved @ d82f202
beanstalk: approved @ e5f1e40
beanstalk: approved @ ee8ab5f
ci: approved
good first issue
gpu/amd
gpu/igpu
gpu/intel
gpu/nvidia
help wanted
kind
bug
kind
chore
kind
docs
kind
feature
kind
performance
kind
question
needs measurements
platform/linux
platform/macos
platform/steam-deck
platform/windows
preview
priority
critical
priority
high
priority
low
regression
status
blocked
status
confirmed
status
duplicate
status
invalid
status
needs-info
status
wontfix
testing wanted
ci: approved
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!472
Loading…
Reference in a new issue
No description provided.
Delete branch "zeldaootr/Longshot:scan-uniform-updates"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Type: perf · Risk: low · Tested on: Steam Deck OLED, SteamOS 3.9.5, RADV (ours and a tester's runs); Linux x86-64 (game-free) · Needs: Windows, any GPU
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 on the replay thread, which sets the frame rate wherever the GPU is not the limit.
What changed
The lookup now scans the pending updates where they are instead of copying their addresses into two arrays first: the same answer and the same cached gap, with less work per query.
ub_gap,ub_try_gap) ingpu/vk/vk_replay.cppscans the existingub_updatesdeque over the requested iterator range. It stops on the first overlap and otherwise caches 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. The elements read are the ones the copy read.tests/test_ub_gap.pyfollows the new scan and checks the empty range with no deque behind it.Measurements
Verdict: no measurable change in the game: on a GPU-bound Steam Deck Field scene 20.60 against 20.75 fps (inside the spread), and on a tester's CPU-bound Deck view 0.2-0.8% fewer instructions a frame, no larger than the spread between two starts; the lookup itself drops from 424 to 110 ns a query in a Deck microbenchmark. No fault anywhere.
Hardware: Steam Deck OLED, AMD Custom APU 0932 (RADV VANGOGH), Mesa 26.2.0-devel (git-5c65d4fc31), 16 GB shared, SteamOS 3.9.5 (kernel 7.2.7-valve2)
Scene: Hyrule Field (child), arriving from Kokiri Forest by teleport to the Lost Woods bridge entrance, day; 1280×720 output, render resolution option 4, no frame generation, precompile off, 1536 MiB texture budget, 256 MiB mirror budget, headless
Method: one
scripts/dev_packs.pybase at main9c9d69ed(release optimisation, x86-64-v3); main is the base's runtime with no pack, this PR a renderer pack (longshot_vk.soonly; the host binary is byte-identical). One warm-up pass of main made the caches, copied fresh into every run. Each run: child Kokiri save settled 20 s, teleport to HyruleField_Child, 20 s warm-up, 45 s stationary sample. Four rounds in one session, interleaved main, #468, #469, #472. Round 3's run of this PR was cut by a 7-second Deck suspend (a keep-awake handover) and is left out; round 4 ran after the resume, with main's round-4 run (20.64 FPS) inside main's earlier spread and the GPU at 1040 MHz again. Medians of the 4 runs of main and the 3 valid runs of this PR. FPS and present intervals fromHOS_PRESENT_TRACEin the 45 s window; GPU clock and busy from sysfs every 0.5 s.Rebased since: the branch is now on main
aa95d35f, the change ate70dc7fa(the same change as 8372892d, which 00ef9f68 carries,git patch-id). Not measured again by us onaa95d35f. Since9c9d69edmainchangedgpu/vk/vk_replay.cppwith a render thread for the replay (#588, Auto: on from 12 logical processors, so off on a Deck) and the Arc occlusion copy (#761), neither in the lookup this PR changes; p0lux's run below is on main9e6f231d, which has #588, and agrees.How to reproduce: build a base at main and a pack of this branch (
python -I scripts/dev_packs.py base|pack|rt, docs/development.md "Testing a change against a base"), start each runtime withHOS_CONTROLand a child save at Kokiri spawn, thenlongshot_ctl.py teleport HyruleField_Child, wait 20 s and sample 45 s.9c9d69edPer run, FPS: main 20.56, 20.54, 20.69, 20.64; this PR 20.32, 20.77, 20.75. The PR's runs straddle main's: no frame-rate change, as expected while the Deck is GPU-bound here (the GPU 99% busy at its 1040 MHz clock, about 48 ms a frame).
A tester's run where the replay thread sets the frame rate: p0lux, 2026-11-15 (#472 (comment)). Steam Deck OLED (RADV VANGOGH, SteamOS 3.9.5, 16 GB), the compute fallback, headless; a still heavy view loaded from a save, about 2,150 draws a frame, the replay about 25 ms of a 30 to 32 ms frame. The render thread's user-space instructions and cycles a frame from
perf statover 720 presents. The two builds are the renderer alone, swapped in one game folder built from main9e6f231d, without and with this PR and #551 together (18243506,d4c9070bande8faf57echerry-picked), on top of other open PRs that tester plays with, the same in both; two starts a build, in both orders.The same sign on both pairs, but no larger than the spread between two starts of one build there (about 0.6% on instructions): no cost, and no gain the tester can state. No fatal line and no GPU timeout in the kernel log on the four starts. Its limits: built with #551, and with the committed play profile, under which a changed function is compiled without its profile until the next recording, which can hide a small gain.
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 110 ns (medians of five interleaved samples per arm); the inputs deliberately exercise the slow path. Earlier, an instrumented v3.0.0-rc.3 renderer (665aef4f4718) with a test switch choosing the scan (
RX_VK_TEST_UNIFORM_GAP, not in this PR) gave 20.19 FPS both ways over two interleaved runs each, about 63 scans per present (docs/tested/2026-11-14-field-renderer-zeldaootr-scan-*.md).Flags and settings
None
Testing
Tested: On the Steam Deck OLED above, this PR interleaved with main through the Kokiri Forest to Hyrule Field transition: Field reached every time (status map
HyruleField_Child, 915-935 presents in each 45 s sample), no device fault, noVK_ERROR, no crash record, the run's low-memory guard never tripped, every run exited 0. p0lux's still-view Deck run (in Measurements): no fatal line, no GPU timeout. Source-extracted original and candidate agree for overflow, empty queries, subranges, boundary equality and overlapping writes. Game-free, on this branch rebased ontomainaa95d35f(57723ffb):tests/test_ub_gap.pypasses with g++ 13.3 and with clang 23.1.5; at the tip, the 88 checks that readgpu/vk/vk_replay.cppor the changed test: 85 passed, and 3 failed that fail or are skipped by OS onmainaa95d35ftoo (test_readback_late,test_walk_key, andtest_present_aspect_vulkan, which needs a GPU and was run here by name). With #468 and #469, as rebased, the branch merges cleanly (git merge-tree).Not tested: The replay thread's CPU time in our own runs (the counters that measured it were in the instrumented build only; p0lux's
perf statis the in-game number); the lookup on #588's render thread (off on a Deck); Windows and other GPUs and drivers in the game.Must be tested:
Risks
What could break: a false gap would let a descriptor read stale data. The test exercises exact boundaries and restricted update ranges, and the differential benchmark compares the answers of both versions.
Turning it off: revert the commit
Compatibility: Nothing changes
Issues
None filed; found by profiling Hyrule Field on the Steam Deck. The GPU timing breakdown of this scene is in the Deck evidence PR, #470.
Checklist
Everyone
type(scope): description) of the status line's type.main. Onto mainaa95d35f, no conflict; the three commits are the same changes as before (git patch-id).python -I -B tests/run_all.pypasses (any failure that also fails onmainis named). The full suite on mainaa95d35f(Linux, g++ 13.3, the Vulkan headers set): 528 passed, 15 failed, 50 skipped; on this branch the changed test and the 88 checks that read the changed files ran, with no failure thatmaindoes not have (see Testing). The 15 that fail on main:test_appimage_player_build,test_bake_portability,test_cpm_discovery,test_crash_dialog,test_env_on,test_lift_cache,test_lift_cleanup_error,test_make_game_cache,test_mods_size_table,test_options_budget_once,test_pipeline_tour,test_readback_late,test_vk_pgo,test_walk_keyandnx2/test_texture_init_barrier.py.third_party/untouched (a new folder with aPROVENANCE.mdinstead).When it applies
Assisted-by:/Co-Authored-By:trailers.docs/tested/for each new measured run (format indocs/tested-configurations.md).tests/that reads changed source updated in this PR, not weakened.RX_VK_ONLY=1 RX_RELEASE=1 RX_TARGET=windowswith llvm-mingw 20261104 linkedlongshot_vk.dll, and the same withoutRX_TARGETlinkedlongshot_vk.so; after the rebase,gpu/vk/vk_replay.cppat57723ffbchecked whole (-fsyntax-only -Wall -Wextra) with clang 23.1.5 for Linux and llvm-mingw 20261104 for Windows: the same diagnostics asmainaa95d35f's (60 and 60), none new. The diff has no path handling.ub_updateswhen their range is empty 80d415626c6a0727c9aea2b24e4bafRebased onto main
1be60eaeand rewritten: the code change is one commit, the Field GPU timing record that belonged to another PR is gone from the history, and the run records are renamed to the tester handle. Measured on a Steam Deck OLED against main: three valid rounds here (a fourth was interrupted by a suspend and is left out), medians 20.60 fps on main and 20.75 here: no change beyond the spread, as expected in a GPU-bound scene. Still WIP: not built for Windows yet; the test request asks for that.Built for Windows from Linux with llvm-mingw 20261104 (the renderer alone, RX_VK_ONLY, as the CI job does) and for Linux: both link, and the changed file has no new warning under -Wall -Wextra on either. The Windows box is ticked; the Windows test request stays open for a run on real hardware.
WIP: perf(vk): scan pending uniform updates in placeto perf(vk): scan pending uniform updates in placeA Steam Deck run in a view where the replay thread sets the frame rate, to go with the GPU-bound Field numbers.
Setup: Steam Deck OLED (RADV VANGOGH, SteamOS 3.9.5, 16 GB), the compute fallback, headless. A still heavy view loaded from a save: about 2,150 draws a frame, the replay about 25 ms of a 30 to 32 ms frame. The render thread's user-space instructions and cycles a frame come from
perf statover 720 presents. The two builds are the renderer alone, swapped in one game folder built from main9e6f231d.The same sign on both pairs, but no larger than the spread between two starts of one build here (about 0.6% on instructions): no cost, and no gain I can state. No fatal line and no GPU timeout in the kernel log on the four starts.
Limits: the two builds differ by this PR and #551 together (
18243506,d4c9070bande8faf57echerry-picked), on top of main9e6f231dplus the open PRs I play with (#223, #384, #387, #391, #567, #568, #590 and a local change), the same in both. Both use the committed play profile, and here a changed function is compiled without its profile until the next recording, which can hide a small gain. Two starts a build.Thank you, p0lux, for the CPU-bound Deck run: it is in the description as you reported it (0.2-0.8% fewer instructions a frame, inside the start-to-start spread, fps unchanged, built together with #551), beside our GPU-bound Field numbers. Rebased onto
mainaa95d35fwith no conflict (the same three changes,git patch-id) and the description moved to the current template.tests/test_ub_gap.pyand the 88 checks that readvk_replay.cppwere run again on the new base (no failuremainlacks); our Field numbers still hold becausemain's changes tovk_replay.cppsince9c9d69ed(#588's render thread, off on a Deck; #761's Arc occlusion copy) do not touch the lookup, and p0lux's run on9e6f231d, after #588, agrees. T-NTAP (Windows) still waits for thetesting wantedlabel.a2b24e4baf57723ffb41View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.