fix(vk): HUD labels no longer turn to noise when a render target over their memory is evicted #323
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 project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!323
Loading…
Reference in a new issue
No description provided.
Delete branch "Amateurwizrd/Longshot:fix/evict-writeback-newer-drawing"
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 a card short of video memory, the HUD's small labels (the d-pad icon, the item count, the item's key prompt) turn into noise after an area loads, and stay that way until the pause menu is opened and closed. It showed in every run at a 6 GB allowance in Hyrule Field.
What changed
texture_frame(gpu/vk/vk_texture.cpp) no longer writes a texture back into the game's memory when another image was drawn into its storage after its own last drawing. It asksdrawn_others, which the drawn write-back (drawn_writeback_finish) already uses for the same question.evict_reason::write_back_stale(gpu/vk/evict_reason.h) holds the comparison. A drawing in the same frame keeps the write-back.tests/test_evict_reason.cppchecks the rule with the frames from the run below;tests/test_evict_reason.pychecks that the eviction pass still calls it.docs/tested-configurations.md.What went wrong, from a traced run of this scene (
RX_VK_WATCH_TEX, the game folder's own renderer):0x1001e88fc00(8,640 KB). The game stops using it.0x1001f039400, 46x46, 140x140 and five more) inside that same memory, finalizes them, and keeps sampling them. Their texels exist only in the Vulkan images.texture_touchuploads them from the game's memory, which never held what the GPU drew.target_overlap.hdoes not call the old target drawn over, because each label lies wholly inside it and covers less than an eighth of it. That rule decides when a target may go early and is left alone here.Flags and settings
None.
RX_VK_EVICT_NO_FLUSH=1(existing, skips every write-back) hid the bug and is unchanged.Measurements
main:
514591a3(2026-11-13)this PR: fix/evict-writeback-newer-drawing @
c8e54dc2Hardware: NVIDIA GeForce RTX 5070 Ti 16 GB, driver 621.01, AMD Ryzen 7 7800X3D, 32 GB, Windows 11 Pro 26200
Scene: Hyrule Field (child) by debug teleport from a copied save, standing; Output resolution 2560x1440 in a 1707x960 window, Render resolution 75%, the game's own upscaler, no frame generation, no frame cap, sound off;
RX_VK_TEST_ALLOWED_MB=6144Method: 3 rounds, the two builds alternating in each round. One game folder (
longshot v3.0.0-rc.3-27-gf516654c) with onlylongshot_vk.dllswapped; both renderers built alone withRX_VK_ONLY=1and the same toolchain (v3.0.0-rc.3-25-g514591a3andv3.0.0-rc.3-26-gc8e54dc2). Each run: teleport, 25 s standing, a screenshot, 5 s in the pause menu, a screenshot. The label score is the mean difference (0 to 255) of three HUD crops from a run without the allowance. Frame rate is the median of the log'sN presents, X fpslines from present 5040 on.How to reproduce: start with
RX_VK_TEST_ALLOWED_MB=6144and Output resolution 2560x1440, load a save, teleport to Hyrule Field (Debug tab) and look at the d-pad icon and the item count once the area has loaded.longshot_log.txthasupload over a drawn imagelines for 110x110, 46x46 and 140x140 textures when it happens.514591a3c8e54dc2upload over a drawn imagelines for the labels, a runFrame rate by round, main / this PR: 105.5 / 104.0, 95.1 / 98.8, 100.7 / 99.3. The rounds differ by more than the builds do: no frame rate change is claimed. The write-backs skipped were of render targets of 1920x1080, 2560x1440 and 960x540, each unused for 600 frames or more. One more run of this PR without the allowance: labels right (1.9 at most), no write-back and none skipped, 112.1 fps.
main @
514591a3, round 1:this PR @
c8e54dc2, round 1:Testing
Tested:
python -I -B tests/run_all.pyon Windows 11 with llvm-mingw: 494 passed, 6 failed, 33 skipped. All six fail the same way onmain514591a3(test_nup_paths,test_check_third_party,test_menu_i18n,test_portable_builder_startup,test_portable_builder_home,test_release). The new checks intests/test_evict_reason.pyfail onmainand pass here. In the game on the PC above: the 7 runs under Measurements, no device lost; one of this PR's pictures looked at whole, nothing missing. Before the fix was written, the same scene with the folder's own renderer (f516654c:mainand the default Output resolution change) gave the labels as noise in 20 of 21 runs with the allowance and right in 2 of 2 withRX_VK_EVICT_NO_FLUSH=1.Not tested: A card with 6 or 8 GB of its own (the allowance was set on a 16 GB card). Areas other than Hyrule Field. Sessions longer than two minutes. AMD and Intel GPUs. Linux. Whether a texture whose write-back is skipped is ever made again and sampled without being drawn first (issue 386's case): the ones skipped here were render targets unused for 600 frames or more.
Needs other testers:
Screenshots
In the first comment: the d-pad icon and the item slot, enlarged, standing in Hyrule Field. Left: main @
514591a3. Right: this PR @c8e54dc2.Risk and rollback
A texture whose write-back is skipped goes with no copy of its texels. If the game makes one again over that storage and samples it before drawing it, it starts empty, as every such texture did before the write-back existed. That takes another image drawn into its storage after it, which is also when its old texels are no longer what that memory holds. The log counts these (
GPU-drawn with no copy kept: N live). Reverting the commit is enough.Reviewer notes
texture_frameafter the selection loop are the change.drawn_otherswalks the texture list once, and only in a pass that has a write-back to do.texture_flush_to_guest) writes every drawn texture over a range in no set order and has the same weakness. It did no harm in these runs and is left alone.Issues
None found (the forge searched for "garbled", "rainbow" and "HUD", 2026-11-13).
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-configurations.mdfor each measured run.mainand passing here.tests/that reads changed source updated in this PR, not weakened.The d-pad icon and the item slot, enlarged 3x, standing in Hyrule Field with
RX_VK_TEST_ALLOWED_MB=6144. Left: main @514591a3. Right: this PR @c8e54dc2. Same game folder and save, round 1 of the runs under Measurements.Test request T-U85X is open for a 6 or 8 GB card; the description names it and the Testing box is ticked. The request can be claimed once a maintainer adds the testing wanted label.
proci referenced this pull request2026-11-14 00:01:43 +00:00
8296d55fc45640a67194Rebased onto main
9ef17269; the history stays linear and both commits are still authored by Amateurwizrd. New head:5640a671.What conflicted: only
docs/tested-configurations.md, in the docs commit. Main had added three rows (the low-VRAM mirror budget runs) where this PR adds its two rows for the eviction write-back runs. I kept main's three rows and put this PR's two after them, unchanged.gpu/vk/evict_reason.h,gpu/vk/vk_texture.cppandtests/test_evict_reason.{cpp,py}rebased cleanly, and main hasn't changed them since the old base514591a3. The PR's diff against the new main has the same lines as its diff against the old base, and nothing of main's is reverted. Main has since moved toed088cb6, which only touchesgpu/vk/vk_replay.cppandtests/test_inline_write_order.py, so the PR doesn't conflict with it either.I didn't run any tests or builds on this PC. Forgejo still shows the PR as not mergeable, but only because it's WIP.
Code review of
5640a671, the head after today's rebase onto9ef17269(read only, nothing built or run here). The rebase changed onlydocs/tested-configurations.md: the code is identical toc8e54dc2(checked withjj interdiff), and this PR's two rows sit below main's three new rows, which looks right.The fix looks right.
drawn_othersalready answers this question for the drawn write-back.!released) textures, so skipping the stale ones touches only render targets that something else has since drawn into.GPU-drawn with no copy kept), so the trade is visible in logs.>against>=question, and a narrower follow-up.Compared with 325: same symptom, different root cause. They complement each other and don't conflict.
RX_VK_PARALLEL=4.texture_frame's eviction here,texture_writtenthere). Onlydocs/tested-configurations.mdconflicts. Recommendation: take both. Merge order doesn't matter; whichever goes second rebases its rows.v3.0.0: yes, worth taking. It's a visible HUD bug on 6 and 8 GB cards, which is a large share of players, from a 15-line change that's measured, A/B'd and easy to revert. What blocks it:
5640a671(run 2885) passed the title, actionlint and third-party checks, but skipped both Python source-check parts and the SPIR-V fixtures. So no Linux run of the checks, the newtest_evict_reasoncases included, exists yet. They need to run once it's ready.testing wanted. For the release that label is the quickest unblock.c8e54dc2on main514591a3. The code is unchanged since, so the numbers hold. A line in the description saying so keepspr statusfrom asking.@ -4680,0 +4686,4 @@for (const Evict& e : evict)if (e.flush) ts.push_back(e.tex);if (!ts.empty()) {const std::vector<uint64_t> other = drawn_others(ts);A follow-up idea, not for this PR. Skipping drops the whole target's texels, 8.6 MB here, to protect a few labels covering under an eighth of it.
Flushalready takes a[lo, hi)range, so the write-back could instead skip only the ranges of images drawn there since, and write back the rest. That keeps issue 386's protection for the remainder. The skipped targets in your runs were unused for 600+ frames, so the whole-skip is fine for now.texture_flush_to_guesthas the same weakness, as your notes say.@ -4680,0 +4689,4 @@const std::vector<uint64_t> other = drawn_others(ts);size_t i = 0;for (Evict& e : evict)if (e.flush) e.flush = !evict_reason::write_back_stale(e.tex->last_written, other[i++]);Question:
drawn_writeback_finishdrops its copy whenother >= last_written(same frame counts as stale), whilewrite_back_stalekeeps the write-back on a same-frame drawing (>). The comment in evict_reason.h gives the reason (which came last isn't known). Both write-backs put an image's texels into memory another image may own, though, so it would be good to have one rule, or a sentence on why eviction differs. As it stands, a label drawn in the same frame as the target's last draw still gets noise. That seems unlikely for a target unused for 600 frames, so this isn't blocking.WIP: fix(vk): HUD labels no longer turn to noise when a render target over their memory is evictedto fix(vk): HUD labels no longer turn to noise when a render target over their memory is evictedAmateurwizrd referenced this pull request2026-11-14 01:19:29 +00:00