fix(vk): order overlapping inline buffer updates #329
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 @ 8015c9a
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 project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!329
Loading…
Reference in a new issue
No description provided.
Delete branch "klimovich008/Longshot:fix/inline-write-order"
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?
Why
Overlapping inline uniform updates can race on Vulkan, reporting SYNC-HAZARD-WRITE-AFTER-WRITE.
What changed
gpu/vk/vk_replay.cpp: One-line change.full_barrier(cmd);orders writes; CPU fallbacks/chunking stay unchanged.tests/test_inline_write_order.py: real helper/barrier/deduplication, with two failing controls.Flags and settings
None.
Measurements
Native record-time WAW: main@514591a3 1, this PR@7b2c70f 0 (one case; no submits).
AMD RX 9070 XT, Windows driver 32.0.32015.12012. No performance claim.
Testing
Tested:
python -I -B tests/run_all.py --jobs 4 --timeout 480: 494 passed, 6 failed, 34 skipped. Failures reproduce on unchanged main:test_release,test_check_third_party,test_spawn_emit,test_fpx_sticky,test_nup_paths,test_menu_i18n.Fails on the old code, passes now.
Rebased onto main
4bab3915(d99befb3; main changed none of the files the fix or its test reads since514591a3):tests/test_inline_write_order.pyrerun there: current passes, both failing controls fail. The full run above is from514591a3.Backport: 96s Hyrule, zero WAW; BC02251 and timing failures remain.
Not tested: main-game integration, repeated play, other hardware/OS.
Needs other testers: NVIDIA/Intel/Linux: check update ordering (request to open).
Screenshots
Validation-only claim.
Risk and rollback
Extra barriers may cost GPU time. Revert this commit.
Issues
Related: #189 (flush timing).
Checklist
For everyone
type(scope): description).main.python -I -B tests/run_all.pyrun and passing (any failure that also fails onmainis named above).third_party/untouched (a new folder with aPROVENANCE.mdinstead).If applicable
Assisted-by:/Co-Authored-By:trailers.mainand passing here.tests/that reads changed source updated in this PR, not weakened.WIP: fix(vk): order overlapping inline buffer updatesto fix(vk): order overlapping inline buffer updatesPost-merge code review of
d99befb3, merged ased088cb6. It merged while I was reviewing it, so this is a comment, not a review. Read only, nothing built or run here.The hazard is real, but the fix costs more than it needs to, and it went in unmeasured (the Measured box was unticked and there was no table).
flush_ub_updates(gpu/vk/vk_replay.cpp) already puts onefull_barrierbefore and one after a recording's uniform updates. Line 889 now adds an ALL_COMMANDS to ALL_COMMANDS memory barrier before everyvkCmdUpdateBuffer, so N updates in a recording cost N+1 barriers. Each update counts as work (note_work), so the dedupe never skips one. NVN games update the same uniform block many times a recording (why the overlay copies exist), so N can be large, and this is the replay's hot path.For v3.0.0: it's on main, so it ships unless someone acts. I'd want one of these before the release:
ed088cb6against its parent9ef17269, several interleaved runs, plus theubupdspan fromRX_VK_PASS_TIMING. If the cost is noise, keep it.full_barrieronly when a write meets one of them.flush_ub_updates, apply the recording'sub_updatesin stream order into merged ranges (later bytes win) and write each once. No WAW is left to order, fewer bytes go out, and no barrier is added.tests/test_inline_write_order.pyshould also assert that two disjoint writes add no barrier.Also left open:
test_requests.py).d99befb3were still pending (run 2842) when it merged.Thanks, HumanBean. Agreed on the cost: it was opened as WIP with Measured unticked, and it merged before that was done. Each point:
Measuring now (your option 1). The standard test: Hyrule Field, 16:9, the fixed Custom profile,
HOS_FPS_MAX=240, 3 alternating pairs. The merged line as a renderer-only pack on main839e85ff(patch-identical to ed088cb6's change, which is the only difference betweened088cb6and its parent in the replay) against the same base without it, on an RX 9070 XT, AMD 26.10.2, Ryzen 7 5700X3D, Windows 11. Reported: fps, CPU ms a frame, p99, worst frame, plus the renderer's own GPU time and barriers-recorded lines a frame from each run's log. Results go here when the runs are done.A cheaper form (your option 2). A follow-up from Codex, who wrote the fix: the CPU merge in
flush_ub_updates(the recording's updates applied in stream order into merged ranges, later bytes winning, each written once: no barrier added, fewer bytes) or, if that does not fit the overlay copies, the barrier only when a write meets a range written since the last barrier. Either waytests/test_inline_write_order.pygains the case you name: two disjoint writes add no barrier.Revert (your option 3). If the A/B shows a cost and the follow-up is not ready before v3.0.0, we will open the revert ourselves.
The rest:
839e85ff, the same line plus clock and texture-creation diagnostics, with validation on; RX 9070 XT, AMD 26.10.2, Windows 11; one 96 s run. It showed zero write-after-write reports against the control's ten; it is not a performance or picture pass (that run also had BC5/BC602251reports and a rejected timing window).Measured (HumanBean's option 1): on this PC the added barriers cost about 0.06 ms of GPU time a frame and no measurable frame rate.
main:
839e85ff(2026-11-13)this change: the merged line (
d99befb3, merged ased088cb6) as a renderer-only pack on839e85ff(diag/waw839 @ 96b445d4,longshot_vk.dllonly)Hardware: RX 9070 XT, AMD 26.10.2, Ryzen 7 5700X3D, 64 GB, Windows 11
Scene: the standard test: Hyrule Field from its save, camera turning at full speed, 16:9, the fixed Custom profile,
HOS_FPS_MAX=240, 20 s windowMethod: 3 alternating pairs (warm-ups first), the renderer's own lines from each run's log (the last four 10 s summaries)
839e85ffThe medians are not a gain: three of the six runs (two controls, one with the line) had stalls of 800-890 ms that pulled them to 61-62 fps, and two of those three were controls. The stall-free runs agree within 0.6 fps. Labelled diagnostic (3 pairs, a difference under 5 %).
So in Hyrule on this card the barrier count rises by about one per inline update (+78 a frame, 80 updates), and the GPU pays about 0.06 ms for them; the frame is limited by the CPU's replay here, so fps does not move. Not measured: other GPUs, where a full barrier can cost more (NVIDIA, Intel, the Steam Deck), and busier scenes such as the Castle Town market. That is the case for the cheaper form (option 2), which Codex is preparing: with it, this table should show the barrier count back at main's.