fix(vk): HUD labels no longer turn to noise when a render target over their memory is evicted #323

Merged
HumanBean merged 2 commits from Amateurwizrd/Longshot:fix/evict-writeback-newer-drawing into main 2026-11-14 01:04:37 +00:00
Contributor

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

  • vk: the eviction pass in 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 asks drawn_others, which the drawn write-back (drawn_writeback_finish) already uses for the same question.
  • rule: evict_reason::write_back_stale (gpu/vk/evict_reason.h) holds the comparison. A drawing in the same frame keeps the write-back.
  • test: tests/test_evict_reason.cpp checks the rule with the frames from the run below; tests/test_evict_reason.py checks that the eviction pass still calls it.
  • docs: two rows in docs/tested-configurations.md.

What went wrong, from a traced run of this scene (RX_VK_WATCH_TEX, the game folder's own renderer):

  1. Frame 3804: a 1920x1080 target is made at 0x1001e88fc00 (8,640 KB). The game stops using it.
  2. Frame 3987: the game draws its HUD labels (110x110 at 0x1001f039400, 46x46, 140x140 and five more) inside that same memory, finalizes them, and keeps sampling them. Their texels exist only in the Vulkan images.
  3. Frame 4410: over the budget, the old target is evicted, and written back into its memory first (the write-back for issue 386). The write covers all 8,640 KB and marks those pages written.
  4. Same frame: the finalized labels now count as written by the CPU since their finalize, so texture_touch uploads them from the game's memory, which never held what the GPU drew.

target_overlap.h does 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 @ c8e54dc2
Hardware: 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=6144
Method: 3 rounds, the two builds alternating in each round. One game folder (longshot v3.0.0-rc.3-27-gf516654c) with only longshot_vk.dll swapped; both renderers built alone with RX_VK_ONLY=1 and the same toolchain (v3.0.0-rc.3-25-g514591a3 and v3.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's N presents, X fps lines from present 5040 on.
How to reproduce: start with RX_VK_TEST_ALLOWED_MB=6144 and 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.txt has upload over a drawn image lines for 110x110, 46x46 and 140x140 textures when it happens.

main @ 514591a3 this PR @ c8e54dc2
Runs with the labels as noise 3 of 3 0 of 3
Label score: d-pad / count / key (median of 3) 73.7 / 72.6 / 79.6 2.3 / 0.0 / 0.7
upload over a drawn image lines for the labels, a run 8, 8, 8 0, 0, 0
Write-backs skipped, a run 0, 0, 0 3, 3, 2
Frame rate standing in the field (median of 3) 100.7 fps 99.3 fps

Frame 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:

vk: 1 textures retired (1473 left, 2207 MB): budget 1, drawn over 0, dead targets 0
vk: upload over a drawn image: #1470 0x1001f049800 140x140x1 x1 NVN 0x1 at present 5009 (texture frame 5010), finalized, 48 KB dirty in 1 ranges
vk: upload over a drawn image: #1483 0x1001f039400 110x110x1 x1 NVN 0x1 at present 5009 (texture frame 5010), finalized, 16 KB dirty in 1 ranges
vk: upload over a drawn image: #1480 0x1001f03d600 46x46x1 x1 NVN 0x25 at present 5009 (texture frame 5010), finalized, 12 KB dirty in 1 ranges

this PR @ c8e54dc2, round 1:

vk: texture #1843 0x1001e88fc00 1920x1080x1 x1 NVN 0x3f the GPU drew goes with no copy of its texels (live, unused for 607 frames, over the budget): made again over this storage, it starts empty
vk: 1 textures retired (1418 left, 2087 MB): budget 1, drawn over 0, dead targets 0; GPU-drawn with no copy kept: 1 live, 0 finalized, 7 MB

Testing

Tested: python -I -B tests/run_all.py on Windows 11 with llvm-mingw: 494 passed, 6 failed, 33 skipped. All six fail the same way on main 514591a3 (test_nup_paths, test_check_third_party, test_menu_i18n, test_portable_builder_startup, test_portable_builder_home, test_release). The new checks in tests/test_evict_reason.py fail on main and 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: main and the default Output resolution change) gave the labels as noise in 20 of 21 runs with the allowance and right in 2 of 2 with RX_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:

  • A 6 or 8 GB card: play through several area changes, expecting the d-pad icon, item count and key prompts to stay right, and no ground or terrain texture coming back empty (T-U85X)

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

  • The 15 lines in texture_frame after the selection loop are the change. drawn_others walks the texture list once, and only in a pass that has a write-back to do.
  • The write-back for remapped virtual pages (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.
  • Hookshot's 40955ed3 fixes another cause of the same log line for the same labels (a texture with no page map). The labels here have a page map, so that change would not cover this. Hookshot's eviction pass has the same line as ours.

Issues

None found (the forge searched for "garbled", "rainbow" and "HUD", 2026-11-13).

Checklist

For everyone

  • One topic.
  • The title is a Conventional Commit (type(scope): description).
  • Rebased on current main.
  • python -I -B tests/run_all.py run and passing (any failure that also fails on main is named above).
  • Tested in the game, or what wasn't is named under Testing.
  • Testing filled in: what was tested, what wasn't, and what needs other testers (with test requests).
  • Measured: main and this PR at named commits, on the same PC, scene and settings, at least 2 interleaved runs each, the median reported.
  • No game files, dumps, build output or generated files.
  • third_party/ untouched (a new folder with a PROVENANCE.md instead).
  • The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled.

If applicable

  • AI assistance recorded with Assisted-by: / Co-Authored-By: trailers.
  • Screenshots for a change that shows on screen.
  • A row in docs/tested-configurations.md for each measured run.
  • A game-free test added for the fix, failing on main and passing here.
  • A check under tests/ that reads changed source updated in this PR, not weakened.
  • Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths. (Built on Windows only. The change is standard C++ with no platform call and no path.)
  • Docs updated.
## 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 - vk: the eviction pass in `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 asks `drawn_others`, which the drawn write-back (`drawn_writeback_finish`) already uses for the same question. - rule: `evict_reason::write_back_stale` (`gpu/vk/evict_reason.h`) holds the comparison. A drawing in the same frame keeps the write-back. - test: `tests/test_evict_reason.cpp` checks the rule with the frames from the run below; `tests/test_evict_reason.py` checks that the eviction pass still calls it. - docs: two rows in `docs/tested-configurations.md`. What went wrong, from a traced run of this scene (`RX_VK_WATCH_TEX`, the game folder's own renderer): 1. Frame 3804: a 1920x1080 target is made at `0x1001e88fc00` (8,640 KB). The game stops using it. 2. Frame 3987: the game draws its HUD labels (110x110 at `0x1001f039400`, 46x46, 140x140 and five more) inside that same memory, finalizes them, and keeps sampling them. Their texels exist only in the Vulkan images. 3. Frame 4410: over the budget, the old target is evicted, and written back into its memory first (the write-back for issue 386). The write covers all 8,640 KB and marks those pages written. 4. Same frame: the finalized labels now count as written by the CPU since their finalize, so `texture_touch` uploads them from the game's memory, which never held what the GPU drew. `target_overlap.h` does 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 @ c8e54dc2 Hardware: 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=6144` Method: 3 rounds, the two builds alternating in each round. One game folder (`longshot v3.0.0-rc.3-27-gf516654c`) with only `longshot_vk.dll` swapped; both renderers built alone with `RX_VK_ONLY=1` and the same toolchain (`v3.0.0-rc.3-25-g514591a3` and `v3.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's `N presents, X fps` lines from present 5040 on. How to reproduce: start with `RX_VK_TEST_ALLOWED_MB=6144` and 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.txt` has `upload over a drawn image` lines for 110x110, 46x46 and 140x140 textures when it happens. | | main @ 514591a3 | this PR @ c8e54dc2 | |---|---|---| | Runs with the labels as noise | 3 of 3 | 0 of 3 | | Label score: d-pad / count / key (median of 3) | 73.7 / 72.6 / 79.6 | 2.3 / 0.0 / 0.7 | | `upload over a drawn image` lines for the labels, a run | 8, 8, 8 | 0, 0, 0 | | Write-backs skipped, a run | 0, 0, 0 | 3, 3, 2 | | Frame rate standing in the field (median of 3) | 100.7 fps | 99.3 fps | Frame 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: ``` vk: 1 textures retired (1473 left, 2207 MB): budget 1, drawn over 0, dead targets 0 vk: upload over a drawn image: #1470 0x1001f049800 140x140x1 x1 NVN 0x1 at present 5009 (texture frame 5010), finalized, 48 KB dirty in 1 ranges vk: upload over a drawn image: #1483 0x1001f039400 110x110x1 x1 NVN 0x1 at present 5009 (texture frame 5010), finalized, 16 KB dirty in 1 ranges vk: upload over a drawn image: #1480 0x1001f03d600 46x46x1 x1 NVN 0x25 at present 5009 (texture frame 5010), finalized, 12 KB dirty in 1 ranges ``` this PR @ c8e54dc2, round 1: ``` vk: texture #1843 0x1001e88fc00 1920x1080x1 x1 NVN 0x3f the GPU drew goes with no copy of its texels (live, unused for 607 frames, over the budget): made again over this storage, it starts empty vk: 1 textures retired (1418 left, 2087 MB): budget 1, drawn over 0, dead targets 0; GPU-drawn with no copy kept: 1 live, 0 finalized, 7 MB ``` ## Testing **Tested:** `python -I -B tests/run_all.py` on Windows 11 with llvm-mingw: 494 passed, 6 failed, 33 skipped. All six fail the same way on `main` 514591a3 (`test_nup_paths`, `test_check_third_party`, `test_menu_i18n`, `test_portable_builder_startup`, `test_portable_builder_home`, `test_release`). The new checks in `tests/test_evict_reason.py` fail on `main` and 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: `main` and the default Output resolution change) gave the labels as noise in 20 of 21 runs with the allowance and right in 2 of 2 with `RX_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:** - A 6 or 8 GB card: play through several area changes, expecting the d-pad icon, item count and key prompts to stay right, and no ground or terrain texture coming back empty (T-U85X) ## 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 - The 15 lines in `texture_frame` after the selection loop are the change. `drawn_others` walks the texture list once, and only in a pass that has a write-back to do. - The write-back for remapped virtual pages (`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. - Hookshot's [40955ed3](https://git.notrexed.dev/Hookshot/Hookshot/commit/40955ed3ad5cba480961f0b92ccab69f52407843) fixes another cause of the same log line for the same labels (a texture with no page map). The labels here have a page map, so that change would not cover this. Hookshot's eviction pass has the same line as ours. ## Issues None found (the forge searched for "garbled", "rainbow" and "HUD", 2026-11-13). ## Checklist ### For everyone - [x] One topic. - [x] The title is a Conventional Commit (`type(scope): description`). - [x] Rebased on current `main`. - [x] `python -I -B tests/run_all.py` run and passing (any failure that also fails on `main` is named above). - [x] Tested in the game, or what wasn't is named under Testing. - [x] Testing filled in: what was tested, what wasn't, and what needs other testers (with test requests). - [x] Measured: main and this PR at named commits, on the same PC, scene and settings, at least 2 interleaved runs each, the median reported. - [x] No game files, dumps, build output or generated files. - [x] `third_party/` untouched (a new folder with a `PROVENANCE.md` instead). - [x] The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled. ### If applicable - [x] AI assistance recorded with `Assisted-by:` / `Co-Authored-By:` trailers. - [x] Screenshots for a change that shows on screen. - [x] A row in `docs/tested-configurations.md` for each measured run. - [x] A game-free test added for the fix, failing on `main` and passing here. - [x] A check under `tests/` that reads changed source updated in this PR, not weakened. - [ ] Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths. (Built on Windows only. The change is standard C++ with no platform call and no path.) - [x] Docs updated.
Over the texture budget, a render target the GPU drew is written back into the game's memory before it is evicted
(issue 386). The write covers all of its storage and marks those pages written. OoT draws its HUD's labels (the d-pad
icon, the item count, the key prompt) inside the storage of a 1920x1080 target it has stopped using. When that target
was evicted about 600 frames later, the finalized labels counted as written by the CPU and were uploaded from memory
that never held their texels: they showed as noise until the pause menu redrew them. Seen at a 6,144 MB VRAM
allowance in Hyrule Field; RX_VK_EVICT_NO_FLUSH=1 hid it.

The eviction now skips the write-back of a texture when another image was drawn into its storage after its own last
drawing (evict_reason::write_back_stale, with drawn_others, which the drawn write-back already uses). Such a texture
goes with no copy of its texels, as the log's "no copy kept" count says. A drawing in the same frame keeps the
write-back.

Assisted-by: Claude Fable 5.2
Co-Authored-By: Claude Fable 5.2 <[email protected]>
docs: tested-configurations rows for the eviction write-back runs
Some checks failed
PR description / Description follows the template (pull_request_target) Successful in 3s
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been cancelled
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been cancelled
CI / Workflow lint (actionlint) (pull_request) Has been cancelled
Third-party parts / As their notes say (pull_request) Has been cancelled
Third-party parts / The same as upstream (pull_request) Has been cancelled
8296d55fc4
Assisted-by: Claude Fable 5.2
Co-Authored-By: Claude Fable 5.2 <[email protected]>
Author
Contributor

main @ 514591a3 (left), this PR @ c8e54dc2 (right)

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.

![main @ 514591a3 (left), this PR @ c8e54dc2 (right)](https://git.magicbean.dev/attachments/abf851ad-7426-409e-94df-7357faaff357) 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.
Author
Contributor

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.

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.
HumanBean added this to the v3.0.0 milestone 2026-11-14 00:57:40 +00:00
HumanBean force-pushed fix/evict-writeback-newer-drawing from 8296d55fc4
Some checks failed
PR description / Description follows the template (pull_request_target) Successful in 3s
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been cancelled
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been cancelled
CI / Workflow lint (actionlint) (pull_request) Has been cancelled
Third-party parts / As their notes say (pull_request) Has been cancelled
Third-party parts / The same as upstream (pull_request) Has been cancelled
to 5640a67194
Some checks failed
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 4s
CI / Workflow lint (actionlint) (pull_request) Successful in 7s
Third-party parts / As their notes say (pull_request) Successful in 8s
PR description / Description follows the template (pull_request_target) Failing after 3s
Website / Website on Cloudflare (pull_request_target) Has been skipped
2026-11-14 00:59:39 +00:00
Compare
Owner

Rebased 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.cpp and tests/test_evict_reason.{cpp,py} rebased cleanly, and main hasn't changed them since the old base 514591a3. 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 to ed088cb6, which only touches gpu/vk/vk_replay.cpp and tests/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.

Rebased 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.cpp` and `tests/test_evict_reason.{cpp,py}` rebased cleanly, and main hasn't changed them since the old base `514591a3`. 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 to `ed088cb6`, which only touches `gpu/vk/vk_replay.cpp` and `tests/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.
HumanBean left a comment

Code review of 5640a671, the head after today's rebase onto 9ef17269 (read only, nothing built or run here). The rebase changed only docs/tested-configurations.md: the code is identical to c8e54dc2 (checked with jj interdiff), and this PR's two rows sit below main's three new rows, which looks right.

The fix looks right.

  • drawn_others already answers this question for the drawn write-back.
  • The eviction write-back only ever runs for live (!released) textures, so skipping the stale ones touches only render targets that something else has since drawn into.
  • The skipped ones are counted in the retired line (GPU-drawn with no copy kept), so the trade is visible in logs.
  • No bug found. Two points inline: a > against >= question, and a narrower follow-up.

Compared with 325: same symptom, different root cause. They complement each other and don't conflict.

  • Here: the labels have a page map. Evicting an old 1920x1080 target written back over the same storage marks those pages CPU-written, so the labels are uploaded from guest memory. Main's log shows 16 KB of a 48 KB label.
  • 325: a virtual-pool label the GPU drew first has no page map, so its first check after the finalize takes all of its storage. Main's log shows the whole 48 KB, with RX_VK_PARALLEL=4.
  • Neither fixes the other's case: 325's map still takes the pages this write-back marks, and this PR doesn't help a texture with no map. 325's own notes say the same.
  • The code doesn't overlap (texture_frame's eviction here, texture_written there). Only docs/tested-configurations.md conflicts. 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:

  1. It's WIP, and the Linux box is unticked. CI on 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 new test_evict_reason cases included, exists yet. They need to run once it's ready.
  2. T-U85X (a real 6 or 8 GB card) is open with no result, and no tester comment is on the PR. It can only be claimed once a maintainer adds testing wanted. For the release that label is the quickest unblock.
  3. Measurements name c8e54dc2 on main 514591a3. The code is unchanged since, so the numbers hold. A line in the description saying so keeps pr status from asking.
Code review of 5640a671, the head after today's rebase onto 9ef17269 (read only, nothing built or run here). The rebase changed only `docs/tested-configurations.md`: the code is identical to c8e54dc2 (checked with `jj interdiff`), and this PR's two rows sit below main's three new rows, which looks right. **The fix looks right.** - `drawn_others` already answers this question for the drawn write-back. - The eviction write-back only ever runs for live (`!released`) textures, so skipping the stale ones touches only render targets that something else has since drawn into. - The skipped ones are counted in the retired line (`GPU-drawn with no copy kept`), so the trade is visible in logs. - No bug found. Two points inline: a `>` against `>=` question, and a narrower follow-up. **Compared with [325](https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/325): same symptom, different root cause. They complement each other and don't conflict.** - Here: the labels **have** a page map. Evicting an old 1920x1080 target written back over the same storage marks those pages CPU-written, so the labels are uploaded from guest memory. Main's log shows 16 KB of a 48 KB label. - [325](https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/325): a virtual-pool label the GPU drew first has **no** page map, so its first check after the finalize takes all of its storage. Main's log shows the whole 48 KB, with `RX_VK_PARALLEL=4`. - Neither fixes the other's case: 325's map still takes the pages this write-back marks, and this PR doesn't help a texture with no map. 325's own notes say the same. - The code doesn't overlap (`texture_frame`'s eviction here, `texture_written` there). Only `docs/tested-configurations.md` conflicts. **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: 1. It's WIP, and the Linux box is unticked. CI on 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 new `test_evict_reason` cases included, exists yet. They need to run once it's ready. 2. T-U85X (a real 6 or 8 GB card) is open with no result, and no tester comment is on the PR. It can only be claimed once a maintainer adds `testing wanted`. For the release that label is the quickest unblock. 3. Measurements name c8e54dc2 on main 514591a3. The code is unchanged since, so the numbers hold. A line in the description saying so keeps `pr status` from 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);
Owner

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. Flush already 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_guest has the same weakness, as your notes say.

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. `Flush` already 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_guest` has 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++]);
Owner

Question: drawn_writeback_finish drops its copy when other >= last_written (same frame counts as stale), while write_back_stale keeps 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.

Question: `drawn_writeback_finish` drops its copy when `other >= last_written` (same frame counts as stale), while `write_back_stale` keeps 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.
HumanBean changed title from WIP: fix(vk): HUD labels no longer turn to noise when a render target over their memory is evicted to fix(vk): HUD labels no longer turn to noise when a render target over their memory is evicted 2026-11-14 01:04:33 +00:00
HumanBean deleted branch fix/evict-writeback-newer-drawing 2026-11-14 01:04:37 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
MagicBeanDev/Longshot!323
No description provided.