fix(vk): HUD labels the GPU drew no longer go blank after a zone change #325

Merged
HumanBean merged 1 commit from proci/Longshot:fix/virtual-pool-drawn-page-map into main 2026-11-14 01:28:02 +00:00
Contributor

Why

After a zone change, the HUD's button labels (R1, R2) and the D-pad cross can go missing, until the game draws them again. On Longshot main it showed in both of my sessions with RX_VK_PARALLEL=4, walking from Hyrule Field into Castle Town. With the serial replay the same path uploads full-size render and depth targets from guest memory over what the GPU drew.

This is the change merged in Hookshot as NotRexed/Hookshot#3004 (2026-11-13), ported to Longshot and measured again here. None of the restore PRs carries it.

What changed

  • vk: texture_written (gpu/vk/vk_texture.cpp) notes a texture's page map when the texture is in a virtual pool (GPU address at or above 1 TB) and the GPU writes it before anything uploads it. It notes the map the first time a writer that gives a reason writes the texture, with the same call texture_touch's first-upload seed makes (texture_dirty(*tex, 0, ...)), and before it marks the texture uploaded.
    • The bug. Without the map, the texture's first check after the game finalizes it (texture_touch, then texture_dirty) takes all of its storage for remapped. The whole image is then uploaded from guest memory, which in a virtual pool is the live memory (guest_bytes) and never held what the GPU drew.
    • With the map, that check takes only the pages remapped and what the CPU wrote since the finalize, as it already does below 1 TB.
    • Which writers. In practice the writers that give a reason are a pass's colour and depth targets and the dry pass's marks of a draw's targets. A copy (resolve()) and a storage image (texture_view_of) sample their texture first, which notes the map there.
    • Writers left out. Those that give no reason ("?": the upscaler's stand-ins, which run without the ParLock a change to the map needs, and clear_tex) do what they did on main.
  • vk: a texture like that, sampled again after a finalize, stays the GPU's (gpu_written, released) and is not taken as uploaded.
    • So eviction may keep it as a small finalized drawn texture (keep_finalized).
    • texture_flush_to_guest keeps writing it back before its virtual pages are remapped.
    • A finalized virtual depth target that is sampled again keeps the GPU's depth.
  • tests: tests/test_drawn_page_map.py (new, game-free) cuts texture_written, texture_released, texture_dirty and texture_touch's checks and first-upload seed out of vk_texture.cpp as they are. It builds them over a fake GPU mapping and checks them with the change on and with it off. It also lists the nine reasons texture_written is given, each with its writer's file and lock (NOTED_BY), and fails when a reason is added or moves.
  • docs: RX_VK_NO_DRAWN_PAGE_MAP=1 in docs/development.md; a row in docs/worklist.md (Open, 0.9); four rows in docs/tested-configurations.md.

Cost:

  • When. One page walk per texture object at its first reasoned write: guest_size / 64 KB mapping lookups (1 for a 48 KB label, 240 for a 15 MB depth target). In a parallel replay it runs mostly in the serial dry pass.
  • What is new. main made the same walk at the object's first check after a finalize, so it is new only for objects never sampled after one.
  • What it saves. It removes the whole-image uploads in the table below: 15 MB each for the full-size targets.

Not fixed: the map is noted once per texture object. Suppose a texture's own pages are remapped and the GPU draws it again before any check. The check after its next finalize still takes the remap, and the image is uploaded whole over the newer drawing, as on main.

Flags and settings

Flag What it does Default now Recommendation Helps on Doesn't help on Untested on
RX_VK_NO_DRAWN_PAGE_MAP=1 Turns the change off, as before: a virtual-pool texture the GPU writes first has no page map, and its first check after a finalize uploads it whole unset (the change on) On by default: the old path replaces what the GPU drew with guest memory, which never held it Textures in a virtual pool the GPU draws before any upload (OoT's HUD labels with RX_VK_PARALLEL=4; full-size targets with the serial replay) Textures below 1 TB; the 640x360 NVN 0x14 image below AMD, Intel, Linux, Steam Deck

Read once with env_on. The flag starts with none of translator_stamp's prefixes, so setting it keeps the program cache. It is in docs/development.md's switch table, next to RX_VK_NO_DRAWN_WRITEBACK=1. The README lists the switches players use, so it is not there.

Measurements

main: e51e28e9 (2026-11-13)
this PR: fix/virtual-pool-drawn-page-map @ 4830ea7d (this change on e51e28e9), rebased since onto main 514591a3 as 627455b0
Hardware: RTX 5080 16 GB, driver 610.88; Ryzen 9 7900X3D (SMT on), 64 GB; Windows 11 Pro 26200, fullscreen
Scene: Hyrule Field next to Castle Town by day, walking into Castle Town and its market and back out. 2560x1440, DLAA, frame generation off, frame rate cap 90, Reflex off, VRAM budget 11 GB; RX_VK_PARALLEL=4, HOS_PIN=*=FFF; DualSense.
Method: four sessions interleaved (main, this PR, main, this PR) of 2-4 minutes each, the HUD watched at the town gate.

  • The build. The game folder was built once from e51e28e9 (longshot e51e28e9 (source 8dd02aab)). Each side's longshot_vk.dll was built alone from its commit (RX_VK_ONLY, release, llvm-mingw). The main one is the same file as the full build's.
  • What is counted. longshot_log.txt's vk: upload over a drawn image: lines in the bug's form: finalized, KB dirty, without ", a GPU buffer wrote it".
  • The test. The game-free test, against main's vk_texture.cpp and against this change's.

How to reproduce: RX_VK_PARALLEL=4, a save in Hyrule Field near Castle Town, then walk into Castle Town. On main the R1 and R2 labels and the D-pad cross are gone by the gate, and the log has the lines below.

main @ e51e28e9 (run 1 / run 3) this PR @ 4830ea7d (run 2 / run 4)
The HUD at the Castle Town gate (seen in the game) no R1 or R2 label, no D-pad cross / the same drawn / drawn
The six label and cross textures (140x140, 137x136, 109x113, 76x76, 73x72, 45x49) uploaded whole over the GPU's drawing 6 / 6 (all at one present) 0 / 0
Other HUD textures in the virtual pool, uploaded so 4 / 6 0 / 0
Render targets uploaded so (2560x1440 NVN 0x34 and 0xa, 1280x720 0xa, 640x360 0xa) 4 / 9 0 / 0
The 640x360 NVN 0x14 image at 0x10001fe0800 (another path, see Reviewer notes) 6 / 2 11 / 12
Validation, device-lost or VK_ERROR lines 0 / 0 0 / 0
tests/test_drawn_page_map.py fails: the whole storage is taken passes

main @ e51e28e9, run 1, at the gate:

[hos host] vk: upload over a drawn image: #1535 0x1001f00d800 140x140x1 x1 NVN 0x1 at present 7002 (texture frame 7003), finalized, 48 KB dirty in 1 ranges
[hos host] vk: upload over a drawn image: #1533 0x1001f019a00 76x76x1 x1 NVN 0x25 at present 7002 (texture frame 7003), finalized, 40 KB dirty in 1 ranges
[hos host] vk: upload over a drawn image: #1543 0x1001effd400 109x113x1 x1 NVN 0x1 at present 7002 (texture frame 7003), finalized, 16 KB dirty in 1 ranges
[hos host] vk: upload over a drawn image: #1541 0x1001f001600 45x49x1 x1 NVN 0x25 at present 7002 (texture frame 7003), finalized, 12 KB dirty in 1 ranges
[hos host] vk: upload over a drawn image: #1550 0x1001efde000 137x136x1 x1 NVN 0x1 at present 7002 (texture frame 7003), finalized, 48 KB dirty in 1 ranges
[hos host] vk: upload over a drawn image: #1548 0x1001efea200 73x72x1 x1 NVN 0x25 at present 7002 (texture frame 7003), finalized, 40 KB dirty in 1 ranges

and full-size targets in the same session:

[hos Presentation Thread] vk: upload over a drawn image: #246 0x10001e00400 2560x1440x1 x1 NVN 0x34 at present 3116 (texture frame 3117), finalized, 15360 KB dirty in 1 ranges
[hos Presentation Thread] vk: upload over a drawn image: #11251 0x10003e00600 2560x1440x1 x1 NVN 0x34 at present 7999 (texture frame 8000), finalized, 15360 KB dirty in 1 ranges
[hos Presentation Thread] vk: upload over a drawn image: #11252 0x10004d00800 2560x1440x1 x1 NVN 0xa at present 8001 (texture frame 8002), finalized, 15360 KB dirty in 1 ranges

this PR @ 4830ea7d, runs 2 and 4: no such line for any HUD texture or render target. The only ones left are the 0x14 image's, on both sides:

[hos Presentation Thread] vk: upload over a drawn image: #702 0x10001fe0800 640x360x1 x1 NVN 0x14 at present 3000 (texture frame 3001), finalized, 842 KB dirty in 1 ranges

The test, with main's vk_texture.cpp and with this change's:

$ python -I -B tests/test_drawn_page_map.py --cxx clang++ --source <main 514591a3's gpu/vk/vk_texture.cpp>
PASS: the 9 reasons texture_written is given are the ones NOTED_BY lists, each by one writer in its file; 5 writers give none
FAIL: a texture the GPU drew in a virtual pool, finalized, nothing written since: not uploaded again; taken for written: {0, 0xc000}
FAIL: its page map noted at the GPU's first write
FAIL: other pages remapped, its own as they were: not uploaded again; taken for written: {0, 0x8000}
FAIL: written by the CPU after the finalize: that page; taken for written: {0, 0x4000}
FAIL: its second 64 KB page remapped: that page; taken for written: {0, 0x20000}
FAIL: RX_VK_CPU_OVER_GPU=1, the CPU's write over a texture the GPU owns: that page; taken for written: {0, 0x8000}
FAIL: drawn and finalized: not uploaded again; taken for written: {0, 0x10000}
FAIL: written with no reason, then drawn by a pass, finalized: not uploaded again; taken for written: {0, 0x10000}
FAIL: written with no reason, then drawn by a pass: its page map noted at the pass's write
FAIL: the code cut out of vk_texture-main-514591a3.cpp

$ python -I -B tests/test_drawn_page_map.py --cxx clang++
PASS: the 9 reasons texture_written is given are the ones NOTED_BY lists, each by one writer in its file; 5 writers give none
PASS: a texture the GPU drew in a virtual pool has its page map noted at that write: after a finalize, only what was remapped or written since is taken for written
PASS: RX_VK_NO_DRAWN_PAGE_MAP=1: no page map noted at the GPU's write, all of a finalized texture taken at its first check, as before

After the rebase. The runs are on e51e28e9; the branch is now rebased onto 514591a3 with no conflicts. The numbers still hold:

  • None of main's 27 commits since then touches gpu/vk/vk_texture.cpp.
  • In gpu/vk/vk_draw.cpp none changes a texture_written call, a ParLock or dry_mark_written, so the test's NOTED_BY lock notes still hold.
  • The test and run_all pass on the rebased branch (Testing).

Testing

Tested:

  • Game-free: tests/test_drawn_page_map.py, with the change on and with RX_VK_NO_DRAWN_PAGE_MAP=1.
    • Cases with the change on:
      • a texture drawn in a virtual pool and finalized, with nothing written since, is not uploaded again. The same holds after other pages were remapped, when it is drawn and finalized twice, and when a writer that gives no reason wrote it first;
      • a CPU write after the finalize, a page remapped since, and a CPU write over a texture the GPU still owns (RX_VK_CPU_OVER_GPU=1): only those pages are taken;
      • a writer that gives no reason, a texture below 1 TB, one already noted, and one of no size: no page walk.
    • With the flag set: all of the storage is taken at the first check, as before.
    • On main: with main's vk_texture.cpp the test fails with the whole storage taken. With this change it passes (llvm-mingw clang 23.1.5, Windows 11).
  • run_all: python -I -B tests/run_all.py --jobs 8 on Windows 11 (llvm-mingw clang 23.1.5).
    • On this branch at 627455b0: 497 passed, 4 failed, 33 skipped.
    • On main 514591a3: 496 passed, the same 4 failed (test_check_third_party, test_finish_output, test_menu_i18n, test_release), 33 skipped.
    • The checks that read vk_texture.cpp. 31 checks under tests/ and nx2/ read it. On the branch, 30 pass unchanged, and nx2/test_bc_storage_views.py is skipped as on main (it needs a Windows Vulkan device). None needed changing.
  • Renderer: longshot_vk.dll built with this change on e51e28e9 (RX_VK_ONLY, release, llvm-mingw): 0 failed, no warnings.
  • In the game: the four sessions in Measurements. I played them and closed each from the pause menu.
  • On Hookshot: a blind A/B there (four sessions, serial and RX_VK_PARALLEL=4) showed the same: labels broken before and not after, and the full-size target uploads gone (NotRexed/Hookshot#3004).

Not tested:

  • Other hardware: AMD and Intel GPUs, Linux and the Steam Deck. The change is in the vendor-independent texture cache, but it ran on one NVIDIA card only.
  • The serial replay on Longshot. On Hookshot it showed the full-size target uploads, not the labels.
  • GCC. The test's fixture keeps a guard for GCC 13's -Wdangling-reference.
  • A Linux build. I built on Windows only (llvm-mingw). The change is five lines of portable C++ in gpu/vk/vk_texture.cpp, plus comments, with no OS call and no path.
  • The frame-time cost at one fixed spot. The cost is one page walk per texture object; on Hookshot the session medians were 70-76 fps either way.

Needs other testers:

  • AMD GPU, Windows: with RX_VK_PARALLEL=4, walk from Hyrule Field into Castle Town. Expect the HUD's labels and D-pad cross drawn, and no upload over a drawn image ... finalized line for a HUD texture (request to open).
  • Linux or Steam Deck: build the branch, then the same walk, expecting the same (request to open).

Screenshots

The Castle Town gate, the top of the screen (the counter at the top right is Longshot's FPS counter):

main @ e51e28e9 (run 1): no R1 or R2 label beside the item, no D-pad cross beside the ocarina.

main @ e51e28e9: no R1 or R2 label, no D-pad cross

this PR @ 4830ea7d (run 2): R1, R2 and the D-pad cross drawn.

this PR @ 4830ea7d: R1, R2 and the D-pad cross drawn

The two small grey squares left of R1 show on both sides: they are not part of this bug.

Risk and rollback

  • More write-backs. A finalized drawn virtual-pool texture now stays the GPU's. So texture_flush_to_guest also writes it back into its memory before its pages are remapped. In a virtual pool that write-back goes over what the CPU wrote there since the GPU's write.
  • More kept textures. Eviction may keep more of these textures (keep_finalized).
  • Turning it off. RX_VK_NO_DRAWN_PAGE_MAP=1 turns the change off, or the commit can be reverted alone.

Reviewer notes

  • Where to look. The change is the note_map lines in texture_written (gpu/vk/vk_texture.cpp), then the test's NOTED_BY table.
  • The design rule. The bug breaks rule 2 of docs/design.md, section 4: "Nothing the replay reads comes from live guest memory". The whole-image upload read a virtual pool's live memory (guest_bytes) over what the GPU drew. The change keeps that read for the pages the CPU really wrote or remapped.
  • Overlaps. Open PRs that also change vk_texture.cpp merge with this one with no conflict in the code: #211, #223, #252, #282, #286, #287, #288, #323 and #33.
    • #282. It works on the other form of the same log line, upload over a drawn image ... a GPU buffer wrote it: it uploads only the part a GPU buffer's write covered. This change works on the form without that clause, where no GPU buffer wrote the texture and the whole image was taken for remapped. Each leaves the other's case as it was.
    • #323. Another way the same labels go wrong, on a card short of VRAM: an old render target over the labels' memory is evicted and written back, which marks their pages written since their finalize, so they are uploaded from guest memory as noise. Those pages count as CPU-written here too, so this change leaves that case to #323, which stops the stale write-back. This change covers the labels that had no page map at all.
    • #211. It caps what eviction keeps for drawn textures the game samples again (keep_finalized's cap shared with its new rule). This change can add to the finalized drawn textures keep_finalized keeps (Risk and rollback).
    • The docs rows. Only docs/tested-configurations.md conflicts, with any PR that adds rows at the top (#223, #237, #303, #323 and #326, the mirror-sync PR, among them).
  • The 640x360 NVN 0x14 image at 0x10001fe0800 is uploaded again on both sides. Its storage is shared with three render targets.
    • The same object is uploaded again a few times.
    • Once the texture budget retires textures (vk: N textures retired ...: budget N), the image is also made again about every 6 s, and each new object's first upload is whole.
    • The two PR sessions ran longer into the budget, hence more uploads.
    • Hookshot's blind logs show the same without the change: 20 and 7 objects in its two "before" sessions.
    • This change does not touch that path; it is left for a separate change.
  • The 260x260 NVN 0xd textures uploaded in the PR runs (1 and 2) are below 1 TB, outside this change's condition. Hookshot's blind runs have them on both sides.

Issues

No Longshot issue found for blank HUD labels (searched 2026-11-13). See NotRexed/Hookshot#2508.

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.
  • Flags noted, and the switch reference updated (docs/development.md, the README).
  • 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.
  • Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths. Built on Windows only so far; the Linux build is in Needs other testers.
  • Docs updated.

Written with Claude Code (Claude Opus 5.5). I played the in-game A/B. The commit carries a Co-Authored-By trailer.

🤖 Generated with Claude Code

## Why After a zone change, the HUD's button labels (R1, R2) and the D-pad cross can go missing, until the game draws them again. On Longshot `main` it showed in both of my sessions with `RX_VK_PARALLEL=4`, walking from Hyrule Field into Castle Town. With the serial replay the same path uploads full-size render and depth targets from guest memory over what the GPU drew. This is the change merged in Hookshot as [NotRexed/Hookshot#3004](https://github.com/NotRexed/Hookshot/pull/3004) (2026-11-13), ported to Longshot and measured again here. None of the restore PRs carries it. ## What changed - vk: `texture_written` (gpu/vk/vk_texture.cpp) notes a texture's page map when the texture is in a virtual pool (GPU address at or above 1 TB) and the GPU writes it before anything uploads it. It notes the map the first time a writer that gives a reason writes the texture, with the same call `texture_touch`'s first-upload seed makes (`texture_dirty(*tex, 0, ...)`), and before it marks the texture uploaded. - **The bug.** Without the map, the texture's first check after the game finalizes it (`texture_touch`, then `texture_dirty`) takes all of its storage for remapped. The whole image is then uploaded from guest memory, which in a virtual pool is the live memory (`guest_bytes`) and never held what the GPU drew. - **With the map,** that check takes only the pages remapped and what the CPU wrote since the finalize, as it already does below 1 TB. - **Which writers.** In practice the writers that give a reason are a pass's colour and depth targets and the dry pass's marks of a draw's targets. A copy (`resolve()`) and a storage image (`texture_view_of`) sample their texture first, which notes the map there. - **Writers left out.** Those that give no reason (`"?"`: the upscaler's stand-ins, which run without the `ParLock` a change to the map needs, and `clear_tex`) do what they did on `main`. - vk: a texture like that, sampled again after a finalize, stays the GPU's (`gpu_written`, `released`) and is not taken as uploaded. - So eviction may keep it as a small finalized drawn texture (`keep_finalized`). - `texture_flush_to_guest` keeps writing it back before its virtual pages are remapped. - A finalized virtual depth target that is sampled again keeps the GPU's depth. - tests: `tests/test_drawn_page_map.py` (new, game-free) cuts `texture_written`, `texture_released`, `texture_dirty` and `texture_touch`'s checks and first-upload seed out of vk_texture.cpp as they are. It builds them over a fake GPU mapping and checks them with the change on and with it off. It also lists the nine reasons `texture_written` is given, each with its writer's file and lock (`NOTED_BY`), and fails when a reason is added or moves. - docs: `RX_VK_NO_DRAWN_PAGE_MAP=1` in docs/development.md; a row in docs/worklist.md (Open, 0.9); four rows in docs/tested-configurations.md. Cost: - **When.** One page walk per texture object at its first reasoned write: `guest_size / 64 KB` mapping lookups (1 for a 48 KB label, 240 for a 15 MB depth target). In a parallel replay it runs mostly in the serial dry pass. - **What is new.** `main` made the same walk at the object's first check after a finalize, so it is new only for objects never sampled after one. - **What it saves.** It removes the whole-image uploads in the table below: 15 MB each for the full-size targets. Not fixed: the map is noted once per texture object. Suppose a texture's own pages are remapped and the GPU draws it again before any check. The check after its next finalize still takes the remap, and the image is uploaded whole over the newer drawing, as on `main`. ## Flags and settings | Flag | What it does | Default now | Recommendation | Helps on | Doesn't help on | Untested on | |---|---|---|---|---|---|---| | `RX_VK_NO_DRAWN_PAGE_MAP=1` | Turns the change off, as before: a virtual-pool texture the GPU writes first has no page map, and its first check after a finalize uploads it whole | unset (the change on) | On by default: the old path replaces what the GPU drew with guest memory, which never held it | Textures in a virtual pool the GPU draws before any upload (OoT's HUD labels with `RX_VK_PARALLEL=4`; full-size targets with the serial replay) | Textures below 1 TB; the 640x360 NVN 0x14 image below | AMD, Intel, Linux, Steam Deck | Read once with `env_on`. The flag starts with none of `translator_stamp`'s prefixes, so setting it keeps the program cache. It is in docs/development.md's switch table, next to `RX_VK_NO_DRAWN_WRITEBACK=1`. The README lists the switches players use, so it is not there. ## Measurements main: e51e28e9 (2026-11-13) this PR: fix/virtual-pool-drawn-page-map @ 4830ea7d (this change on e51e28e9), rebased since onto main 514591a3 as 627455b0 Hardware: RTX 5080 16 GB, driver 610.88; Ryzen 9 7900X3D (SMT on), 64 GB; Windows 11 Pro 26200, fullscreen Scene: Hyrule Field next to Castle Town by day, walking into Castle Town and its market and back out. 2560x1440, DLAA, frame generation off, frame rate cap 90, Reflex off, VRAM budget 11 GB; `RX_VK_PARALLEL=4`, `HOS_PIN=*=FFF`; DualSense. Method: four sessions interleaved (main, this PR, main, this PR) of 2-4 minutes each, the HUD watched at the town gate. - **The build.** The game folder was built once from e51e28e9 (`longshot e51e28e9 (source 8dd02aab)`). Each side's `longshot_vk.dll` was built alone from its commit (`RX_VK_ONLY`, release, llvm-mingw). The main one is the same file as the full build's. - **What is counted.** `longshot_log.txt`'s `vk: upload over a drawn image:` lines in the bug's form: finalized, KB dirty, without ", a GPU buffer wrote it". - **The test.** The game-free test, against main's vk_texture.cpp and against this change's. How to reproduce: `RX_VK_PARALLEL=4`, a save in Hyrule Field near Castle Town, then walk into Castle Town. On `main` the R1 and R2 labels and the D-pad cross are gone by the gate, and the log has the lines below. | | main @ e51e28e9 (run 1 / run 3) | this PR @ 4830ea7d (run 2 / run 4) | |---|---|---| | The HUD at the Castle Town gate (seen in the game) | no R1 or R2 label, no D-pad cross / the same | drawn / drawn | | The six label and cross textures (140x140, 137x136, 109x113, 76x76, 73x72, 45x49) uploaded whole over the GPU's drawing | 6 / 6 (all at one present) | 0 / 0 | | Other HUD textures in the virtual pool, uploaded so | 4 / 6 | 0 / 0 | | Render targets uploaded so (2560x1440 NVN 0x34 and 0xa, 1280x720 0xa, 640x360 0xa) | 4 / 9 | 0 / 0 | | The 640x360 NVN 0x14 image at 0x10001fe0800 (another path, see Reviewer notes) | 6 / 2 | 11 / 12 | | Validation, device-lost or `VK_ERROR` lines | 0 / 0 | 0 / 0 | | `tests/test_drawn_page_map.py` | fails: the whole storage is taken | passes | main @ e51e28e9, run 1, at the gate: ``` [hos host] vk: upload over a drawn image: #1535 0x1001f00d800 140x140x1 x1 NVN 0x1 at present 7002 (texture frame 7003), finalized, 48 KB dirty in 1 ranges [hos host] vk: upload over a drawn image: #1533 0x1001f019a00 76x76x1 x1 NVN 0x25 at present 7002 (texture frame 7003), finalized, 40 KB dirty in 1 ranges [hos host] vk: upload over a drawn image: #1543 0x1001effd400 109x113x1 x1 NVN 0x1 at present 7002 (texture frame 7003), finalized, 16 KB dirty in 1 ranges [hos host] vk: upload over a drawn image: #1541 0x1001f001600 45x49x1 x1 NVN 0x25 at present 7002 (texture frame 7003), finalized, 12 KB dirty in 1 ranges [hos host] vk: upload over a drawn image: #1550 0x1001efde000 137x136x1 x1 NVN 0x1 at present 7002 (texture frame 7003), finalized, 48 KB dirty in 1 ranges [hos host] vk: upload over a drawn image: #1548 0x1001efea200 73x72x1 x1 NVN 0x25 at present 7002 (texture frame 7003), finalized, 40 KB dirty in 1 ranges ``` and full-size targets in the same session: ``` [hos Presentation Thread] vk: upload over a drawn image: #246 0x10001e00400 2560x1440x1 x1 NVN 0x34 at present 3116 (texture frame 3117), finalized, 15360 KB dirty in 1 ranges [hos Presentation Thread] vk: upload over a drawn image: #11251 0x10003e00600 2560x1440x1 x1 NVN 0x34 at present 7999 (texture frame 8000), finalized, 15360 KB dirty in 1 ranges [hos Presentation Thread] vk: upload over a drawn image: #11252 0x10004d00800 2560x1440x1 x1 NVN 0xa at present 8001 (texture frame 8002), finalized, 15360 KB dirty in 1 ranges ``` this PR @ 4830ea7d, runs 2 and 4: no such line for any HUD texture or render target. The only ones left are the 0x14 image's, on both sides: ``` [hos Presentation Thread] vk: upload over a drawn image: #702 0x10001fe0800 640x360x1 x1 NVN 0x14 at present 3000 (texture frame 3001), finalized, 842 KB dirty in 1 ranges ``` The test, with main's vk_texture.cpp and with this change's: ``` $ python -I -B tests/test_drawn_page_map.py --cxx clang++ --source <main 514591a3's gpu/vk/vk_texture.cpp> PASS: the 9 reasons texture_written is given are the ones NOTED_BY lists, each by one writer in its file; 5 writers give none FAIL: a texture the GPU drew in a virtual pool, finalized, nothing written since: not uploaded again; taken for written: {0, 0xc000} FAIL: its page map noted at the GPU's first write FAIL: other pages remapped, its own as they were: not uploaded again; taken for written: {0, 0x8000} FAIL: written by the CPU after the finalize: that page; taken for written: {0, 0x4000} FAIL: its second 64 KB page remapped: that page; taken for written: {0, 0x20000} FAIL: RX_VK_CPU_OVER_GPU=1, the CPU's write over a texture the GPU owns: that page; taken for written: {0, 0x8000} FAIL: drawn and finalized: not uploaded again; taken for written: {0, 0x10000} FAIL: written with no reason, then drawn by a pass, finalized: not uploaded again; taken for written: {0, 0x10000} FAIL: written with no reason, then drawn by a pass: its page map noted at the pass's write FAIL: the code cut out of vk_texture-main-514591a3.cpp $ python -I -B tests/test_drawn_page_map.py --cxx clang++ PASS: the 9 reasons texture_written is given are the ones NOTED_BY lists, each by one writer in its file; 5 writers give none PASS: a texture the GPU drew in a virtual pool has its page map noted at that write: after a finalize, only what was remapped or written since is taken for written PASS: RX_VK_NO_DRAWN_PAGE_MAP=1: no page map noted at the GPU's write, all of a finalized texture taken at its first check, as before ``` **After the rebase.** The runs are on e51e28e9; the branch is now rebased onto 514591a3 with no conflicts. The numbers still hold: - None of main's 27 commits since then touches gpu/vk/vk_texture.cpp. - In gpu/vk/vk_draw.cpp none changes a `texture_written` call, a `ParLock` or `dry_mark_written`, so the test's `NOTED_BY` lock notes still hold. - The test and `run_all` pass on the rebased branch (Testing). ## Testing **Tested:** - **Game-free:** `tests/test_drawn_page_map.py`, with the change on and with `RX_VK_NO_DRAWN_PAGE_MAP=1`. - **Cases with the change on:** - a texture drawn in a virtual pool and finalized, with nothing written since, is not uploaded again. The same holds after other pages were remapped, when it is drawn and finalized twice, and when a writer that gives no reason wrote it first; - a CPU write after the finalize, a page remapped since, and a CPU write over a texture the GPU still owns (`RX_VK_CPU_OVER_GPU=1`): only those pages are taken; - a writer that gives no reason, a texture below 1 TB, one already noted, and one of no size: no page walk. - **With the flag set:** all of the storage is taken at the first check, as before. - **On `main`:** with `main`'s vk_texture.cpp the test fails with the whole storage taken. With this change it passes (llvm-mingw clang 23.1.5, Windows 11). - **run_all:** `python -I -B tests/run_all.py --jobs 8` on Windows 11 (llvm-mingw clang 23.1.5). - On this branch at 627455b0: 497 passed, 4 failed, 33 skipped. - On `main` 514591a3: 496 passed, the same 4 failed (`test_check_third_party`, `test_finish_output`, `test_menu_i18n`, `test_release`), 33 skipped. - **The checks that read vk_texture.cpp.** 31 checks under `tests/` and `nx2/` read it. On the branch, 30 pass unchanged, and `nx2/test_bc_storage_views.py` is skipped as on `main` (it needs a Windows Vulkan device). None needed changing. - **Renderer:** `longshot_vk.dll` built with this change on e51e28e9 (`RX_VK_ONLY`, release, llvm-mingw): 0 failed, no warnings. - **In the game:** the four sessions in Measurements. I played them and closed each from the pause menu. - **On Hookshot:** a blind A/B there (four sessions, serial and `RX_VK_PARALLEL=4`) showed the same: labels broken before and not after, and the full-size target uploads gone ([NotRexed/Hookshot#3004](https://github.com/NotRexed/Hookshot/pull/3004)). **Not tested:** - **Other hardware:** AMD and Intel GPUs, Linux and the Steam Deck. The change is in the vendor-independent texture cache, but it ran on one NVIDIA card only. - **The serial replay on Longshot.** On Hookshot it showed the full-size target uploads, not the labels. - **GCC.** The test's fixture keeps a guard for GCC 13's `-Wdangling-reference`. - **A Linux build.** I built on Windows only (llvm-mingw). The change is five lines of portable C++ in gpu/vk/vk_texture.cpp, plus comments, with no OS call and no path. - **The frame-time cost at one fixed spot.** The cost is one page walk per texture object; on Hookshot the session medians were 70-76 fps either way. **Needs other testers:** - AMD GPU, Windows: with `RX_VK_PARALLEL=4`, walk from Hyrule Field into Castle Town. Expect the HUD's labels and D-pad cross drawn, and no `upload over a drawn image ... finalized` line for a HUD texture (request to open). - Linux or Steam Deck: build the branch, then the same walk, expecting the same (request to open). ## Screenshots The Castle Town gate, the top of the screen (the counter at the top right is Longshot's FPS counter): **main @ e51e28e9** (run 1): no R1 or R2 label beside the item, no D-pad cross beside the ocarina. ![main @ e51e28e9: no R1 or R2 label, no D-pad cross](/attachments/6eb27eb9-8206-4256-a82c-d60e6515c4c0) **this PR @ 4830ea7d** (run 2): R1, R2 and the D-pad cross drawn. ![this PR @ 4830ea7d: R1, R2 and the D-pad cross drawn](/attachments/0cc4b292-d30d-43e9-95d1-52d7cdb3a64d) The two small grey squares left of R1 show on both sides: they are not part of this bug. ## Risk and rollback - **More write-backs.** A finalized drawn virtual-pool texture now stays the GPU's. So `texture_flush_to_guest` also writes it back into its memory before its pages are remapped. In a virtual pool that write-back goes over what the CPU wrote there since the GPU's write. - **More kept textures.** Eviction may keep more of these textures (`keep_finalized`). - **Turning it off.** `RX_VK_NO_DRAWN_PAGE_MAP=1` turns the change off, or the commit can be reverted alone. ## Reviewer notes - **Where to look.** The change is the `note_map` lines in `texture_written` (gpu/vk/vk_texture.cpp), then the test's `NOTED_BY` table. - **The design rule.** The bug breaks rule 2 of [docs/design.md](https://git.magicbean.dev/MagicBeanDev/Longshot/src/branch/main/docs/design.md#4-the-lessons-from-mkw-as-rules-here), section 4: "Nothing the replay reads comes from live guest memory". The whole-image upload read a virtual pool's live memory (`guest_bytes`) over what the GPU drew. The change keeps that read for the pages the CPU really wrote or remapped. - **Overlaps.** Open PRs that also change vk_texture.cpp merge with this one with no conflict in the code: #211, #223, #252, #282, #286, #287, #288, #323 and #33. - **#282.** It works on the other form of the same log line, `upload over a drawn image ... a GPU buffer wrote it`: it uploads only the part a GPU buffer's write covered. This change works on the form without that clause, where no GPU buffer wrote the texture and the whole image was taken for remapped. Each leaves the other's case as it was. - **#323.** Another way the same labels go wrong, on a card short of VRAM: an old render target over the labels' memory is evicted and written back, which marks their pages written since their finalize, so they are uploaded from guest memory as noise. Those pages count as CPU-written here too, so this change leaves that case to #323, which stops the stale write-back. This change covers the labels that had no page map at all. - **#211.** It caps what eviction keeps for drawn textures the game samples again (`keep_finalized`'s cap shared with its new rule). This change can add to the finalized drawn textures `keep_finalized` keeps (Risk and rollback). - **The docs rows.** Only docs/tested-configurations.md conflicts, with any PR that adds rows at the top (#223, #237, #303, #323 and #326, the mirror-sync PR, among them). - **The 640x360 NVN 0x14 image** at 0x10001fe0800 is uploaded again on both sides. Its storage is shared with three render targets. - The same object is uploaded again a few times. - Once the texture budget retires textures (`vk: N textures retired ...: budget N`), the image is also made again about every 6 s, and each new object's first upload is whole. - The two PR sessions ran longer into the budget, hence more uploads. - Hookshot's blind logs show the same without the change: 20 and 7 objects in its two "before" sessions. - This change does not touch that path; it is left for a separate change. - **The 260x260 NVN 0xd textures** uploaded in the PR runs (1 and 2) are below 1 TB, outside this change's condition. Hookshot's blind runs have them on both sides. ## Issues No Longshot issue found for blank HUD labels (searched 2026-11-13). See [NotRexed/Hookshot#2508](https://github.com/NotRexed/Hookshot/issues/2508). ## 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] Flags noted, and the switch reference updated (docs/development.md, the README). - [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. - [ ] Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths. Built on Windows only so far; the Linux build is in Needs other testers. - [x] Docs updated. Written with Claude Code (Claude Opus 5.5). I played the in-game A/B. The commit carries a `Co-Authored-By` trailer. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(vk): HUD labels the GPU drew no longer go blank after a zone change
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
627455b086
Ported from Hookshot, where it was merged on 2026-11-13
(https://github.com/NotRexed/Hookshot/pull/3004), onto Longshot main: Longshot's names (longshot_env.h, the
(longshot) comment tags). docs/tested-configurations.md has the four runs of the in-game A/B on Longshot (main,
this change, main, this change).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
HumanBean added this to the v3.0.0 milestone 2026-11-14 01:00:09 +00:00
HumanBean left a comment

Code review of 627455b0 (read only, nothing built or run here).

The fix looks right. Noting the page map at the GPU's first write in a virtual pool does the same texture_dirty(*tex, 0, …) seed texture_touch does before a first upload. After a finalize, the check then takes only what was remapped or written since release_gen, the same as below 1 TB. The "?" writers are left out because they don't hold the ParLock (checked in begin_pass: the walk is skipped there without the lock only when the dry pass already noted the map). No bug found.

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

  • Here: a virtual-pool label the GPU drew first has no page map. Its first check after the finalize takes all of its storage (main's log: 48 KB dirty for a 140x140 label, the whole image). Seen with RX_VK_PARALLEL=4, and as 15 MB target re-uploads.
  • 323: the label has a map, but evicting an old 1920x1080 target written back over the same storage marks those pages CPU-written (main's log there: 16 KB of a 48 KB label). This PR's map takes those pages too, so it doesn't fix 323's case, and 323 doesn't fix this one.
  • The code doesn't overlap (texture_written here, texture_frame's eviction 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 small, it's behind a switch, it's the fix already merged in Hookshot, and it removes whole-image uploads over drawn targets on the default (serial) path. What blocks it:

  1. It's WIP, and the two "request to open" testers in Testing aren't opened. test_requests.py shows no request for this PR.
  2. The Linux box is unticked. CI's runs for 627455b0 are still pending (run 2786). The Python checks on Linux include the new test's compile, so they settle part of it once they run.
  3. The worklist row (inline) is in Open. Once this merges it belongs in Fixed, waiting for a release (docs/worklist.md, "Move").
  4. Not measured on Longshot's default serial replay. That's where most players are, and the description cites Hookshot for it. One serial A/B session pair would cover it, ideally on the rebased head.
Code review of 627455b0 (read only, nothing built or run here). **The fix looks right.** Noting the page map at the GPU's first write in a virtual pool does the same `texture_dirty(*tex, 0, …)` seed `texture_touch` does before a first upload. After a finalize, the check then takes only what was remapped or written since `release_gen`, the same as below 1 TB. The `"?"` writers are left out because they don't hold the `ParLock` (checked in `begin_pass`: the walk is skipped there without the lock only when the dry pass already noted the map). No bug found. **Compared with [323](https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/323): same symptom, different root cause. They complement each other and don't conflict.** - Here: a virtual-pool label the GPU drew first has **no page map**. Its first check after the finalize takes all of its storage (main's log: 48 KB dirty for a 140x140 label, the whole image). Seen with `RX_VK_PARALLEL=4`, and as 15 MB target re-uploads. - [323](https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/323): the label **has** a map, but evicting an old 1920x1080 target written back over the same storage marks those pages CPU-written (main's log there: 16 KB of a 48 KB label). This PR's map takes those pages too, so it doesn't fix 323's case, and 323 doesn't fix this one. - The code doesn't overlap (`texture_written` here, `texture_frame`'s eviction 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 small, it's behind a switch, it's the fix already merged in Hookshot, and it removes whole-image uploads over drawn targets on the default (serial) path. What blocks it: 1. It's WIP, and the two "request to open" testers in Testing aren't opened. `test_requests.py` shows no request for this PR. 2. The Linux box is unticked. CI's runs for 627455b0 are still **pending** (run 2786). The Python checks on Linux include the new test's compile, so they settle part of it once they run. 3. The worklist row (inline) is in **Open**. Once this merges it belongs in *Fixed, waiting for a release* (docs/worklist.md, "Move"). 4. Not measured on Longshot's default serial replay. That's where most players are, and the description cites Hookshot for it. One serial A/B session pair would cover it, ideally on the rebased head.
docs/worklist.md Outdated
@ -39,6 +39,7 @@ Agents may add and re-score rows; a person reviews the change like any other PR.
| 1.2 | Frame generation: stutter and freezes at a 120 cap with DLSS + 3x frame generation (v2.1.0); before it, ghosting and a 30 fps feel with it on. Open: #2531 (the game keeps its own frame rate with frame generation on), #2633 | S2 | 1.0 upper bound; frame generation users | 0.4 | | #2510; #bug-reporting "stutter and freeze on 120fps cap", "ghosting while using framegen", "RTX 2080 feels capped at game 30fps" |
| 1.1 | Blocky squares or a low-resolution grid around light sources, fire, fairies and volumetric fog ("the haze bug from 2.0.5 returned" on v2.1.0); also green particle corruption. No PR found | S2 | 0.51 upper bound (AMD); one NVIDIA report | 0.7 | | #1006 (logs from 4 commenters), #1117 (closed into it); #bug-reporting "Jarring artifacts", "Volumetric grid patterning.", "the resolution/haze bug from 2.0.5 returned", "Visual Artifacting: Pixelated Grid Overlay & Green Particle Corruption" |
| 1.1 | Updating to v2.1.0 stalls for over 10 minutes, or leaves the game unable to start (an error in `wgpu_native` after 2.0.14 to 2.1.0). Merged after v2.1.0: #2141, #2155, #2519 | S1 | 0.57 | 0.4 | | #2505; #bug-reporting "stuck updating from 2.0.11 to 2.1", [Error in wgpu_native file while updating](https://discord.com/channels/1568810484818378804/1568813928002224148/threads/1570272793231233084) |
| 0.9 | After a zone change, HUD button labels the GPU draws come out blank or as white blocks until they are drawn again (seen with `RX_VK_PARALLEL=4`). A texture the GPU writes in a virtual pool (at or above 1 TB) before anything uploads it had no page map, so its first check after the game finalized it took all of its storage for remapped, and the whole image was uploaded from guest memory over the GPU's texels; with the serial replay the same path re-uploads full-size render and depth targets | S2 | 0.3 | 1.0 | proci | This PR (fix, game-free test, in-game A/B on Longshot); ported from [NotRexed/Hookshot#3004](https://github.com/NotRexed/Hookshot/pull/3004) (merged 2026-11-13); see [NotRexed/Hookshot#2508](https://github.com/NotRexed/Hookshot/issues/2508) |
Owner

This row is under Open, but it lands on main only when this PR merges, and by docs/worklist.md's Move rule the row then belongs in Fixed, waiting for a release, with this PR's link. As it stands, main would list a fixed bug as open work with an owner. Please move it (and replace "This PR" with the PR's full link).

This row is under **Open**, but it lands on main only when this PR merges, and by docs/worklist.md's *Move* rule the row then belongs in **Fixed, waiting for a release**, with this PR's link. As it stands, main would list a fixed bug as open work with an owner. Please move it (and replace "This PR" with the PR's full link).
@ -3565,0 +3570,4 @@
// without that lock. A texture clear gives none: resolve() samples its texture first, which notes the map, unless
// only writers that give none wrote it before (then as before). RX_VK_NO_DRAWN_PAGE_MAP=1: as before
static const bool note_map = !env_on("RX_VK_NO_DRAWN_PAGE_MAP");
if (why && strcmp(why, "?") != 0 && note_map && tex->gpu >= (1ull << 40) && tex->page_map.empty() && tex->guest_size) {
Owner

Checked: the only path that reaches this without the ParLock is begin_pass with every target already texture_marked. The dry pass's own reasoned write has noted the map by then, so page_map.empty() is false and nothing is walked. Suggestion: say that in the comment ("a pass's targets: under the ParLock, or already noted by the dry pass"), so the next person adding a lock-free reasoned writer sees why it matters. NOTED_BY enforces it already, so this is only a nit.

Checked: the only path that reaches this without the `ParLock` is `begin_pass` with every target already `texture_marked`. The dry pass's own reasoned write has noted the map by then, so `page_map.empty()` is false and nothing is walked. Suggestion: say that in the comment ("a pass's targets: under the ParLock, or already noted by the dry pass"), so the next person adding a lock-free reasoned writer sees why it matters. `NOTED_BY` enforces it already, so this is only a nit.
HumanBean force-pushed fix/virtual-pool-drawn-page-map from 627455b086
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 150e96eec2
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 5s
Third-party parts / As their notes say (pull_request) Successful in 8s
PR description / Description follows the template (pull_request_target) Failing after 4s
Website / Website on Cloudflare (pull_request_target) Has been skipped
2026-11-14 01:26:02 +00:00
Compare
Owner

Rebased onto main c0442078d3 (it had been on 514591a3), to clear the merge conflict.

  • Only docs/tested-configurations.md conflicted: main had added six 2026-11-13 rows at the same place as this PR's four. Kept main's rows and put this PR's four rows right after them, unchanged.
  • No code conflicted: gpu/vk/vk_texture.cpp, the new test and the other docs applied cleanly on top of #323 and the rest of main.
  • Checked: for every file, this PR's added and removed lines against the new main are the same as against its old base.

New head: 150e96eec2 (still authored by proci). Nothing was built, tested or run here.

Rebased onto main c0442078d32e13b7073a0451129296948e6fd1c8 (it had been on 514591a3), to clear the merge conflict. - Only docs/tested-configurations.md conflicted: main had added six 2026-11-13 rows at the same place as this PR's four. Kept main's rows and put this PR's four rows right after them, unchanged. - No code conflicted: gpu/vk/vk_texture.cpp, the new test and the other docs applied cleanly on top of https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/323 and the rest of main. - Checked: for every file, this PR's added and removed lines against the new main are the same as against its old base. New head: 150e96eec21e720fffb0a57b696c251b63a6f7a2 (still authored by proci). Nothing was built, tested or run here.
HumanBean changed title from WIP: fix(vk): HUD labels the GPU drew no longer go blank after a zone change to fix(vk): HUD labels the GPU drew no longer go blank after a zone change 2026-11-14 01:27:56 +00:00
HumanBean deleted branch fix/virtual-pool-drawn-page-map 2026-11-14 01:28:02 +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!325
No description provided.