Skip to content

perf(vk): import game memory in 4 MB chunks, not 16, so VRAM mirrors fit the game's pools - #2685

Open
SadTransGirl wants to merge 1 commit into
NotRexed:mainfrom
SadTransGirl:perf/import-chunk-4mb
Open

SadTransGirl wants to merge 1 commit into
NotRexed:mainfrom
SadTransGirl:perf/import-chunk-4mb

Conversation

@SadTransGirl

Copy link
Copy Markdown
Contributor

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 of guest_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, which mirrors_give_back can'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_pools still 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=16 gives the old size. docs/development.md gets a row for the switch.

Measured. RTX 4070 SUPER, Windows 11, DLAA 2560x1440, unattended, the same build (main 15646d1; the branch is rebased onto edaef2a) with and without RX_VK_IMPORT_CHUNK_MB=16:

16 MB 4 MB
Hyrule Field walk, mirrors (two runs a side) 1,645-1,657 MB 1,236-1,268 MB
Hyrule Field walk, fps 59.0, 58.2 58.1, 58.6
Kokiri Forest, mirrors / VRAM mean 1,154 / 7,281 MB 843 / 6,672 MB
Kokiri Forest, fps 57.0 58.2
Castle Town to the field, VRAM mean / fps 8,357 MB / 50.4 7,859 MB / 50.5
6 GB allowance (RX_VK_TEST_ALLOWED_MB=6144), VRAM peak 6,135 / 6,129 MB 5,865 / 5,887 MB
6 GB allowance, GPU's longest frame 24.1 / 21.7 ms 13.5 / 12.5 ms
6 GB allowance, fps 50.3 / 54.9 53.0 / 52.4
Imports in a five-minute walk (driver time) 59-65 (0.7 s) 91-94 (0.8 s)

In 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.py checks the default (4 with mirrors, 16 without), that the switch still sets it, and that pool_lookup still spans pools across a chunk's end; it fails on the old default. nx2/test_import_windows.py's fixture gets a mirror_on() stub, since it compiles import_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.

🤖 Generated with Claude Code

…fit the game's pools

A pool's VRAM mirror covers its import's whole range, and imports are made
by chunk around what was bound. With 16 MB chunks 28-39% of the mirrors'
bytes were outside every game pool: 459 of 1,666 MB in a Hyrule Field walk
at DLAA 2560x1440, up to 925 MB after Castle Town to the field.

The default chunk is now 4 MB where the pools have VRAM mirrors (discrete
cards). Without mirrors (iGPUs, GCN) a chunk's padding costs no VRAM, so
they keep 16 MB and no extra imports. span_game_pools still takes a game
pool across a chunk's end into one import. RX_VK_IMPORT_CHUNK_MB=16: as
before.

Measured on an RTX 4070 SUPER (Windows, DLAA 2560x1440, unattended):
- Hyrule Field walk, two runs a side: mirrors 1,236-1,268 MB against
  1,645-1,657 MB; 58.1 / 58.6 fps against 59.0 / 58.2.
- Kokiri Forest: mirrors 843 MB against 1,154, VRAM 6,672 MB mean
  against 7,281; 58.2 fps against 57.0.
- Castle Town to the field: VRAM 7,859 MB mean against 8,357; 50.5
  against 50.4 fps.
- A 6 GB allowance (RX_VK_TEST_ALLOWED_MB=6144), two runs a side: the
  VRAM peak 5,865-5,887 MB against 6,129-6,135, the GPU's longest frame
  12.5-13.5 ms against 21.7-24.1; the frame rate either way (53.0 / 52.4
  against 50.3 / 54.9 fps).
- Imports: ~50% more (91-94 against 59-65 in five minutes), each shorter,
  0.8 s of driver time in all against 0.7.

tests/test_import_chunk_default.py fails on the old default.

Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@klikso

klikso commented Nov 12, 2026

Copy link
Copy Markdown
Contributor

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. main ba005ca with #2711 and #2731 merged locally (both arms), plus this PR, A/B with RX_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.

fps median fps mean frames over 50 ms in 30 s frame p99 GPU a frame mirrors (last line) texture budget
4 MB (this PR) 47.5 / 52.2 / 49.5 46.8 / 49.7 / 49.2 17 / 5 / 10 50.5 / 44.9 / 45.0 ms 11.3 ms 1,656-1,772 MB 1,809-2,112 MB
16 MB 44.3 / 42.5 / 41.8 42.0 / 41.6 / 41.3 35 / 34 / 53 62.1 / 62.3 / 77.7 ms 12.1 ms 1,968-2,000 MB 1,673-1,853 MB

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.

@klikso

klikso commented Nov 12, 2026

Copy link
Copy Markdown
Contributor

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 (main with and without #2711, #2731, this PR, #2709, #2624), all the same 30-40 s camera-turning test. Last a frame: mirrors N (M MB) line of each run against its fps:

mirrors at the end runs fps mean frames over 50 ms
1,284-1,360 MB (75-79 mirrors) 5 51.2-54.4 1-7
1,632-1,772 MB 4 35.3-49.7 5-60
1,876-2,004 MB (121-338 mirrors) 28 28.2-44.8 26-169

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 / 4 on 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.

@Joey-Magnus Joey-Magnus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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() reads dev.props, and the first import_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, and span_game_pools still 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=16 is the A/B. I found no other open PR that touches import_chunk. The docs row merges cleanly alongside #2684's.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants