Skip to content

perf(vk): 6 GB cards mirror a quarter of the card (1536 MB), on Auto and on the Low preset - #2834

Open
i12bp8 wants to merge 3 commits into
NotRexed:mainfrom
i12bp8:perf/low-vram-laptop-image-pool
Open

i12bp8 wants to merge 3 commits into
NotRexed:mainfrom
i12bp8:perf/low-vram-laptop-image-pool

Conversation

@i12bp8

@i12bp8 i12bp8 commented Nov 12, 2026 •

Copy link
Copy Markdown

6 GB cards (the RTX 3060 Laptop, 2060, 1660 and the like) start on the Low preset, whose 512 MB of mirrors leaves most of the game's memory pools out of VRAM: the GPU reads them over PCIe, 30-32 GB of bound ranges every 600 submits in Hyrule Field on an RTX 3060 Laptop. This gives those cards a quarter of the card, 1536 MB, on Low and on Auto alike: in Castle Town Market 20 → 22.4 fps, GPU 32 → 25 ms a frame, half the frames over 50 ms.

What changed

  • The VRAM budget row's 3.6 GB step mirrors 1536 MB, not 512 (gpu/vk/vk_menu.cpp). It is the Low (6GB) preset's, which menu_first_start_for_gpu gives every dedicated card of 4.5-6.5 GB.
  • Auto agrees (gpu/vk/mirror_budget.h, auto_bytes, used by mirror_budget in vk_memory.cpp): cards over 4.5 GB and up to 6.5 GB get a quarter (1536 MB on 6 GB; it was an eighth, 768). Cards of 4.5 GB or less keep an eighth (512 MB on 4 GB), larger ones a quarter up to 3 GB as before. The eighth on 6 GB cards was there because the image blocks made at the start sat empty beside the mirrors; since fix(vk): make the blocks at the start in the pool the textures are made in (#2690) #2711 they are made in the textures' pool.
  • A preset's mirrors never pass Auto's on the card (preset_bytes, when a Quality preset set the budgets): Low on a 4 GB card, which preset_caps_memory lets through, or Minimum with the row moved to 3.6 GB, stays at 512. A budget chosen on the VRAM budget row itself stays the player's on any card (feat(menu): a VRAM budget row of its own (builds on #1358) #1859).
  • tests/test_mirror_budget.py runs the rules and checks that mirror_budget uses them; tests/test_vram_budget_row.py has Low's 1536. docs/development.md says what RX_VK_MIRROR_MB defaults to.

Tested

Windows 11, RTX 3060 Laptop 6 GB (5.0-5.1 GB allowed), NVIDIA 621.14, Ryzen 7 6800HS, 16 GB. The first start's Low (6GB) budget, DLSS (1152x648 → 1536x864), Reflex on, Game speed Per frame (HOS_REALTIME_STEP=0). No window (HOS_HEADLESS), input through the control bridge: the debug teleport, then standing with the camera turning for 60 s. A = 51da046 + #2711, B = that + this; both renderers built with llvm-mingw (RX_VK_ONLY, RX_RELEASE=1, NVIDIA DLSS). main's own renderer (API 20, a new translator stamp) does not run against this PC's game and program cache, so the base is the installed build's plus #2711. Two runs each, in turn:

A B
Castle Town Market fps 20.2, 20.1 22.5, 22.3
GPU ms a frame 31.4, 33.5 24.6, 25.2
frames over 50 ms 1252, 1287 571, 616
driver's count at most / allowed (MB) 4669 / 5096, 4671 / 5064 5039 / 5096, 5233 / 5099
textures' budget (held) 1.98 (2.25), 1.99 (2.31) GB 1.08 (1.86), 1.03 (1.90) GB
textures made again empty 0, 3 0, 3
Hyrule Field fps 28.8, 29.3 29.1, 32.3
GPU ms a frame 24.0, 23.5 21.0, 18.7
frames over 50 ms 165, 134 34, 148
driver's count at most / allowed (MB) 5469 / 5039, 5359 / 5039 4988 / 5097, 4982 / 5103
textures' budget (held) 2.67 (1.82), 3.00 (1.89) GB 0.87 (1.29), 0.88 (1.29) GB
textures made again empty 6, 4 11, 11

The cost is the textures' budget: about 1 GB in the market and under 0.9 GB in Hyrule Field, where twice as many textures were made again in 60 s (headless: not looked at on screen). Earlier, on a scripted walk (main before #2711, the preallocation off by switch): 1536 MB took ~0 GB over PCIe every 600 submits against 30-32 GB, GPU 27 → 19.5 ms; 1024 MB gave 26.5 and 31.3 fps with the budget full and which pools stayed out varying.

  • No device lost, no new error lines; the program cache kept (47866 keys).
  • python -I -B tests/run_all.py on Windows: 472 passed, 10 failed, 41 skipped. The same 10 fail on main on this PC (symlink privilege, dotnet restore, no gcc, and test_readme_menu_rows: the Skip opening row has no README line).
  • Rows in docs/tested-configurations.md.
  • Nothing on screen changes: no screenshots.

Not tested: another 6 GB card (a desktop one with a display attached has less allowed), AMD (GCN keeps its mirrors off) and Intel, Linux, 4 GB cards (the cap keeps them at 512).

Overlap

🤖 Generated with Claude Code

Copilot AI balanced review requested due to automatic review settings November 12, 2026 15:53

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

@ghost ghost left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks, this is a careful write-up: one named card, alternated pairs, the PCIe traffic counted, and the 1024 MB arm too. The finding that the images' pool holds 896 MB empty on a 6 GB card matches what #2711 and #2759 found on 8 GB and larger cards. The two parts need different handling, so I'll take them separately.

(1) The preallocation. On #2759 I set where this lands: #2711 moves the blocks made at the start into the textures' pool instead of dropping them. That keeps what #2438 made them for, the ~170 ms block allocations on AMD's Windows driver, which nobody has measured since. image_prealloc_blocks here gives the images' pool the same count as #2711 does (0 when compacted, 15% with RX_VK_COMPACT=0). The difference is that #2711 puts those blocks where the textures land. So please drop part (1) and build (2) on #2711. With #2711 the 7 blocks hold Hyrule Field's textures (your (1)-alone arm shows a 1.8-1.9 GB texture budget, well past 896 MB), so the room (2) needs should still be there. That's the run I'd like to see: #2711 + (2) against #2711 alone on the 3060 Laptop, same walk.

(2) Low's mirrors, 512 → 1536. The numbers on your laptop are convincing (GPU 27 → 19 ms, the PCIe reads gone). But this is a default for every card that starts on Low: menu_first_start_for_gpu puts any dedicated card of 4.5-6.5 GB there (GTX 1060 6 GB, the 1660s, 2060, 3050 6 GB, 3060/4050 laptops, RX 5600 XT, Arc A380), plus everyone who saved Low already. GCN keeps the mirrors off (mirror_on), so it doesn't touch those. A few things before I'd take it:

  • A scene that fills the textures. At Low, textures (3.6 GB) and mirrors (1.5 GB) now ask for 5.1 GB together, the card's whole allowance, and nothing holds the mirrors back under pressure by default (RX_VK_MIRROR_TIGHT is opt-in). So the textures' budget is what gives. Hyrule Field for 75 s may not get there. Could you add one run in a heavier spot (Castle Town Market or Kakariko), and give the textures' budget and the driver's count from the log for both arms, the 1536 one included? The table has the budget for (1) alone but not for (1)+(2).
  • One more 6 GB card if you can find one. A desktop card with a display attached has less allowed than your laptop. Or AMD, which this hasn't been run on. If you can't, a tester with a 2060/1660 or an RX 5600 XT running the same walk would do.
  • The Auto rule. mirror_budget gives cards of 6.5 GB or less vram/8 (768 MB) on purpose: its comment says a 6 GB laptop went past what the card has with a quarter. So the new comment's "a quarter of the card as on larger ones" doesn't match: a 6 GB player on Auto gets 768, on Low 1536. Either Low is right and Auto should follow (with the same data), or the comment should say why the two differ.

Smaller points are inline. Overlaps: this conflicts with #2711 and #2759 in images_pool(), with #2676 on the kVramMirrorMb line and the preset table in test_vram_budget_row.py, and with #2826 in vram_compact.h and test_vram_compact.*. I'd land #2711 first, then this as (2) only. #2676 maps Balanced to the 3.6 GB step, so with both, 8 GB cards on Balanced mirror 1536 (Auto gives them 2048); that's fine with me.

CI: only the title check has run; the other workflows are waiting for approval to run on this PR. Your local run_all results are noted. It merges cleanly with main for now.

Comment thread gpu/vk/vk_menu.cpp
// whole submissions: 25.4 fps, 525-645 frames over 50 ms in a three-minute walk. At 1536, 29.2-29.5 fps, GPU 19.5 ms, 146-164
// frames over 50 ms, the driver's count no higher than before. It has the room once the images' pool keeps no blocks it does
// not use (vram_compact.h image_prealloc_blocks: 896 MB of that card's 5.1 GB allowance, held empty)
constexpr int kVramMirrorMb[kVramBudgets] = {0, 256, 1536, 1024, 1536, 2048, 2560, 3072};

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The table no longer rises with the step: 3.6 GB mirrors 1536, 4.8 GB 1024. That's fine for the row's own steps, but preset_caps_memory lets Low's budgets through on any card up to 6.75 GB. So a 4 GB card on Low, or on Minimum with the row moved to 3.6 GB (that keeps opts.budget at 1, so the cap passes), now asks for 1.5 GB of mirrors on top of a 3.6 GB texture budget, where it was 512. Those players have already gone past the row's hint, so it's an edge case. Still, a cap on the mirrors by the card's size (for example, no more than vram/4 when a preset sets them) would keep a 4 GB card where it was.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Fixed: preset_bytes keeps a preset's mirrors at Auto's for the card, so a 4 GB card on Low stays at 512.

Comment thread gpu/vk/vk_vma.cpp Outdated
// (RX_VK_COMPACT=0). Compacted, they have a pool of their own and these blocks only sat empty, never given back: 896 MB of a 6 GB
// card's 5.1 GB allowance held for 53 MB of images (vram_compact.h image_prealloc_blocks). RX_VK_IMAGE_PREALLOC=<blocks>: that
// many, 0: none)
const bool textures_own_pool = vram_compact::enabled(getenv("RX_VK_COMPACT"));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This hunk and image_prealloc_blocks are what #2711 changes (it gives the images' pool 0 when compacted too, and makes the blocks in the textures' pool instead). Please drop it here and rebase (2) on #2711, see the review body.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Fixed: dropped, and the PR is on #2711.

@i12bp8
i12bp8 force-pushed the perf/low-vram-laptop-image-pool branch from f0a61fd to a620ba1 Compare November 12, 2026 17:59
i12bp8 added a commit to i12bp8/Hookshot that referenced this pull request Nov 12, 2026
…irrors never pass Auto's

Review of NotRexed#2834: Low's 1536 MB and Auto's 768 MB disagreed on the same
6 GB card, and preset_caps_memory lets Low's budgets through on cards up
to 6.75 GB, so a 4 GB card on Low (or on Minimum with the row at 3.6
GB) would have asked for 1.5 GB of mirrors where it had 512.

gpu/vk/mirror_budget.h holds the rule, apart from Vulkan:
- auto_bytes: a quarter of the card, at most 3 GB, as before above
  6.5 GB; an eighth on cards of 4.5 GB or less (512 MB on 4 GB), as
  before; a quarter between (1536 MB on 6 GB, was an eighth). The
  eighth there was for the empty image blocks the images' pool made at
  the start, which NotRexed#2711 now makes in the textures' pool.
- preset_bytes: a Quality preset's mirrors never more than Auto's on
  the card. A budget chosen on the VRAM budget row itself stays the
  player's on any card.

tests/test_mirror_budget.py runs the rules and checks that
mirror_budget uses them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
i12bp8 added a commit to i12bp8/Hookshot that referenced this pull request Nov 12, 2026
 with NotRexed#2834, Hyrule Field and Castle Town Market

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@i12bp8
i12bp8 force-pushed the perf/low-vram-laptop-image-pool branch from a620ba1 to 45cec91 Compare November 12, 2026 18:03
i12bp8 added a commit to i12bp8/Hookshot that referenced this pull request Nov 12, 2026
…irrors never pass Auto's

Review of NotRexed#2834: Low's 1536 MB and Auto's 768 MB disagreed on the same
6 GB card, and preset_caps_memory lets Low's budgets through on cards up
to 6.75 GB, so a 4 GB card on Low (or on Minimum with the row at 3.6
GB) would have asked for 1.5 GB of mirrors where it had 512.

gpu/vk/mirror_budget.h holds the rule, apart from Vulkan:
- auto_bytes: a quarter of the card, at most 3 GB, as before above
  6.5 GB; an eighth on cards of 4.5 GB or less (512 MB on 4 GB), as
  before; a quarter between (1536 MB on 6 GB, was an eighth). The
  eighth there was for the empty image blocks the images' pool made at
  the start, which NotRexed#2711 now makes in the textures' pool.
- preset_bytes: a Quality preset's mirrors never more than Auto's on
  the card. A budget chosen on the VRAM budget row itself stays the
  player's on any card.

tests/test_mirror_budget.py runs the rules and checks that
mirror_budget uses them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
i12bp8 added a commit to i12bp8/Hookshot that referenced this pull request Nov 12, 2026
 with NotRexed#2834, Hyrule Field and Castle Town Market

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@i12bp8 i12bp8 changed the title perf(vk): 6 GB cards mirror the game's memory in the VRAM the images' pool held empty perf(vk): 6 GB cards mirror a quarter of the card (1536 MB), on Auto and on the Low preset Nov 12, 2026
i12bp8 and others added 3 commits November 12, 2026 19:05
…, not 512

Every 6 GB card starts on the Low (6GB) preset, whose 512 MB of mirrors
left most of the game's memory pools out of VRAM: the GPU read them
over PCIe and the game's readbacks waited for whole submissions.

RTX 3060 Laptop (6 GB), Hyrule Field, a scripted walk, two runs each:
30-32 GB of bound ranges read over PCIe every 600 submits at 512 MB,
~0 at 1536. Main: 25.4, 25.4 fps, GPU 26.7-27.2 ms a frame, 525-645
frames over 50 ms. With this and the commit before (a build of them,
no switches): 30.1, 31.8 fps, GPU 18.4-19.1 ms, 161-285 frames over
50 ms, VRAM at most 5.7-5.8 GB against 5.9. 1024 MB was not enough:
26.5 and 31.3 fps, the budget full and which pools stayed out varying.

The row's 3.6 GB step (the Low preset's) only: a preset's budget
applies on the card it is for (preset_caps_memory). The 1.5 GB comes
out of what the images' pool no longer holds empty.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…irrors never pass Auto's

Review of NotRexed#2834: Low's 1536 MB and Auto's 768 MB disagreed on the same
6 GB card, and preset_caps_memory lets Low's budgets through on cards up
to 6.75 GB, so a 4 GB card on Low (or on Minimum with the row at 3.6
GB) would have asked for 1.5 GB of mirrors where it had 512.

gpu/vk/mirror_budget.h holds the rule, apart from Vulkan:
- auto_bytes: a quarter of the card, at most 3 GB, as before above
  6.5 GB; an eighth on cards of 4.5 GB or less (512 MB on 4 GB), as
  before; a quarter between (1536 MB on 6 GB, was an eighth). The
  eighth there was for the empty image blocks the images' pool made at
  the start, which NotRexed#2711 now makes in the textures' pool.
- preset_bytes: a Quality preset's mirrors never more than Auto's on
  the card. A budget chosen on the VRAM budget row itself stays the
  player's on any card.

tests/test_mirror_budget.py runs the rules and checks that
mirror_budget uses them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
 with NotRexed#2834, Hyrule Field and Castle Town Market

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@i12bp8
i12bp8 force-pushed the perf/low-vram-laptop-image-pool branch from 45cec91 to 14b92e2 Compare November 12, 2026 18:06
@i12bp8

i12bp8 commented Nov 12, 2026

Copy link
Copy Markdown
Author

Thanks for the review. Updated:

#2711 alone (A) against #2711 + this (B), RTX 3060 Laptop, Low, DLSS, standing with the camera turning for 60 s, two runs each in turn. Built on 51da046 + #2711: main's renderer is API 20 with a new translator stamp, which this PC's game and program cache don't take.

A B
Castle Town Market fps 20.2, 20.1 22.5, 22.3
GPU ms a frame 31.4, 33.5 24.6, 25.2
frames over 50 ms 1252, 1287 571, 616
driver's count at most / allowed (MB) 4669 / 5096, 4671 / 5064 5039 / 5096, 5233 / 5099
textures' budget (held) 1.98 (2.25), 1.99 (2.31) GB 1.08 (1.86), 1.03 (1.90) GB
textures made again empty 0, 3 0, 3
Hyrule Field fps 28.8, 29.3 29.1, 32.3
GPU ms a frame 24.0, 23.5 21.0, 18.7
frames over 50 ms 165, 134 34, 148
driver's count at most / allowed (MB) 5469 / 5039, 5359 / 5039 4988 / 5097, 4982 / 5103
textures' budget (held) 2.67 (1.82), 3.00 (1.89) GB 0.87 (1.29), 0.88 (1.29) GB
textures made again empty 6, 4 11, 11

You were right that the textures' budget is what gives: about 1 GB in the market and under 0.9 GB in Hyrule Field, where twice as many textures were made again (11 against 4-6 in 60 s; headless, so I haven't looked at them on screen). The market, the heavier spot, is the clearer win: +11% fps, GPU -24%, half the long frames, no more textures made again. With #2711 alone the driver's count went past the allowance in Hyrule Field (5.36-5.47 of 5.04 GB); with this it stayed under there and reached it in the market.

Not done: another 6 GB card, or AMD. I can't run those here; a tester with a 2060, a 1660 or an RX 5600 XT on the same two spots would settle it. If the texture side needs holding back, the two things I'd measure next are RX_VK_MIRROR_TIGHT on for these cards, or 1024 MB (26.5 and 31.3 fps on my first walk, with the budget full and which pools stayed out varying).

@i12bp8
i12bp8 requested a review from hckrman101 November 12, 2026 19:22

@ghost ghost left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for turning this around so quickly. The PR is much easier to judge as the mirrors alone, and the Market run is the run I asked for.

Fixed

  • (1) The preallocation is gone, and the PR sits on #2711.
  • The 4 GB cap. preset_bytes holds a preset's mirrors at Auto's for the card. So Low on a 4 GB card, and Minimum with the row moved to 3.6 GB (opts.budget stays 1), keep 512. A step chosen on the row itself stays the player's (#1859). test_mirror_budget runs both rules and checks that mirror_budget calls them.
  • Auto and Low agree at a quarter on 4.5-6.5 GB cards, and the comment now says why the eighth was there. Auto on 6 GB is a new default, but it leaves more room than Low: Auto's textures are 45% of the allowance (~2.3 GB on your laptop), Low's 3.6 GB.

Still open: the measurements behind a default for every 6 GB card
The Market numbers are a clear win: 20 → 22.4 fps, GPU 32 → 25 ms, the long frames halved. The rest of the table is less clear:

  • In Hyrule Field, B made textures again 11 times against 4-6, with the textures' budget down to 0.87 GB. The runs were headless, so nobody has seen whether that shows up as blurry or popping textures. The fps there (29.1 / 32.3 against 28.8 / 29.3) is within the spread of two runs.
  • In the Market, B's second run went past the allowance (5233 of 5099 MB), where both A runs stayed under. In Hyrule Field it was the other way round. So both arms go over somewhere.
  • The base is missing the merges that set this balance. Both arms are 51da046 + #2711. Today main also took #2685 (4 MB import chunks, "so VRAM mirrors fit the game's pools"), #2825 (an import mostly outside the pools trimmed back), #2826 (eviction takes only the excess), #2624 (BC textures counted by blocks) and #2795. #2685 and #2825 change what a given mirror budget covers, and #2624 and #2826 change how the textures give way. 768 or 1024 may now do what 1536 did on the old base, or 1536 may cost the textures less. I know main's renderer doesn't run against your current program cache. Could you prepare the game again from main (rebased) and repeat the two spots, A = main, B = this? A windowed look at Hyrule Field in B would also show whether the extra textures made again are visible.
  • A second 6 GB card is still missing. A desktop card with a display attached gets a smaller allowance than your laptop. I'll ask the testers for a 2060, 1660 or RX 5600 XT on the same two spots. One such run, together with the rerun on main, is what I'd want before this becomes the default for these cards.

Merge order and conflicts. It conflicts with main now, in docs/tested-configurations.md only (rows added at the top of the same table; vk_menu.cpp and development.md merge cleanly). #2676 (presets' VRAM budgets) edits the lines next to kVramMirrorMb and the preset table in test_vram_budget_row.py. It's clean and smaller, so I'd land it first and rebase this on it. With both, Balanced on an 8 GB card mirrors 1536 against Auto's 2048, which is fine, as I said before. #2850 (Auto (this PC)) sets opts.budget to its tier, so preset_bytes caps its mirrors the same way, and the two don't overlap in code.

CI: only the title check has run on this head. The build and test workflows are still waiting for approval.

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.

2 participants