WIP: fix(vk): a late page the game reads is no longer overwritten with older words by its pending fix #526
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/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
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!526
Loading…
Reference in a new issue
No description provided.
Delete branch "mooki/Longshot:fix/nightly-flicker"
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?
Regression: black flashes on Link and other characters since nightly 123, caused by #248. Nightly 80 was fine. Workaround until this is in a nightly:
RX_VK_LATE_FIX=0inlongshot_env.txt.Why
Players on Windows with NVIDIA cards see short black flashes on Link and other characters, in Zora's Fountain and Hyrule Field among other places, with frame generation off too; setting
RX_VK_LATE_FIX=0stops them for every player who tried it (#366). The cause is in the late pages' fix from #248 (nightly 123): when the game reads a page whose GPU fix is still pending, the capture could end with older words than the game had written. This changes only that read.What is measured: a game-free test of the sequence fails on main and passes here (below). Not measured: the flash itself. It was not seen in a game run on Linux, and nothing was run on Windows or on a GPU that is slow enough to make late pages frequent.
What changed
late_fix_cpu_readingpu/vk/vk_memory.cpp: the replay reads a late page of a capture on the CPU (for a draw's descriptors and constants) and the page has a fix. Before, a fix recorded in an earlier part of the same submission was neither waited for nor dropped; the function waited for the write-back, copied the page again from the game's memory and left the fix to run. Now that part is waited for (it is already submitted, so the wait is for work that is on its way), so its fix has run, and the capture is read as the fix left it, with nothing written to it (what a fix of an earlier recording already did). The wait has a hang guard of 5 s, logged; a part that ran past it is not waited for again. Three cases keep the old copy, with the fix's table counts set to 0 first so the fix writes nothing over it: the current part (not submitted, so the GPU has not read its table), a part past the guard, and a part that cannot be waited for. The last is only the case ofRX_VK_PER_QUEUE_FENCES=0or a recordings' timeline that could not be created (gpu/vk/vk_replay.cpp,per_queue_fences_off,replay_start): no GPU, driver or OS decides it, so NVIDIA's driver on Windows takes the wait unless that switch is set. That fallback keeps the race of zeroing a table a running part may be reading; it is rare by construction and counted (next bullet).vk_part_waitingpu/vk/vk_replay.cpp, declared ingpu/vk/vk_internal.h: waits for one part of a submission on the recordings' timeline, the same signal the readback wait already uses (wait_recorded_part); returns 1 done, 0 ran out, -1 cannot be waited for.late pages' fix (600 recordings with one)log line gets two counts at its end: of the reads on the CPU, how many waited for an earlier part's fix and how many copied the page again instead, so a player's log shows which way it went.tests/test_late_fix_build.py: the function is cut out of the source and run beside the existing cases, with a stand-in forshaders/late_fix.comprun over the fix table. Cases: the fix of an earlier part, the current part, a part that cannot be waited for (and its second page), an earlier recording done and not done.RX_VK_LATE_FIXand ofRX_VK_SHADOW_FOLLOWS_SYNC.The chain of causes
capture_make,gpu/vk/vk_memory.cpp). Since #248 the capture copies it at once and a small compute pass (shaders/late_fix.comp), recorded in the uploads of the submission's first part, writes the words the write-backs wrote into the capture before the mirror is made from it.maybe_split,submit_recordingingpu/vk/vk_replay.cpp). The part that recorded the fix can be running, or run after what the replay thread does next.memory_read_as_submitted), which for a late page goes throughlate_fix_cpu_read. For a fix recorded in an earlier part it took the lastelsebranch: wait for the write-back, copy the page again from guest memory, leave the fix.shaders/late_fix.compreads the pool's write-back shadow) over them.The other branches of the function do not do this: a fix already run, or recorded by an earlier recording, only reads the fixed buffer; a fix not recorded yet, or recorded in the current part, is dropped before the copy.
Flags and settings
None.
RX_VK_LATE_FIX=0stays the way to get the old wait. No flag is added.Measurements
Branch state: on main
7ff5256e000671d0fccf27f77ba41275916b21df; headfea96c39c04c802882c545107202068fb7b5b32f; whole game-free suite on this head:553 passed, 2 failed, 16 skipped(main7ff5256e000671d0fccf27f77ba41275916b21df:553 passed, 2 failed, 16 skipped). The 2 failing tests fail on main the same way:tests/test_cpm_discovery.py,tests/test_walk_key.py. The commits are unchanged by rebases apart from the results table.main:
543846ebb0(2026-11-14)this PR: fix/nightly-flicker @
4610ffddaHardware: game-free test, Arch Linux 7.2.15, Core i9-13900K, g++ with
-Wall -Wextra -Werror; no GPUScene: none (a unit test of the function over a made-up page)
Method:
python -I -B tests/test_late_fix_build.py, run on the test commit alone (test only, main's source) and on this headHow to reproduce: from the branch,
git checkout a1929ba56 -- tests/test_late_fix_build.pyis the test alone on main's source (it fails);python -I -B tests/test_late_fix_build.pyon the head passes543846ebb4610ffddatests/test_late_fix_build.py, case j: a CPU read of a late page whose fix an earlier part recorded; the write-back landed and the game wrote a newer wordfailed line 868: slot[kX] == kA && slot[kY] == kCpu(the page was copied again, then the fix ran over it)What the players' logs show, as evidence that the path is hit (not a measurement of this change):
late pages' fix (600 recordings with one)lines of the flickering run read 674 captures with late pages, 659 fixed, 82.8 pages a capture, 235 read on the CPU first, and 764 captures, 759 fixed, 113.8 pages, 587 read on the CPU first. Its run withRX_VK_LATE_FIX=0does not flicker.RX_VK_LATE_FIX=0stops the flicker, on, off, on.RX_VK_LATE_FIX=0; three players who set the three switches of #324 back still saw the flicker.RX_VK_SHADOW_FOLLOWS_SYNC=0and the late fix on. That fits this cause: with it on, the pages a mirror sync took are "known" to the write-back shadow and are not filled again (shadow_sync_copy,shadow_synced_known), so the fix reads shadow words older than guest memory; with it off those pages are unknown at the next write-back and are filled again from guest memory on the GPU, so the shadow follows the newer words and the old fix's overwrite wrote nearly current ones. Not proven in a game.Arc laptop: frame rate and dark frames, main against this pull request (Windows)
Baseline: main @
543846ebb, this pull request's base when it was measured. Measured at4610ffddabefore the carry onto the current main; the carry replays the same two commits unchanged, and main's changes since (the replay's reserved core on Linux, the per-CPU game build) do not touch the late pages' fix, so the numbers were not taken again.main:
543846ebb0(2026-11-14)this PR: fix/nightly-flicker @
4610ffddaHardware: Intel Arc Graphics, integrated (Core Ultra 9 185H laptop, shared memory), driver 32.0.101.9175; 64 GB; Windows 11 Pro 10.0.26300
Scene: Kokiri Forest (child), the same autosave resumed on the forest floor, a 50 s walk of fixed stick inputs between the houses, camera centering off; frame generation off; 1280x720 window; Windows builds of both commits (same compiler and settings), shader caches warmed and restored to the same saved state before each measured run
Method: the two builds alternated, main / this PR / main / this PR. Picture-free runs: no pictures during the walk; fps from the game's own
vi: ... frames presented (... fps over the last 5.0 s)lines inside the walk (ten per run). Dense-picture runs: a picture of every frame the game's control bridge could take (about half of the presented frames, 306 to 365 per run); a program flags frames where part of the picture is much darker than in both neighbours, and every flagged frame was looked at.How to reproduce: Kokiri Forest, an autosave on the forest floor, the same 50 s walk on both builds, alternated; the late-page counters are in the game's log (
a capture taken (ms): ... GPU wait for late pages (N of 600 takes)), the fix's own summary line (late pages' fix (600 recordings with one)) did not appear in any run.543846ebb4610ffddaRX_VK_LATE_FIX=0as control: 13.35)RX_VK_LATE_FIX=0: 0 of 347)late pages' fix (600 recordings with one)lines, and the new counts "waited for an earlier part's fix" / "copied the page again instead"How to read the frame-rate rows: four picture-free runs, two per build, is a small sample. The two runs of one build differ by up to 1.1 fps; this PR's medians are 0.6 fps higher on average, inside that spread, so the claim is "not slower", not "faster". The walks end in different places (real-time physics) and first-time shader translations differ (0 to 35 per walk), so differences of about 1 fps are not meaningful. The late-page path is hardly used in these walks (0.3% of captures wait for a late page, about 12 to 14% in the menus before the walk), so these rows show no regression and the frame-rate comparison, not the fault or its fix. The flash was not seen on this GPU, with or without the fix.
Testing
Tested: the new cases both ways (table above) on Linux; the test commit alone fails; the rest of
tests/test_late_fix_build.pyis unchanged and passes. Game runs on Linux (RTX 4090, NVIDIA 615.78.08, windowless) of the nightly 140 build only, for the late-page counts; the build of this change was made but not run in the game, since this card shows too few late pages for a run to say anything. No flash was seen and none was expected: the card finishes a write-back before the next capture is taken, so late pages are rare in play (a walk through the field, the market and Kokiri Forest with area loads gave no late-pages summary line at all, which needs 600 recordings with a fix) and come in bursts at loads (618 captures with late pages, 371 read on the CPU first, at 5120x2880). Whole game-free suite on this head:554 passed, 1 failed, 16 skipped(Linux,--jobs 6 --strict, 153 s). The one failure,tests/test_walk_key.py(a link error,undefined reference to hos_oot_autoload_input), fails the same way on main @543846ebb; this change does not touch it, and #497 fixes it.Built for Windows and Linux at the previous head, not run (the pushed head
fea96c39cis built again before the build box is ticked): At4610ffddad50771e1b26b63bfe9e06837765c32f, the Windows x86-64 Vulkan renderer DLL and all 50 selected host translation units were cross-built on Linux with llvm-mingw 20261104 (Clang 23.1.5), using the project's production flags and pinned dependencies (renderer: 35 newly compiled / 170 reused; host: 50 newly compiled / 0 reused; lifted C: 72 newly compiled / 8 reused; all Windows units: 317 newly compiled / 332 reused from matching object-cache entries), the renderer DLL linked and the full game executable linked; nothing was run on Windows or under Wine, and this is neither the project's hosted builder run nor a Windows UTF-8 run-time path test.That previous head
4610ffddad50771e1b26b63bfe9e06837765c32falso compiled and linked its full native Linux executable and renderer with Clang 23.1.3 through the documented resume path (322 newly compiled units, 169 matching Linux-cache units, source stampd01a77d5), using the verified target-neutral C from lift5c175a4727d82f85e425e85400dca17fea0750fbrather than carrying the patch onto another commit; neither Linux binary was run.Not tested: a game run on Windows or with NVIDIA's Windows driver; any card slow enough for late pages to be frequent in play; frame generation; a before and after picture of a flash (none was captured here); whether the part wait costs frame time where many late pages are read in later parts (the old way waited for the write-back instead; no frame times were taken); the fallback paths (a part that cannot be waited for or past the guard) in a real game, and the race that fallback keeps.
Needs other testers:
RX_VK_LATE_FIXline: watch Link and other characters for brief black flashes, expecting none (test request T-LUBF).Screenshots
Nothing on screen: the flash was not captured here.
Risk and rollback
The wait for an earlier part can stall the replay thread for as long as that part runs on the GPU (about 120 draws) the first time a later part reads one of its late pages; the old way waited for the write-back instead. A wait past 5 s (a hang guard that is logged) falls back to the old copy and that part is not waited for again.
RX_VK_LATE_FIX=0turns the whole late pages' fix off; reverting the commit is enough.Reviewer notes
late_fix_cpu_read(about 2384 on main) and the new case j intests/test_late_fix_build.py.RX_VK_PER_QUEUE_FENCES=0.Issues
Fixes #366
Checklist
For everyone
type(scope): description).main.python -I -B tests/run_all.pyrun and passing (any failure that also fails onmainis named above).543846ebbagainst this PR @4610ffdda, two alternated runs each, medians above; it shows no regression and the frame rate, not the flash, which this hardware does not produce.)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.New on this forge; not opened on GitHub.
Arc laptop check of this pull request: no regression seen
This is a check for regressions and frame rate on a slow integrated GPU. It does not show the fault or the fix: late pages turned out to be rare in these walks (see below), so I never saw the black flash with or without the change.
Hardware: Intel Arc Graphics (the integrated GPU of a Core Ultra 9 185H laptop, shared memory), driver 32.0.101.9175, Windows 11 Pro 10.0.26300, 64 GB RAM.
Built: this pull request's head
4610ffddad, and main543846ebb0(the pull request's base; the pull request is main plus its two commits). Both are Windows builds made from those exact commits with the same compiler and settings.What I did, all in Kokiri Forest, frame generation off, window output 1280x720, no co-op, no audio, camera centering off:
RX_VK_LATE_FIX=0as a control. A small program compared each picture with the ones before and after it and flagged any frame where part of the picture was much darker than in both neighbours (the kind of dip a black Link would make). I looked at every flagged frame in a strip of before, flagged and after: 83 single frames and 26 pairs of frames.Late pages in these walks:
late pages' fix (600 recordings with one)line, so fewer than 600 recordings with a fix happen in a whole run of 2 to 5 minutes. For the same reason the two new counts this pull request adds to that line never appeared.GPU wait for late pages (N of 600 takes)line is the other sign of them: in the walks 51 of 16,200 captures waited on main (0.3%), 43 of 16,800 on this pull request (0.3%), 11 of 4,200 withRX_VK_LATE_FIX=0(0.3%). In the title screen, menus and loading before each walk it is about 12 to 14% (medians of 74 to 87 per 600 captures) on all three.Results (D1 to D5 dense-picture runs, T1 to T4 picture-free runs, each one 50 second walk):
RX_VK_LATE_FIX=0Not tested: NVIDIA cards or any Windows run on NVIDIA; Zora's Fountain; Hyrule Field with pictures (only the late-page survey on main); frame generation (off in every run); runs longer than a few minutes; the flash itself, which I did not see on this GPU in any run, with or without
RX_VK_LATE_FIX=0.4610ffddadfea96c39c0Carried onto main
7ff5256e0(main gained the replay's reserved-core fix and the per-CPU game build since); new headfea96c39c. The two commits are unchanged. The whole suite on this exact head: 553 passed, 2 failed, 16 skipped. The build box is unticked until this pushed head has been built for Windows and Linux; it is ticked again then.View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.