fix(vk): HUD labels the GPU drew no longer go blank after a zone change #325
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!325
Loading…
Reference in a new issue
No description provided.
Delete branch "proci/Longshot:fix/virtual-pool-drawn-page-map"
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
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
mainit showed in both of my sessions withRX_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
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 calltexture_touch's first-upload seed makes (texture_dirty(*tex, 0, ...)), and before it marks the texture uploaded.texture_touch, thentexture_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.resolve()) and a storage image (texture_view_of) sample their texture first, which notes the map there."?": the upscaler's stand-ins, which run without theParLocka change to the map needs, andclear_tex) do what they did onmain.gpu_written,released) and is not taken as uploaded.keep_finalized).texture_flush_to_guestkeeps writing it back before its virtual pages are remapped.tests/test_drawn_page_map.py(new, game-free) cutstexture_written,texture_released,texture_dirtyandtexture_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 reasonstexture_writtenis given, each with its writer's file and lock (NOTED_BY), and fails when a reason is added or moves.RX_VK_NO_DRAWN_PAGE_MAP=1in docs/development.md; a row in docs/worklist.md (Open, 0.9); four rows in docs/tested-configurations.md.Cost:
guest_size / 64 KBmapping 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.mainmade the same walk at the object's first check after a finalize, so it is new only for objects never sampled after one.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
RX_VK_NO_DRAWN_PAGE_MAP=1RX_VK_PARALLEL=4; full-size targets with the serial replay)Read once with
env_on. The flag starts with none oftranslator_stamp's prefixes, so setting it keeps the program cache. It is in docs/development.md's switch table, next toRX_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 main514591a3as627455b0Hardware: 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.
e51e28e9(longshot e51e28e9 (source 8dd02aab)). Each side'slongshot_vk.dllwas built alone from its commit (RX_VK_ONLY, release, llvm-mingw). The main one is the same file as the full build's.longshot_log.txt'svk: upload over a drawn image:lines in the bug's form: finalized, KB dirty, without ", a GPU buffer wrote it".How to reproduce:
RX_VK_PARALLEL=4, a save in Hyrule Field near Castle Town, then walk into Castle Town. Onmainthe R1 and R2 labels and the D-pad cross are gone by the gate, and the log has the lines below.e51e28e9(run 1 / run 3)VK_ERRORlinestests/test_drawn_page_map.pymain @
e51e28e9, run 1, at the gate:and full-size targets in the same session:
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:
The test, with main's vk_texture.cpp and with this change's:
After the rebase. The runs are on
e51e28e9; the branch is now rebased onto514591a3with no conflicts. The numbers still hold:texture_writtencall, aParLockordry_mark_written, so the test'sNOTED_BYlock notes still hold.run_allpass on the rebased branch (Testing).Testing
Tested:
tests/test_drawn_page_map.py, with the change on and withRX_VK_NO_DRAWN_PAGE_MAP=1.RX_VK_CPU_OVER_GPU=1): only those pages are taken;main: withmain'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).python -I -B tests/run_all.py --jobs 8on Windows 11 (llvm-mingw clang 23.1.5).627455b0: 497 passed, 4 failed, 33 skipped.main514591a3: 496 passed, the same 4 failed (test_check_third_party,test_finish_output,test_menu_i18n,test_release), 33 skipped.tests/andnx2/read it. On the branch, 30 pass unchanged, andnx2/test_bc_storage_views.pyis skipped as onmain(it needs a Windows Vulkan device). None needed changing.longshot_vk.dllbuilt with this change one51e28e9(RX_VK_ONLY, release, llvm-mingw): 0 failed, no warnings.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:
-Wdangling-reference.Needs other testers:
RX_VK_PARALLEL=4, walk from Hyrule Field into Castle Town. Expect the HUD's labels and D-pad cross drawn, and noupload over a drawn image ... finalizedline for a HUD texture (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.this PR @ 4830ea7d (run 2): 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
texture_flush_to_guestalso 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.keep_finalized).RX_VK_NO_DRAWN_PAGE_MAP=1turns the change off, or the commit can be reverted alone.Reviewer notes
note_maplines intexture_written(gpu/vk/vk_texture.cpp), then the test'sNOTED_BYtable.guest_bytes) over what the GPU drew. The change keeps that read for the pages the CPU really wrote or remapped.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.keep_finalized's cap shared with its new rule). This change can add to the finalized drawn textureskeep_finalizedkeeps (Risk and rollback).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.Issues
No Longshot issue found for blank HUD labels (searched 2026-11-13). See NotRexed/Hookshot#2508.
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.Written with Claude Code (Claude Opus 5.5). I played the in-game A/B. The commit carries a
Co-Authored-Bytrailer.🤖 Generated with Claude Code
proci referenced this pull request2026-11-14 00:01:43 +00:00
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, …)seedtexture_touchdoes before a first upload. After a finalize, the check then takes only what was remapped or written sincerelease_gen, the same as below 1 TB. The"?"writers are left out because they don't hold theParLock(checked inbegin_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.
RX_VK_PARALLEL=4, and as 15 MB target re-uploads.texture_writtenhere,texture_frame's eviction there). 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 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:
test_requests.pyshows no request for this PR.627455b0are still pending (run 2786). The Python checks on Linux include the new test's compile, so they settle part of it once they run.@ -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) |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 beforestatic 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) {Checked: the only path that reaches this without the
ParLockisbegin_passwith every target alreadytexture_marked. The dry pass's own reasoned write has noted the map by then, sopage_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_BYenforces it already, so this is only a nit.627455b086150e96eec2Rebased onto main
c0442078d3(it had been on514591a3), to clear the merge conflict.New head:
150e96eec2(still authored by proci). Nothing was built, tested or run here.WIP: fix(vk): HUD labels the GPU drew no longer go blank after a zone changeto fix(vk): HUD labels the GPU drew no longer go blank after a zone change