fix(vk): remake write-back shadows given back under VRAM pressure #2767
Closed
github-sync
wants to merge 1 commit from
github/pr/2767 into main
pull from: github/pr/2767
merge into: MagicBeanDev:main
MagicBeanDev:main
MagicBeanDev:github/pr/2893
MagicBeanDev:github/pr/2890
MagicBeanDev:github/pr/2887
MagicBeanDev:github/pr/2878
MagicBeanDev:github/pr/2875
MagicBeanDev:github/pr/2874
MagicBeanDev:github/pr/2873
MagicBeanDev:github/pr/2871
MagicBeanDev:github/pr/2756
MagicBeanDev:github/pr/2897
MagicBeanDev:github/pr/2896
MagicBeanDev:github/pr/2718
MagicBeanDev:github/pr/2842
MagicBeanDev:github/pr/2691
MagicBeanDev:github/pr/2895
MagicBeanDev:github/pr/2743
MagicBeanDev:github/pr/2765
MagicBeanDev:github/pr/2708
MagicBeanDev:github/pr/2732
MagicBeanDev:github/pr/2755
MagicBeanDev:github/pr/2817
MagicBeanDev:github/pr/2876
MagicBeanDev:release-please--branches--main
MagicBeanDev:release-please--branches--main--release-notes
MagicBeanDev:github/pr/2778
MagicBeanDev:github/pr/2888
MagicBeanDev:github/pr/2866
MagicBeanDev:github/pr/2877
MagicBeanDev:github/pr/2800
MagicBeanDev:github/pr/2829
MagicBeanDev:github/pr/2850
MagicBeanDev:github/pr/2889
MagicBeanDev:github/pr/2882
MagicBeanDev:github/pr/2860
MagicBeanDev:github/pr/2849
MagicBeanDev:github/pr/2886
MagicBeanDev:github/pr/2794
MagicBeanDev:github/pr/2840
MagicBeanDev:github/pr/2830
MagicBeanDev:github/pr/2834
MagicBeanDev:github/pr/2719
MagicBeanDev:github/pr/2742
MagicBeanDev:github/pr/2741
MagicBeanDev:github/pr/2820
MagicBeanDev:github/pr/2771
MagicBeanDev:github/pr/2859
MagicBeanDev:github/pr/2858
MagicBeanDev:github/pr/2823
MagicBeanDev:github/pr/2757
MagicBeanDev:github/pr/2769
MagicBeanDev:github/pr/2844
MagicBeanDev:github/pr/2843
MagicBeanDev:github/pr/2673
MagicBeanDev:github/pr/2855
MagicBeanDev:github/pr/2739
MagicBeanDev:github/pr/2807
MagicBeanDev:github/pr/2851
MagicBeanDev:github/pr/2789
MagicBeanDev:github/pr/2814
MagicBeanDev:github/pr/2818
MagicBeanDev:github/pr/2774
MagicBeanDev:github/pr/2772
MagicBeanDev:github/pr/2750
MagicBeanDev:github/pr/2786
MagicBeanDev:github/pr/2785
MagicBeanDev:github/pr/2782
MagicBeanDev:github/pr/2779
MagicBeanDev:github/pr/2775
MagicBeanDev:github/pr/2706
MagicBeanDev:github/pr/2766
MagicBeanDev:github/pr/2747
MagicBeanDev:github/pr/2759
MagicBeanDev:github/pr/2745
MagicBeanDev:github/pr/2740
MagicBeanDev:github/pr/2733
MagicBeanDev:github/pr/2725
MagicBeanDev:github/pr/2727
MagicBeanDev:github/pr/2728
MagicBeanDev:github/pr/2729
MagicBeanDev:github/pr/2721
MagicBeanDev:github/pr/2722
MagicBeanDev:github/pr/2715
MagicBeanDev:github/pr/2714
MagicBeanDev:github/pr/2707
MagicBeanDev:github/pr/2703
MagicBeanDev:github/pr/2705
MagicBeanDev:github/pr/2697
MagicBeanDev:github/pr/2679
MagicBeanDev:github/pr/2676
MagicBeanDev:github/pr/2669
MagicBeanDev:docs/what-hookshot-connects-to
MagicBeanDev:fix/hdr-encode-test
MagicBeanDev:fix/main-tests
MagicBeanDev:fix/play-cache-files
MagicBeanDev:perf/game-barrier-order
MagicBeanDev:feat/heap-lazy
MagicBeanDev:feat/texture-detail
MagicBeanDev:perf/astc-opaque-bc1
MagicBeanDev:fix/fs-dir-lock
MagicBeanDev:fix/framegen-native-kept
MagicBeanDev:fix/update-no-downgrade
MagicBeanDev:port/k10-gcn-f16-mirrors
MagicBeanDev:fix/framegen-mult-cap
MagicBeanDev:chore/release-as-2.1.1
MagicBeanDev:perf/pcache-load-switch
MagicBeanDev:fix/prepare-progress-log
MagicBeanDev:fix/update-closes-stale-game
MagicBeanDev:fix/hdr-scaled-sampled
MagicBeanDev:perf/first-start-preset
MagicBeanDev:perf/zero-upload
MagicBeanDev:fix/vcore-escape-2s
MagicBeanDev:test/fake-gh-atomic
MagicBeanDev:perf/batch-idle-flush
MagicBeanDev:test/finish-stub-a
MagicBeanDev:chore/record-2.1.0
MagicBeanDev:fix/main-red-tests
MagicBeanDev:fix/test-display-startup-driver-cache
MagicBeanDev:feat/mesh-shader-mode
MagicBeanDev:fix/menu-probes-at-startup
MagicBeanDev:fix/fps-counter-classic
MagicBeanDev:fix/test-log-batch-flake
MagicBeanDev:fix/narrow-screen-hud
MagicBeanDev:ci/prereleases
MagicBeanDev:release/v2.0.15
MagicBeanDev:hotfix/v2.0.15
MagicBeanDev:fix/present-ahead-replay
MagicBeanDev:release/v2.0.13
MagicBeanDev:fix/bracket-paths-2013
MagicBeanDev:fix/hdr-overlays-2013
MagicBeanDev:fix/fps-counter-scale-2013
MagicBeanDev:fix/stick-seen-at-rest
MagicBeanDev:hotfix/menu-fits-screen
MagicBeanDev:fix/f1-always-opens-menu
MagicBeanDev:fix/ini-keep-lines
MagicBeanDev:claude/cranky-pike-7cf247
MagicBeanDev:perf/texture-answer-flat
MagicBeanDev:fix/stamp-test-modules
MagicBeanDev:fix/appimage-fonts
MagicBeanDev:fix/display-awake
MagicBeanDev:release/v2.0.12
MagicBeanDev:fix/discord-windows
MagicBeanDev:rc/v2.0.12-tests
MagicBeanDev:fix/dlss-keep-feature
MagicBeanDev:rc/v2.0.12
MagicBeanDev:button-icons
No reviewers
Labels
Clear labels
accessibility
Barrier affecting people with disabilities
autorelease: pending
autorelease: tagged
bug
Something isn't working
dependencies
Pull requests that update a dependency file
documentation
Improvements or additions to documentation
duplicate
This issue or pull request already exists
enhancement
New feature or request
github_actions
Pull requests that update GitHub Actions code
good first issue
Good for newcomers
help wanted
Extra attention is needed
invalid
This doesn't seem right
question
Further information is requested
testing wanted
wontfix
This will not be worked on
No labels
accessibility
autorelease: pending
autorelease: tagged
bug
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
question
testing wanted
wontfix
Milestone
Clear milestone
No items
No milestone
Assignees
Clear assignees
No assignees
1 participant
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/Hookshot!2767
Loading…
Reference in a new issue
No description provided.
Delete branch "github/pr/2767"
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
When VRAM is tight the renderer gives back its write-back shadows, and it made one again only when a pool's own write-back reached 256 KB. After a scene change a pool's write-backs can stay under that size for good, so the shadow never came back: in the log on #2238, 48 of 352 MB stayed whole copies for the rest of the session while the others were made again. That leaves the pool without a shadow and its next write-back reads the mirror instead.
Fixes #2238 - FPS drops heavily and stays low after entering a new scene
Issue: #2238
What changed
gpu/vk/vk_memory.cpp: a pool whose write-back was worth a shadow is marked (Pool::shadow_wanted), and the mark stays after VRAM takes the shadow.shadows_check_roomnow runs every 120 write-back submissions when a wanted pool has a mirror and no shadow, even withshadow_bytesat 0. Once the pressure rule says there is room, it makes up to 8 shadows per check (each is an allocation of the pool's whole size), skipping a pool whose mirror is gone or whose shadow cannot fit now without spending a try. It logs what this check made, not the session total.tests/test_wb_shadow_remake.pyruns the real functions from the source over stubs: the remake after a give-back, only wanted pools, the issue's exact give-back sequence, 8 per check, retired pools never, mirror-less and too-big pools not spending tries, the make lines, and VRAM unknown.Testing
Game-free:
python -I -B tests/run_all.pyin the worktree, full suite.tests/test_input_background.py, a declared known environment failure on this PC (no MSVC SDK).tests/test_precompile_compute_guard.pyandtests/test_recorder_sync.pypassed in this run.test_wb_shadow_remakepasses.In game: NVIDIA GeForce RTX 5070 12 GB, driver 32.0.16.2114 (Vulkan 1.4), Windows 11 build 26200, i5-14600KF. Hookshot v2.1.0, Vulkan back end, windowed 2560x1440, default quality preset, frame generation on, frame pacing on, 240 cap, Kokiri Forest clearing save. Pressure harness:
RX_VK_TEST_ALLOWED_MB=4700+RX_VK_VRAM_MB=4096. The allowance is synthetic: the driver really allows 11175 MB, so this exercises the rule but cannot reproduce real paging.Path evidence:
the shadows given backfired in every run (1 + 1 per build) at 5322 MB used of the synthetic allowance, andover the driver's video-memory allowancefired once per run.shadows made again(the branch this PR adds) was 0 in all four runs: after the give-back the count stays above 97% of the synthetic allowance on this route, and the pools involved (112 MB + 16 MB) had no room undershadow_fits. The issue's repro needs a scene transition (Dodongo's Cavern); this route has none. fps is neutral within run-to-run spread (93.5-96.1 for both builds, same-binary repeats up to 4.5% apart). Not a real VRAM test: the driver never paged.Relation to #2368
#2368 keeps drawn shadow maps through evictions (the cached 12288x12288 map sampled again). This PR remakes write-back shadows after a give-back under pressure. They affect different textures and files and can land in either order.
AI assistance
Assisted-by: oc-impl-4 (opencode/deepseek-v4.1-flash), oc-upstream-1 (opencode/deepseek-v4.1-flash-free)
Tracking: #993 (where #2238 was consolidated).
I merged this into current main and ran tests/test_wb_shadow_remake.py and the memory, shadow, write-back, mirror and vram checks from run_all (18 pass). It also merges cleanly with #2759 and #2766, which touch the same files. CI hasn't run on this head yet.
The change is sound. shadow_wanted is set only where a shadow was first made, and the remake in shadows_check_room goes through shadow_room, which reads VRAM again before each allocation. Shadows are only dropped above 95/99% (shadows_go) and only remade below 90/97% (shadow_fits), and that gap keeps it from making and dropping them every 120 submissions. Capping at 8 attempts per check and skipping pools without a mirror both make sense.
As the description says, the new branch fired 0 times in all four runs, so this is untested in game. The fps table only shows the change does no harm. A run that reaches the remake would show it works: a scene change under the synthetic allowance, with the allowance raised after the give-back, until
write-back diff: N shadows made againappears in the log. Dodongo's Cavern from #2238 would be ideal.Thanks for this, and for being upfront that the new branch never fired in your runs.
The code is safe. I checked what happens when a shadow is made again partway through a frame:
shadow_knownall zero.shadows_check_roomruns at the top of the write-back block, aftershadows_forget_written. So the first write-back that uses the shadow copies each page it touches from guest memory into the shadow, in the command stream, before the diff pass reads it. That's the same state as a brand-new shadow from the 256 KB path.dead_shadows, so it isn't destroyed until its submissions are done. Pools whose mirror was freed or that were retired are skipped.tries++ == 8allows exactly 8 attempts.Joey-Magnus already covered the hysteresis between
shadows_goandshadow_fits, and I agree with him.What's missing is evidence that this fixes #2238. Your table shows the change does no harm, but the remake count was 0 in all four runs, so the fix hasn't been seen working. I'm also not convinced this is what caused the drop. The pools that stay without a shadow are, by construction, the ones whose write-backs stay under 256 KB per submission. Their whole copies are small. The 48 MB in the issue's log is the size of the shadows they'd get, not how much they copy each frame. For a "drops heavily and stays low" report on an 8 GB laptop card, I'd expect the cause to be the VRAM pressure itself rather than those copies.
It also changes a trade-off. These pools now get a shadow the size of the whole pool, under pressure, for write-backs that the original 256 KB rule decided weren't worth one.
shadow_fitslimits the risk, but the gain needs showing.Could you get one run where the branch actually fires? Same synthetic allowance, raise it after the give-back (or take a route with a scene change, ideally Death Mountain into Dodongo's Cavern from the issue), until
shadows made againshows up in the log. Then post fps and p99 for that scene before and after the remake. Logs withRX_VK_LOG_WRITEBACK=1would also show how many bytes those pools really copy each frame. If the gain is real it'll show there. If it isn't, this probably belongs in #993 as a finding rather than as the fix for #2238.The test reads the real functions from the source, declares
RUN_ALL_ARGS, and covers the cases that matter (mirror-less pools and pools that are too big don't use up a try, retired pools aren't remade).Merge order: this merges cleanly with main, including #2731 (now merged), and Joey checked #2759 and #2766. #2794 (sparse mirrors) adds its own fields to
Poolright aftershadow_known, the same spot asshadow_wanted, so whichever lands second has a one-line conflict to resolve. Nothing interacts beyond that: a shadow is still sized to the whole pool under sparse mirrors.CI on this head is waiting for workflow approval. So far only the title check has run.
The new fixture fails Windows source CI when clang uses MSVC CRT headers:
getenvis deprecated and-Werrorrejects it in bothhookshot_env.hand the fixture. Completed job and log.The test,
vk_memory.cppandhookshot_env.hare byte-identical to main86a9cda2in the affected ASTC branch; the RomFS branch has the same failure with identical base inputs too. Could you carry the CRT compatibility define used by the existingtests/test_game_font.pyinto this fixture, preserving-Werror? This is a test compilation failure, not evidence against the shadow-remake behavior.