perf(vk): import game memory in 4 MB chunks, not 16, so VRAM mirrors fit the game's pools #2685
Closed
github-sync
wants to merge 4 commits from
github/pr/2685 into main
pull from: github/pr/2685
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!2685
Loading…
Reference in a new issue
No description provided.
Delete branch "github/pr/2685"
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?
A pool's VRAM mirror covers its whole import, and imports are made in 16 MB chunks, so 28-39% of the mirrors' bytes hold memory outside any game pool. Importing in 4 MB chunks cuts the mirrors by roughly a quarter with no frame-rate cost measured.
Cause. Imports are made by chunk (
import_chunk,RX_VK_IMPORT_CHUNK_MB) around what was bound, and a mirror copies its pool's whole range. A temporary measuring patch counted, for the live mirrors, the bytes outside every entry ofguest_pools: 459 of 1,666 MB in a Hyrule Field walk at DLAA 2560x1440, 463 of 1,360 MB in Kokiri Forest, and up to 925 of 2,400 MB after Castle Town to the field. It also found almost no mirrors with no game pool in them at all, so the waste is padding inside live mirrors, whichmirrors_give_backcan't reach.Change.
import_chunk()defaults to 4 MB where the pools have VRAM mirrors (mirror_on(): discrete cards, not GCN). Without mirrors a chunk's padding costs no VRAM, so iGPUs and GCN keep 16 MB and take no extra imports.span_game_poolsstill takes a game pool across a chunk's end into one import (the Kokiri hitch it was made for did not come back).RX_VK_IMPORT_CHUNK_MB=16gives the old size.docs/development.mdgets a row for the switch.Measured. RTX 4070 SUPER, Windows 11, DLAA 2560x1440, unattended, the same build (main
15646d18; the branch is rebased ontoedaef2a9) with and withoutRX_VK_IMPORT_CHUNK_MB=16:RX_VK_TEST_ALLOWED_MB=6144), VRAM peakIn the walk the freed VRAM is taken up by textures, as the budget allows, so the process's VRAM mean only drops 90-240 MB there; where the allowance is tight it shows as a lower peak. The 6 GB frame rate went either way across two pairs, so I'm not claiming a frame-rate gain.
Tests.
tests/test_import_chunk_default.pychecks the default (4 with mirrors, 16 without), that the switch still sets it, and thatpool_lookupstill spans pools across a chunk's end; it fails on the old default.nx2/test_import_windows.py's fixture gets amirror_on()stub, since it compilesimport_chunk.Not tested. AMD (an import there cost 20-75 ms on the replay thread per the code's comments, so ~50% more of them may matter), Intel, Linux, a real 6 or 8 GB card.
Part of #993.
🤖 Generated with Claude Code
Tested on an 8 GB card, where it matters most: in Castle Town Market it takes about 7 fps and most of the long frames away.
Windows 11, RTX 3060 Ti 8 GB (driver 32.0.16.2114), Ryzen 5 5600X (12 threads), 2560x1440 DLSS.
mainba005ca4with #2711 and #2731 merged locally (both arms), plus this PR, A/B withRX_VK_IMPORT_CHUNK_MB=16. Market child, 30 s of the camera turning after a settle,RX_VK_FRAMEGEN=0,HOS_FPS_MAX=240, arms alternated.The Market's GPU time moves between launches on this card, but all three 4 MB runs are above all three 16 MB ones. As in your walk, the freed VRAM goes to textures (the budget rises by ~250 MB); with fewer textures evicted and made again, the long frames drop.
A follow-up that may help with the mirrors on 8 GB cards in general: on this card, the Market's frame rate from launch to launch follows how much the mirrors hold.
Same machine as above (RTX 3060 Ti 8 GB, Windows). 37 runs in Castle Town Market today, across several builds (
mainwith and without #2711, #2731, this PR, #2709, #2624), all the same 30-40 s camera-turning test. Lasta frame: mirrors N (M MB)line of each run against its fps:Correlation between fps and mirror MB over the 37: -0.77. The slow launches are the ones whose mirrors reach the budget's 2 GB (
vram / 4on this card): textures then get ~1.8 GB where the fast launches get ~2.4-2.7 GB, and the GPU's frame goes from ~11 ms to 14-20 ms. That is the launch-to-launch "fast or slow" GPU time blushOFF described in #995.I could not find what makes a launch build 75 mirrors or 300: the save, the route and the build were the same, and the five fast launches came in a row at the end of the session. With this PR the fast case is more common (its 4 MB arm was at 1.66-1.77 GB against 1.97-2.00 GB for 16 MB), which fits the gain above. A lower mirror budget on 8 GB cards might also be worth a test, though the comment in
mirror_budget()says 1 GB made Hyrule Field stutter there.I merged this into current main and ran
tests/test_import_chunk_default.py,nx2/test_import_windows.py(all three modes) and the import/mirror checks: all pass.mirror_on()readsdev.props, and the firstimport_chunk()call comes from an import, which is after device creation, so the cached default can't be decided before the GPU is known. iGPUs and GCN keep 16 MB, andspan_game_poolsstill covers a pool across a chunk's end. Your table, plus klikso's independent A/B on an RTX 3060 Ti 8 GB in Castle Town Market (higher fps on all three 4 MB runs and about half the frames over 50 ms), is enough for me on NVIDIA. AMD RDNA is the tier to watch. It gets the new default too, the code's comments put an import there at 20-75 ms on the replay thread, and this makes ~50% more of them. If an AMD tester reports new hitches,RX_VK_IMPORT_CHUNK_MB=16is the A/B. I found no other open PR that touchesimport_chunk. The docs row merges cleanly alongside #2684's.github-sync referenced this pull request2026-11-12 16:54:48 +00:00
A note on my measurements in this thread: this PC's DLSS model row was on Transformer 2 (L), not the default K, for all of them. L costs ~4 ms more GPU time a frame on this card and more VRAM (#2836), so my absolute fps are ~10 lower than the default would give in Hyrule Field and the card was shorter of memory than a default setup. The comparisons were all made with the same setting in both arms, so their direction stands, but their size is for that harder case.
Thanks, this is a clean, well-measured change. I looked mainly at what the smaller chunks cost.
Allocations and driver limits. Each import is its own
vkAllocateMemory(host-pointer import, or a dma-buf on Linux), so the import count is what counts againstmaxMemoryAllocationCount(4096 on the Windows drivers). Your walk makes 91-94 imports in five minutes. Even if every game pool were imported in 4 MB pieces, the live count stays far below the limit, and retired imports still go back on the existing rules. The mirrors go through VMA withcreate_device_buffer. At 4 MB, as at 16, they are well under half of the 64 MB block, so they share blocks rather than each taking its own memory object, and the extra mirrors don't addvkAllocateMemorycalls one for one. No concern there.CPU per sync.
mirror_syncskips clean 2 MB regions, so its page walk follows the dirty pages, not the mirror's size. Smaller mirrors also mean less padding to walk. What grows is per-pool: one more loop step and at most one morevkCmdCopyBufferper extra mirrored pool, plus the O(live pools) passes at each submission (the capture ranges, the idle scan, the pre-segment sync). On a few hundred pools that should be small. Your runs and klikso's are GPU-bound, though (DLAA/DLSS at 1440p), so they wouldn't show it. If your logs already have the memory-sync time per frame for both arms, a before/after line would settle it. That's not a blocker.Import time. Your table counts every
import_poolcall, including remakes when a binding crosses a chunk end, so the +50% (and 0.7 s against 0.8 s of driver time, longest import 66 ms against 44 ms in Kokiri) is the full cost on NVIDIA. I agree with Joey-Magnus that AMD RDNA is the tier to watch. It gets the new default, and an import there cost 20-75 ms on the replay thread. That cost comes from the driver locking the chunk's pages, so a 4 MB import should cost less each, which offsets some of the larger count, but nobody has measured it.RX_VK_IMPORT_CHUNK_MB=16is the A/B if an RX 6000/7000/9000 tester reports new hitches.Memory saved and where. RTX 4070 SUPER 12 GB: mirrors 1,236-1,268 MB against 1,645-1,657 MB in the walk, and 843 against 1,154 MB in Kokiri. klikso's independent A/B on an RTX 3060 Ti 8 GB has all three 4 MB runs above all three 16 MB runs in the Market, with about half the long frames. Their note that those runs used DLSS model L changes the size of the gain, not its direction. That's enough for the default on NVIDIA. GCN and iGPUs keep 16 MB through
mirror_on(), and Joey already checked that it is only read after device creation.CI is green on
4d0271fand the PR merges cleanly. The only change since Joey's approval is the merge of main and the tested-configurations rows. #2825 (klikso's trim of mostly-dead imports) builds on this PR's 4 MB chunks and was measured on top of it, so this one should go first.