perf(vk): RX_VK_READBACK_EARLY, a read of GPU results written in place waits for its writer's part, not the whole submission #303
No reviewers
Labels
No labels
area/accessibility
area/audio
area/input
area/install
area/menu
area/renderer
area/saves
area/upscaling
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/steam-deck
platform/windows
preview
priority
critical
priority
high
priority
low
status
blocked
status
confirmed
status
duplicate
status
invalid
status
needs-info
status
wontfix
testing wanted
No milestone
No project
No assignees
3 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!303
Loading…
Reference in a new issue
No description provided.
Delete branch "dukenheimer/Longshot:opus/early-readback"
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
On the Steam Deck the game's job threads spent ~18 ms of every frame sleep-polling for a GPU result (the culling camera block at +0x3980) that the GPU had finished computing long before: the read waited for the end of the whole 22-26 ms submission, not for the dispatch that wrote it. This lets such a read go on once its writer is done, opt-in for now.
What changed
RX_VK_LOG_RB_WRITERS=1(withRX_VK_LOG_READBACK_WAITS=1): for each read of GPU results that waits, what wrote its pages (the op, its program, its part and draw in the submission), when the last writer's part was done into the wait, and how many bytes changed after it; a summary by size every 3000 reads.memory_writer_note,memory_count_work,memory_rb_writersingpu/vk/vk_memory.cpp;rb_diag_*and the call ininvalidateingpu/vk/vk_replay.cpp. It is how the writers below were found. Its wait comes afterRX_VK_READBACK_LATE's rule and only for a read that waits; a submission's span is closed on every path that finishes its recording, a burst's end included (rb_span_close).RX_VK_READBACK_EARLY=1(opt-in), for pages the GPU writes in place (no VRAM mirror: integrated GPUs, the Steam Deck):memory_rb_waited, at most 64 ranges), every GPU write in place to it keeps its submission and the part it was recorded in, whatever its size (early_in_place_write, called fromnote_direct_write);memory_in_place_writer_part; the newest of it and a mirror's part), when every page of the range that the read's submission wrote in place has that write seen, else for the whole submission as before (a range widened over a page written before, or a page tracked later); a write to any byte of a page a waited range reaches is kept for the page, and only a write of the waited bytes ends its part (invalidate, before the usual choice, which it replaces only when it waited);split_part(cmd, true)inreplay), at leastRX_VK_READBACK_EARLY_GAPdraws (16) into the part, so writers in a row end one part;maybe_splitissplit_part(cmd, false), unchanged;same_descingpu/vk/vk_draw.cpp), so each such write is noted with its part;RX_VK_PARALLEL≥ 2 (logged): a parallel replay's segments are numbered as parts but never put on the recordings' timeline, so a read could wait for a part that never comes; ballotstomper also found the part before a command buffer's segments written back late there (team room);RX_VK_READBACK_LATE(#238, on main since): a range its rule names is read late as before, with no wait, and the early wait applies only to the other reads. Both switches act on the same waits ininvalidate; whether they stay two switches, become one, or this one closes is asked on #238 (#238 (comment)) and is being measured on the Deck (off /newest/ this / both, one build).tests/test_readback_early.py(new): the tracking cut fromvk_memory.cppand run with the switch on, off and withRX_VK_PARALLEL=3;invalidate's early wait cut out and run against stubs; the wiring inreplay,split_part,run_submit,note_direct_writeand the descriptor reuse. a rangeRX_VK_READBACK_LATEnames takes no early wait.tests/test_parallel_small_cb.pyandtests/test_split_ub_flush.pyfollowmaybe_splitintosplit_part;tests/direct_buffer_writes_test.cppstubsearly_in_place_write;tests/test_readback_part.pystubs the early wait (off) in its cut ofinvalidate;tests/test_late_fix_build.pydeclares the writers' hook inwb_pages_note. From the review: a range widened over a page the same submission wrote before, two ranges in one page, a write really over 1 MiB, and a burst's span (burst_endand the spans cut out and run);tests/test_readback_part.pyruns the late rule with the diagnostics on.docs/development.mdhas both switches and the order withRX_VK_READBACK_LATE; four run files indocs/tested/(the Deck runs below).No translator file changes: the shader caches stay valid.
Flags and settings
RX_VK_READBACK_EARLY=1RX_VK_READBACK_LATE=newestsays otherwise; off elsewhere until measuredRX_VK_PARALLEL≥ 2 (left off)RX_VK_READBACK_EARLY_GAP=<draws>RX_VK_LOG_RB_WRITERS=1Measurements
Steam Deck with the review fixes (
52306c0c), the player's build, two runs per arm: off 11.5 / 11.1 real fps, on 13.5 / 13.2 (+2.05, +18%), no read falling back to its whole submission. Before them, current main with #238, two usable runs per arm: off 10.8 / 11.4 real fps,RX_VK_READBACK_EARLY=112.6 / 12.8 (+1.6, +14% on the means), against #238'sRX_VK_READBACK_LATE=newest12.0 / 11.6. Both switches together: 12.7 / 13.0, level with this one alone. The earlier tables below (+2.3 to +3.0) have one usable off run on the head, or on runs 16.8-19.5 wide: supporting only. RTX 5090: idle at98cfaf0, which is before #250's sparse mirrors; with them a few pages are written in place on a discrete card too, and that path is not measured there.main:
cf605c85(2026-11-13, with #238, #250, #252)this PR:
9e50672(the code of025d8e98) in the Deck build 22253fd = maincf605c85+ #225 Deck profile (d9b3551) + #223 (52a61f4) + this PR; one build for every arm. Measured before the review fixes (52306c0c: a part known only when every write of the submission to the range's pages was seen; the diagnostics' wait after the late rule; the diagnostics' span closed at a burst's end); the next Deck A/B re-measures off against on with them.Baseline: A: this PR @
9e50672, nothing set (with no switch set this PR's code is idle: main's path; one build isolates the switches from #225 and #223, which the Deck build needs).Hardware: Steam Deck OLED (AMD Custom GPU 0932, RADV VANGOGH, Mesa 26.2.0-devel), 4C/8T Zen 2, 16 GB, SteamOS 3.9.5, stock clocks.
Settings: the player's own: handheld profile, the game drawn at 1536x864 (a crop of 2560x1440) at 100% render, a 1280x720 FIFO swapchain, frame generation 3x; no longshot_env.txt;
RX_VK_LOG_READBACK_WAITS=1in every arm.Scene and method: Kokiri Forest, the moving route (walk and camera pans, 125 s, measured 30-110 s), warm, interleaved A B C D D C B A; real fps from the harness's frame log (the game's frames; "shown" with frame generation); 15 route shots and 4 at the start a run, compared at the same points; one unmeasured flash-detector run each for A, B and D (
RX_VK_FLASH=1).9e50672, nothing set9e50672,RX_VK_READBACK_LATE=newest9e50672,RX_VK_READBACK_EARLY=19e50672, bothnewestlets the newest submission's ranges go late but every other read still waits for its whole submission (13-15 ms each).newestdoes not have this cost.With the review fixes, on the player's build (deck-user-1113b @ 832a8a12 = main
cf605c85+ #227 with #225 + #223 + #232 + this PR @025d8e98, plus the review fixes52306c0cand a local follow-up whose switch,RX_VK_READBACK_EARLY_CAPTURE, is off in both columns here: its code then only calls an empty hook; longshot_vk.so 4756e5e7, one build; the same settings and route, interleaved A B (C C) B A, run opusab):52306c0c,RX_VK_READBACK_EARLY=052306c0c,RX_VK_READBACK_EARLY=1The follow-up (C, on in two runs between them) made those captures wait for their late pages' writers' part: it engaged on every one (89, 92) but each still waited ~20 ms, the writers' parts ending about where the submissions do, and ran 12.7 / 12.8: no gain, so it is not proposed. Route shots normal in every arm.
Earlier runs (supporting, before main had #238):
main:
514591a(2026-11-13)this PR: opus/early-readback @
5b31bc0(the Deck build: ae1cffd = main514591a+ #225 Deck profile + this PR @5b31bc0+ #252 movie input limit); the PC pair @98cfaf0Baseline: this PR @
5b31bc0, RX_VK_READBACK_EARLY unset. With the switch unset this change's code is idle, which is main's path; one build for both arms isolates the switch from #225 and #252, which the Deck build needs and main does not have.98cfaf0(after the Deck build) only keeps the switch idle where no page is written in place: on the Deck every page is, so its path there is 5b31bc0's.Hardware: Steam Deck OLED (AMD Custom GPU 0932, RADV VANGOGH, Mesa 26.2.0-devel git-5c65d4fc31), 4C/8T Zen 2, 16 GB (swapping in every run), SteamOS; docked to 3440x1440, game output 2048x1152, FSR 3.1 at ~50%, frame generation 2x (the Deck profile). PC: RTX 5090, Windows 11, the PC runner's bench folder.
Scene: Kokiri Forest, the moving route (walk and camera pans, 125 s, measured 30-110 s); the Deck: the user's options, no longshot_env.txt; the PC: M1152 (2048x1152 window) and MP165 (5120x2160 at 165 Hz)
Method: the Deck agent's A/B harness and the PC runner's bench; runs interleaved off / on / off / on after a cold start that prepared the shaders;
RX_VK_LOG_READBACK_WAITS=1in both arms; fps and frame times from the harness's frame log ("real": the game's frames; "shown": with frame generation); screenshots through the route; flashes and luma on the PCHow to reproduce:
RX_VK_READBACK_EARLY=1in longshot_env.txt, Kokiri Forest; the log'sreadbacks waited for their writer's partline every 3000 reads5b31bc0, RX_VK_READBACK_EARLY unset5b31bc0, RX_VK_READBACK_EARLY=1The cold start before them (switch unset, shaders prepared in it) ran 15.0 real fps on a lighter stretch (1,264 draws a frame); not counted. All four runs stayed on the outdoor route; the picture is normal in every shot.
readbacks waited for their writer's part (RX_VK_READBACK_EARLY): 749-988, 0.98-2.29 ms each; 0 for their whole submission (writer not known); 800-863 parts ended after a readback's writer.98cfaf0, RX_VK_READBACK_EARLY unset98cfaf0, RX_VK_READBACK_EARLY=1On the PC the switch logged
0, 0.00 ms each; 0 for their whole submission (writer not known); 0 parts endedin every run: idle, as it should be with VRAM mirrors. The first PC pair, on8f6ac8fbefore the idle check, cost ~2 fps at M1152 (73.66 -> 71.71) from bookkeeping that never acted;98cfaf0removed it (comment of 2026-11-13).Supporting: the Steam Deck user build, interleaved (deck-longshot-user 92640867 = main
e7ed2f7a+ #225 + memory pressure + #227 + #232, with this change as 996e76b, pushed as opus/early-readback-deck-measured; one build for both arms):Earlier single runs (with HOS_CHAIN), why it helps:
The frame's path is now the render thread (the inline replay and the game's render work, 32 of 58 ms), then the main thread.
What the diagnostic run showed (236 waits for +0x3980, 23 for +0x8b040/+0x9b040): the culling camera block (+0x3980, 1.1 reads a frame) is written by compute dispatch fd958faab99f20a2, its first writer 7984161db3e053b5 one or two parts before, at the median 49% of its submission; on average its part was done 6.4 ms into an 18.9 ms wait, and in no wait did a byte change after it. The +0x8b040/+0x9b040 rings are written by dispatches at ~43% (when +0x3980's wait was skipped outright in an earlier test, these took its place with 5.5-12 ms waits: here their writer's part ends early too).
The cost: each early part end is a part's end (the uniform updates, the write-back, a barrier, a submit) and the next one's start; replay submit time rose 8.3 ms a frame at stock (memory sync 1.4 -> 7.1), but only ~1 ms of it on the render thread's path. Most of it is not the part ends: the capture before each game submit waits for its "late pages" (the game writes the camera block again while the submission still runs), for the whole submission (36-71 of 600 takes, ~8 ms each). A follow-up PR makes those wait for their writers' part (being measured on the Deck).
Testing
Tested:
python -I -B tests/run_all.py@7da13e56(the review fixes; Linux also @52306c0c) and @025d8e9on maincf605c8(with #238, #250, #162), the same failures at each, Linux (Ubuntu 22.04 container, clang 23, the CPM Vulkan headers) and Windows (llvm-mingw): exactly main's failures. Linux 7, each failing on main @cf605c8in the same container (test_cpp_preflight,test_dev_packs,test_framegen_resources,test_module_ids,test_pad_layout,test_prof_clock, the byte-compile without.git), 523 passed against main's 522. Windows 7 (test_fpx_sticky,test_bake_portability,test_module_ids,test_pad_layout,test_spawn_emit,test_tested_configurations(its CRLF pipe check),test_oot_state_tap(named pipe)), each also failing on main here; 478 passed. On the way, main's newertest_split_ub_flushandtest_late_fix_buildcutmaybe_splitandwb_pages_note; both follow them in this PR. The Linux renderer (RX_VK_ONLY) builds clean.tests/test_readback_early.pywith gcc 13 (WSL) and clang 23 (container, llvm-mingw).98cfaf0, M1152 and MP165, 2 interleaved rounds (above): idle, no fps or p99 change, flashes within main's range, 0 bad log lines (no screenshots in that pair: a bench bug left them off on both arms).Not tested:
98cfaf0, before #250's sparse mirrors made a few pages written in place there too.Needs other testers:
readbacks waited for their writer's partline and fps not lower (T-346Y; claimable once a maintainer adds thetesting wantedlabel)Screenshots
Nothing changes on screen by design; the picture check is under Testing (38 shots on the Deck, normal).
Risk and rollback
The reading job goes on while the rest of the submission is still on the GPU, as on the console, whose GPU is at that point when the game polls. The parts after the writer may still read guest memory in place, which the game, let go, may write: it reads only the writer's results and the game's own fences still wait for whole submissions, and no byte changed after the writer in the diagnostic run, but a game whose read is followed by writes to memory that same submission still reads would see them early (issue 997 was reads let go before their writer, which this doesn't do). Off by default;
RX_VK_READBACK_EARLYunset or 0 is the code path as before (the tracking and the splits are behind it); left off with a parallel replay.Reviewer notes
Read
early_in_place_writeandmemory_in_place_writer_partingpu/vk/vk_memory.cpp, then the early wait ininvalidateand the split inreplay(gpu/vk/vk_replay.cpp). The per-part cost is left for a follow-up on purpose. WithRX_VK_READBACK_LATEthe order is ininvalidate:latefirst, then the early wait, then the usual choice.Issues
See #225 (the Deck profile, where a default would go).
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.docs/tested/for each new measured run (format indocs/tested-configurations.md; no appended row or assembled view).mainand passing here.tests/that reads changed source updated in this PR, not weakened.Interleaved confirmation on the Steam Deck OLED (one build for both arms, only
RX_VK_READBACK_EARLYdiffers, Kokiri Forest moving route, the user's options):The description's Measurements section now has both tables; the earlier single runs and the HOS_CHAIN split are kept below them. The device is a Steam Deck OLED, not LCD as the first version said.
docs/tested-configurations.mdhas the two runs (5b31bc0). The PC no-regression pair (RTX 5090) is still running.PC no-regression pair (RTX 5090), first result on
8f6ac8f: the switch never acted there, but cost ~2 fps (M1152 73.66 → 71.71, −1.84 and −2.05 in the two rounds; p99 +0.9 ms; MP165 round 1 −1.21). The B arm logged 0 early waits, which is right: with VRAM mirrors no page is written in place. The cost was its bookkeeping, which ran anyway:wb_pages_mu;98cfaf0(pushed) makes the switch idle until a read of a page written in place waits:On the Steam Deck every page is written in place, so its path and the numbers above are unchanged. The pair is running again on
98cfaf0.tests/test_readback_early.pychecks the idle state (nothing noted for a read of mirrored pages only; no tracking before an in-place read waited).proci referenced this pull request2026-11-14 00:01:43 +00:00
Measured on the head, and ready for review.
514591a(build ae1cffd = main + #225 + this PR @5b31bc0+ #252; switch unset vs on, interleaved, stock clocks):98cfaf0: the switch is idle (0 early waits, 0 fallbacks, 0 parts ended in all 243 log lines). M1152 74.26 → 74.04 fps (+0.06, −0.50), MP165 67.42 → 69.77 (noise), p99 not worse, flashes within main's range. The −2 fps of8f6ac8fis gone.98cfaf0: Linux 500 passed, Windows 456 passed, failing exactly main's 5 / 6.The user-build confirmation (+3.0 at stock, +2.2 at 1600 MHz) stays as supporting evidence. T-346Y (other AMD APUs) waits for the
testing wantedlabel.WIP: perf(vk): RX_VK_READBACK_EARLY, a read of GPU results written in place waits for its writer's part, not the whole submissionto perf(vk): RX_VK_READBACK_EARLY, a read of GPU results written in place waits for its writer's part, not the whole submission98cfaf00b89e5067211bRebased onto main
76699aa, over #238 (RX_VK_READBACK_LATE). Conflicts were ininvalidate: a range #238's rule names is read late as before, with no wait, andRX_VK_READBACK_EARLYapplies only to the other reads. Main's newer checks (test_split_ub_flush,test_late_fix_build) followsplit_partand the writers' hook. The Deck runs are indocs/tested/(the frozen table). How the two switches combine is asked on #238; a Deck A/B of off / newest / early / both is requested. Head9e50672.9e5067211b025d8e98d2Rebased again onto main
cf605c8(#162, #250, #252); only the docs conflicted. Head025d8e9. One note on #250: with sparse mirrors, a dGPU write to a block that has no memory yet goes in place for that submission, so on a discrete card the early wait can act on those pages. The docs and the comment ininvalidatenow say so (the RTX 5090 pair predates #250). Suites: exactly main's failures on Linux and Windows. The Deck A/B (off / newest / early / both) is being built from maincf605c8+ #225 + #223 + this.Reviewed head
025d8e98against maincf605c85: the full diff, surrounding readback and submission paths, all comments and the discussion on #238; the six added or changed test drivers and seven related drivers pass here, including GCC and Clang for every compiled fixture. No game or renderer build was run.What's good
Must change
Require complete writer coverage before returning an early part.
gpu/vk/vk_memory.cpp, lines 2043–2051, skips pages missing fromearly_pagesand accepts the range when any tracked page has the submission valuewhole. For example, first learn page A alone. In submission 11, write A in part 7 and an untracked page B in part 8. A first read covering A and B getswhole == 11; adding that wider range inmemory_rb_waiteddoes not recover B's earlier write. This function returns part 7, andinvalidatereleases the CPU before B's writer has finished. Main waits for the submission instead. Check that every pending in-place portion of the requested range has complete writer history, otherwise keep the whole wait. Add a case where a learned range is widened after another part wrote the newly included bytes, including two subranges of one page.Apply the late rule before the diagnostic wait.
gpu/vk/vk_replay.cpp, lines 3232–3233, callsrb_diag_waitbefore computinglate. WithRX_VK_LOG_RB_WRITERS=1and a matchingRX_VK_READBACK_LATErule, that function already waits for the writer's part. The read is no longer wait-free, despite the later!lateguards, and the diagnostic can change both the values read and the timing being compared. Decide whether this read will wait first, and do not add a wait to a late read. Exercise the diagnostic-enabled path as well: the new choice fixture starts after this call and cannot catch it.Finish the diagnostic span when a burst closes.
gpu/vk/vk_replay.cpp, line 2583, callsrb_span_endonly outside a burst. The normal event-ordered path entersrun_submitwithburst_openset, then finishes throughburst_endandfinish_recording, neither of which closes the span. Its last part and total work remain zero. The log then substitutes the last writer's work count for the total, reporting that writer as the end of the submission and printing a last part of zero. Close the span on the common finishing path and cover a normal burst with work after the writer. These fields are used to establish how early the writer finishes.Measurement and smaller points
newest/ early / both comparison supplies two usable runs per arm and reports their spread. The discussion at #238 (comment) remains open; no naming or default decision is needed to fix the issues above.tests/test_readback_early.py, line 128, calls 64 pages a write beyond 1 MB. That is only 256 KiB. Use a range larger than 1 MiB for the claimed boundary case.Not checked
No Steam Deck, Windows, other GPU, long-session or visual testing here. I checked the published tables and quoted lines, not the underlying captures or screenshots. The source checks pass, but do not cover the three cases above. The current source-check jobs are blocked, not green: https://git.magicbean.dev/MagicBeanDev/Longshot/actions/runs/3821.
@mooki, thanks. All three must-change points are fixed in
52306c0c; head7da13e56.Writer coverage.
memory_in_place_writer_partnow walks every page of the range. Each page whose in-place write-back is the read's submission (asmemory_writeback_valuefound it,wb_pages/wb_parts) needs that write seen inearly_pages, with the same value; otherwise the read waits for the whole submission, as main does. Pages an older submission wrote are covered by any part of this one, because the recordings' timeline is in order. Tracking is now per page: a write to any byte of a page that a waited range reaches is kept for the whole page, so two ranges in one page can't hide a later part. Only a write of the waited bytes still ends its part. New cases intests/test_readback_early.py:The previous code fails four of them (checked).
Late rule first.
rb_diag_waitnow comes afterlateand runs only forpending && !late.tests/test_readback_part.pynow cuts that call with the choice and runs it with the diagnostics on: with a matchingnewestrule there is no wait, and without a rule the diagnostic wait comes first. A source check also fails ifrb_diag_waitappears before the late rule.Span at a burst's end. A new
rb_span_close()runs before everyfinish_recording: run_submit's,burst_end's and a resumed queue's capture.burst_endand the span functions are cut out and run with a burst that does work after its writer: its span closes with the last part and the total work. A source check counts thefinish_recordingcalls.Smaller points
98cfaf0and listed under Not tested for this head, with #250's sparse mirrors named.Measurement
The interleaved Deck comparison is in, on main
cf605c85with #238, two usable runs per arm (they were measured before these fixes):RX_VK_READBACK_LATE=newestRX_VK_READBACK_EARLY=1The headline is now this table, with the spread. The earlier +2.3 to +3.0 runs are marked as supporting only.
One cost is stated there too: with this switch the next submit's capture waits for late pages, about 20 ms of memory sync a frame. A follow-up for that is on the Deck now, in the same A/B that re-measures off against on with these fixes.
Suites at
7da13e56fail exactly main's tests on Linux and Windows. Not tested: the fixes on the Deck (that A/B is queued).Read head
7da13e56, fix52306c0cand its parent025d8e98: the complete diff, updated description, all comments and the earlier review; ran both changed test drivers with GCC and Clang, their regression cases against the parent, three related checks, and the mixed-write case below. No game or renderer build was run.What's good
The new tests catch the earlier small-write coverage, late-diagnostic and burst-span failures. The fix keeps the existing whole-submission fallback and does not change the switch's opt-in default.
Earlier must-change points
Writer coverage: partly settled.
gpu/vk/vk_memory.cpp, lines 2025-2054, now follows writes to any byte of a learned page and rejects missing history for pages stamped withwhole. The four regressions attests/test_readback_early.py, lines 144, 155, 157 and 159, fail against025d8e98and pass at this head with both compilers. The later rewrite at line 147 also selects the later part. However, the new filter misses a current large write when the plain page table still names an older submission. That case is below.Late rule before diagnostics: settled.
gpu/vk/vk_replay.cpp, lines 3247-3249, now decideslatebefore callingrb_diag_wait, guarded bypending && !late. The diagnostic-enabled cases attests/test_readback_part.py, lines 240-247, pass with GCC and Clang. Against the parent, the new source guard fails; compiling the same fixture with the parent's actual diagnostic prefix included also fails the no-wait assertion. This checks the formerly missed call, not just the later choice.Span at a burst's end: settled.
gpu/vk/vk_replay.cpp, lines 2457, 2584 and 2612, closes the span before every recording finish; lines 2962-2964 use the still-live recording's value and part. The new burst fixture attests/test_readback_early.py, lines 261-272, fails against the parent and passes here with both compilers. It retains the last part and total work after work following the writer. The finish-site source guards pass too.The parent runs used the new fixture bodies. Their drivers stop first at source guards, so I also compiled the memory and span fixtures separately and included the old diagnostic call in the part fixture. These are behavioral failures, not missing-symbol failures.
Must change
Include large in-place writes when choosing the early part.
gpu/vk/vk_memory.cpp, lines 2049-2053, skips a page before consultingearly_pageswhen itswb_pagesvalue differs fromwhole. Butnote_direct_write, lines 4136-4140, updatesearly_pagesfor every write andwb_pagesonly for writes at most 1 MiB. A current writer can therefore be skipped despite being known:{11, 7}and B ={11, 8}. Reading both pages giveswhole == 11.invalidate, lines 3264-3278, accepts part 7 and skips the whole wait, so the CPU can read B before its writer finishes. Main waits for the whole submission 11 instead.Reproduced with GCC and Clang, using the actual
note_direct_write, page tables and writer lookup cut from each revision. Both printv=11 whole=11 mirror_part=0; the parent printsearly_part=8, this headearly_part=7. This is a regression introduced by52306c0c. Include the current early entry even when the plain table is stale, or fall back to the whole submission if coverage is uncertain. Add this mixed small/large-write case and require part 8 or that fallback. The new all-large-write case attests/test_readback_early.py:131-134does not cover it.Bounds, repeated writes and smaller points
98cfaf0.Not checked
No Steam Deck, Windows execution, GPU payload correctness, screenshots, long session or current-head performance measurement here. The early-part regression is reproduced without a game; its gameplay frequency is unknown. The updated head merges cleanly with main
0caf0618. Source-check jobs remain blocked, not passed: https://git.magicbean.dev/MagicBeanDev/Longshot/actions/runs/4461.Measured with the review fixes on the Steam Deck. This was the player's build plus
52306c0c: one build, the moving Kokiri route, interleaved, stock clocks. Off ran 11.5 / 11.1 real fps, on 13.5 / 13.2 (+18%); p95 went from 119 / 128 ms to 61 / 90 ms. Each early read waited 1.1–1.4 ms, and 0 reads fell back to their whole submission, so the stricter writer check costs nothing there. A follow-up that made the capture's late pages wait for their writers' part engaged but saved nothing (12.7 / 12.8), so it isn't proposed. Run file in docs/tested/.Windows compile and game-free checks for head
995aab15:longshot_vk.dllbuilt with the pinned llvm-mingw 20261104 (RX_VK_ONLY=1 RX_RELEASE=1 RX_TARGET=windows,--jobs 6): 0 failed, linked.tests/test_readback_early.py,tests/test_readback_part.py,tests/test_split_ub_flush.py,tests/test_parallel_small_cb.pyandtests/test_late_fix_build.py: pass.No game or gameplay run on this machine, so the Deck numbers remain the performance evidence. The Windows build at least shows the new readback paths compile and link there.
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.