perf(vk): a 6 GB card mirrors a quarter of its VRAM, not an eighth or 512 MB #237

Merged
HumanBean merged 6 commits from i12bp8/Longshot:perf/low-vram-mirror-budget into main 2026-11-14 00:41:07 +00:00
Contributor

Why

On 6 GB cards, Hyrule Field runs at about 18 fps on the Low (6GB) preset, which every 6 GB card with mesh shaders starts on. The game's largest memory pools don't fit in its 512 MB of VRAM mirrors (768 MB on Auto), so the GPU reads them over PCIe. Giving these cards a quarter of their VRAM, as larger cards get, more than doubles the frame rate. It costs VRAM headroom: on the card measured, the process then uses about 95% of what the driver allows, against 82% on main (below).

Ported from https://github.com/NotRexed/Hookshot/pull/2834 (Hookshot, before the move).

What changed

  • vk: new gpu/vk/mirror_budget.h (budget_bytes was named preset_bytes until 091daa9):
    • auto_bytes() is Auto's rule: a quarter of the card, at most 3 GB, and an eighth only on a small card, 4.5 GB or less (small_card()). It was an eighth on cards of 6.5 GB or less.
    • budget_bytes() caps a budget's mirrors at Auto's. That applies to a budget set by a Quality preset on any card. It also applies to one chosen on the VRAM budget row itself on a small card, so a 4 GB card on the row's 3.6 GB step stays at 512 MB as before. Its 4.8 GB-and-up steps, past what such a card has, also keep 512 now (1024-3072 on main).
  • vk: mirror_budget() in gpu/vk/vk_memory.cpp uses both, with the card's size from device_vram_bytes(). RX_VK_MIRROR_MB still wins over both. The comment above mirror_on() no longer names a 3072 MB default.
  • menu: kVramMirrorMb in gpu/vk/vk_menu.cpp: the 3.6 GB VRAM budget step (the Low (6GB) preset's) asks for 1536 MB of mirrors, not 512. Through budget_bytes() a 6 GB card gets Auto's quarter (1498 MB on the card measured, which reports 5992 MB), and a 4 GB card gets 512.
  • tests: tests/test_mirror_budget.{py,cpp} compiles and checks both rules, including the small-card cap on the row, and checks that mirror_budget() uses them. tests/test_vram_budget_row.py now expects Low's 1536.
  • docs: RX_VK_MIRROR_MB in docs/development.md says what the default is. docs/tested-configurations.md has the three runs below.

Out of scope, on purpose: the row's mirrors are now non-monotonic. The 3.6 GB step (Low) asks for 1536 MB, more than the 4.8 GB step's (Balanced) 1024. Hyrule Field mirrors 1.3-1.5 GB here, so 8 GB cards on Auto or Balanced (RTX 3070, 4060, RX 6600) may still leave a pool in system memory. That is a question for a follow-up with measurements on an 8 GB card.

Flags and settings

Flag What it does Default now Recommendation Helps on Doesn't help on Untested on
Auto's mirror budget (mirror_budget.h auto_bytes; RX_VK_MIRROR_MB=<MB> overrides it) How much VRAM the game's pools are mirrored in A quarter of a 4.5-6.5 GB card (about 1.5 GB on 6 GB), was an eighth (768 MB). Unchanged above and below On by default RTX 3060 Laptop 6 GB at the Low preset: 17.6 → 39.2 fps (measured) Cards of 8 GB or more and of 4.5 GB or less (unchanged) 6 GB cards that start on the Minimum tier because they have no mesh shaders, where preset_caps_memory() is false so Auto's rule applies (768 → 1536 MB): GTX 1060 6 GB (the README's minimum card) and RX 5600 XT (RDNA1, mirrors on). They run the compute fallback, with the least GPU headroom. Desktop 6 GB cards that drive monitors (RTX 2060, 1660 Super), whose allowance is smaller than this laptop's
VRAM budget row, 3.6 GB step (the Low (6GB) preset) The mirrors of that step Asks 1536 MB (was 512), capped at Auto's on every card a preset set it on, and on small cards when chosen on the row On 6 GB cards - 4 GB cards in the game (the test covers the cap)

To go back to main's sizes for a run: RX_VK_MIRROR_MB=512 (Low's former) or 768 (Auto's former on 6 GB).

Measurements

main: fbd4202 (2026-11-13)
this PR: perf/low-vram-mirror-budget @ c17b339, on main fbd4202 (the code measured). Since rebased onto main d964886 as 38c1a48: the same diff, plus a comment and the tested-configurations row.
Why the numbers still hold after the rebase: main's 9 commits since fbd4202 don't touch the mirrors, their budget or the VRAM budget row (mirror_budget(), kVramMirrorMb, preset_caps_memory and device_vram_bytes are unchanged, as is MKWVK_API_VERSION 21). They do touch occlusion queries and pipelines, which could move both sides' fps alike, not the gap between them.
Hardware: NVIDIA GeForce RTX 3060 Laptop GPU 6 GB (Optimus: it drives no desktop; the driver reports 5992 MB and allows the process about 5.0 GB), driver 621.14, AMD Ryzen 7 6800HS, 16 GB RAM, Windows 11 Home (26200)
Scene: Hyrule Field, walking the same route, about 2 minutes a run; output 2048x1152; Quality preset Low (6GB), with its own settings: VRAM budget 3.6 GB, DLSS render size 1152x648 in every run (no Auto stepping); frame generation off, no cap
Method: the launcher's v3.0.0-rc.3 game (source cb01b5a3, MKWVK_API_VERSION 21 as main) with its renderer, longshot_vk.dll, built alone from each commit (RX_VK_ONLY=1 RX_RELEASE=1 python -I nx2/build.py --standalone, llvm-mingw, NVIDIA DLSS SDK) and swapped in before each run. 6 runs, alternated main, PR, PR at 1024 MB (RX_VK_MIRROR_MB=1024), twice. Frame rates come from longshot_log.txt's "N presents, X fps" lines (every 120 presents), counting only in-field lines (over 1000 draws a frame) and dropping the first one (loading). VRAM comes from the in-field vk: VRAM: ... used of ... allowed lines (every 600 frames). The median is taken per run and over both runs.
How to reproduce: build the renderer alone as above from each commit, put it in a copy of a game folder's bin/, choose Quality preset Low (6GB), and walk through Hyrule Field.

main @ fbd4202 this PR @ c17b339 this PR @ c17b339, RX_VK_MIRROR_MB=1024
fps, run 1 / run 2 (median) 16.7 / 18.6 39.2 / 38.9 22.4 / 33.1
fps, both runs (median) 17.6 39.2 30.7
Mirrors' budget 512 MB 1498 MB 1024 MB
Largest pool left in system memory 224 MB 92 MB 152 MB
VRAM used of allowed, median (max) 4085 of 4998 MB, 82% (90%) 4818 of 5037 MB, 95% (98%) 4432 of 5007 MB, 89% (92%)
Textures held, median 1619 MB 1560 MB 1624 MB
Textures evicted and uploaded again from the game's memory (the always-on counter; the per-texture log, RX_VK_LOG_REMADE, was off) 20 or more in run 1, 0 in run 2 0, 0 0, 0
commit limit near lines (system RAM: this PC has 16 GB), the least commit left 7 and 2, 495 MB 3 and 4, 575 MB 2 and 3, 1015 MB
Render-target pool "VRAM short: given back" lines 0 5 0
VRAM compactions 6 4 2
Vulkan errors, device lost, out of memory none none none

What it costs. The extra GB comes out of the headroom. On this card the process goes from 82% to 95% of the driver's allowance, at most 98%, and the render-target pool gives images back when VRAM runs short (5 times in two runs, never on main). The textures' budget reads 0 MB on both sides because of system RAM, not VRAM: every run, main and PR alike, hit the commit-limit path (commit limit near) on this 16 GB PC, which sets the textures' budget down to 0. So "textures held" says little about the VRAM squeeze. What does: no texture was evicted and uploaded again in the PR's runs (20 or more in main's first run), as far as the always-on counter shows. The mirrors themselves are not squeezed when VRAM runs short, because RX_VK_MIRROR_TIGHT is opt-in. The texture cache takes the squeeze (Risk, below). No texture went missing and nothing failed here, but this laptop is close to a best case: its GPU drives no desktop. A desktop 6 GB card that drives monitors and shares VRAM with a browser has less allowance, and gets closer to the situation in #130 sooner (T-NWN5 asks for one).

1024 MB as a middle. It keeps more headroom (89%) and still gains (30.7 fps median), but it is uneven: 22.4 in one run, 33.1 in the other. The pools are filled first come, first served, so which large pool is left out varies (152 MB in one run, 40 MB in the other). Auto's quarter is the size at which the scene's pools fit every time here. If reviewers prefer the headroom, 1024 on 6 GB cards is the alternative.

Earlier runs, kept in docs/tested-configurations.md: the same card at a 33% render size (Auto's step-down) with main 0edc137's and this branch's renderers, 20.1 against 42.2 fps. Also rc.3's own renderer with only RX_VK_MIRROR_MB set, 18.8 against 38.6.

At higher render resolutions (same card, same route; a full game build of 091daa9, main's mirrors by RX_VK_MIRROR_MB=512 in it; rows in docs/tested-configurations.md). Here the VRAM is at its fullest:

main (512 MB) this PR (a quarter) 1024 MB this PR + RX_VK_MIRROR_TIGHT=1
Handheld, Native (DLSS at 1536x864): fps, 2 runs 16.9 / 17.7 36.2 / 36.1 31.0 / 31.5 30.5 / 31.4
frames over 50 ms a run (75 s) 973 / 936 50 / 47 76 / 86 95 / 70
VRAM used of allowed, median (max) 80% (87%) 96% (99%) 89% (93%) 95% (99%)
Docked, Native (DLSS at 2048x1152): fps, 2 runs 12.6 / 12.5 24.4 / 23.4 - -
frames over 50 ms a run (60 s) ~780 135-153 - -

At Docked native, VRAM went past the allowance for a moment (5200 of 5063 MB) and nothing failed. Two alternatives were tried and dropped. The 90% rule for mirrors (RX_VK_MIRROR_TIGHT=1) costs 5 fps and still ends at 95%. An adaptive budget that shrinks the mirrors when VRAM stays over 97% (kept on a branch, not in this PR) did worse: 20.9 against 24 fps docked, 31 against 34 at Low. VRAM sits at 95-99% with any budget, because the texture cache fills what is left. Lowering the budget frees no VRAM, since mirrors in use stay; it only keeps new pools out, so they are read over PCIe again.

Other testers so far (in the team's Discord, not yet posted here as test-request results):

  • RTX 3060 Laptop 6 GB, Linux (treverflume): a large gain. Its crashes were the kernel's OOM killer (system RAM, 15 GB with other programs open). The same crash happens with rc.3's own renderer, so it is not this PR.
  • GTX 1660 Ti 6 GB (site), with RX_VK_MIRROR_MB set: Hyrule Field 12 to about 25 fps and dungeons 50-55 at 1080p. At its native render resolution, 1536 MB took VRAM to 5.2-5.4 GB and was slower than 1024. That is not reproduced on the laptop above, and the log with the VRAM: ... used of ... allowed lines is asked for (T-XFT3).
rev_main_2: vk: pool 0x108c000000 (224 MB) stays in system memory: the mirrors have 480 MB of 512
rev_main_1: vk: VRAM: 4085 MB used of 4987 MB allowed (699 MB of it free in the blocks); textures 1706 MB (budget 0 MB), ...
rev_pr_2:   vk: VRAM: 4940 MB used of 5022 MB allowed (603 MB of it free in the blocks); textures 1486 MB (budget 0 MB), ...
rev_pr_2:   vk: render target images: 0 reused from the pool, 260 made new, 142 destroyed from it; it holds 0 (0 MB), VRAM short: given back
rev_pr_1:   vk: 59 pools mirrored in VRAM (1496 MB)
rev_1gb_1:  vk: VRAM: 4320 MB used of 5007 MB allowed (509 MB of it free in the blocks); textures 1588 MB (budget 142 MB), ...
median of the in-field "N presents, X fps" lines over both runs: main 17.6 (26 lines), PR 39.2 (34), 1024 MB 30.7 (25)

Testing

Tested:

  • python -I -B tests/run_all.py on Windows 11 with the launcher's llvm-mingw, at the branch's head 091daa9 (on main d964886): 451 passed, 10 failed, 73 skipped. tests/test_menu_i18n.py fails on main d964886 itself (a pt_BR string no longer in vk_menu.cpp). The other 9 failed on main fbd4202 as well (447 passed there): test_release, test_check_third_party, test_lift_cache, test_spawn_emit, test_portable_builder_home, test_portable_builder_startup, test_fpx_sticky, test_bake_portability and nx2/test_nup_paths. None of them reads the files this PR changes. test_mirror_budget and test_vram_budget_row pass.
  • python -I scripts/check_third_party.py: 11 parts as their notes say.
  • Built on Windows: the renderer alone from this branch and from main with llvm-mingw (RX_VK_ONLY, RX_RELEASE, with NVIDIA DLSS), 0 failed, linked.
  • Built on Linux: the renderer alone (longshot_vk.so, as scripts/linux/appimage.sh builds it, with tools.lock's linux-x64 tools and RX_MARCH=x86-64-v3 RX_VK_ONLY=1 RX_RELEASE=1) in scripts/linux/Dockerfile's Ubuntu 22.04 image, at a5ee84d (this branch's head, with the small-card cap): 0 failed, linked. test_mirror_budget passes there with g++.
  • In the game: 6 alternated runs on the Low preset and 4 earlier ones, in Hyrule Field on an RTX 3060 Laptop 6 GB (above). No crashes, no Vulkan errors, no texture problems seen.

Not tested:

  • In the game on Linux, and a full game build of this branch (the renderer was built alone and run in rc.3's game).
  • Desktop 6 GB cards driving monitors, with a smaller allowance.
  • 6 GB cards without mesh shaders (GTX 1060 6 GB, RX 5600 XT) on the Minimum tier.
  • AMD and Intel 6 GB cards.
  • 4 GB cards in the game.
  • 8 GB cards' unchanged budgets (out of scope).

Needs other testers:

  • A 6 GB card that drives a monitor, or an AMD or Intel one (RTX 2060, GTX 1660 Super/Ti, RX 5600 XT, RX 6600M, Arc A580): Hyrule Field on Low, main against this PR, with the VRAM: lines, the render-target pool lines and RX_VK_LOG_REMADE=1, expecting no device lost, no VRAM-full stutter and VRAM under the allowance (T-XFT3, replacing T-NWN5)
  • 4 GB card (GTX 1650, RX 5500M): Low preset, expecting the log's mirrors at 512 MB as on main (T-9799)

Screenshots

Nothing on screen

Risk and rollback

  • What could break. The mirrors take up to about 1 GB more VRAM on 4.5-6.5 GB cards, and nothing gives that back when VRAM runs short. mirrors_trim only cuts away the parts of a mirror outside every game pool while VRAM is over 80%, and the 90% rule for mirrors exists only behind RX_VK_MIRROR_TIGHT, which is opt-in. What gives memory back is the texture cache (its 90% rule, and the compaction's hold at 85%) and the render-target pool. So the trade-off is "mirrors take the extra GB, and textures take the squeeze". The worst case is a desktop 6 GB card with a small allowance: textures re-uploaded more often or held at lower detail, or the driver over its limit as in #130.
  • How to turn it off. For one run, RX_VK_MIRROR_MB=768 (Auto's former) or 512 (Low's former). Otherwise, reverting the commit is enough.

Reviewer notes

Start with gpu/vk/mirror_budget.h, where the rules and the reasons sit together. On cards of 8 GB or more nothing changes. On small cards (4.5 GB or less) Auto and the presets are unchanged, and the VRAM budget row is now capped at Auto's, which lowers its 4.8 GB-and-up steps there. Only the band between 4.5 and 6.5 GB moves from an eighth to a quarter.

Issues

See https://github.com/NotRexed/Hookshot/pull/2834
See #130

Checklist

For everyone

  • One topic.
  • The title is a Conventional Commit (type(scope): description).
  • Rebased on current main.
  • python -I -B tests/run_all.py run and passing (any failure that also fails on main is named above).
  • Tested in the game, or what wasn't is named under Testing.
  • Testing filled in: what was tested, what wasn't, and what needs other testers (with test requests).
  • Measured: main and this PR at named commits, on the same PC, scene and settings, at least 2 interleaved runs each, the median reported.
  • No game files, dumps, build output or generated files.
  • third_party/ untouched (a new folder with a PROVENANCE.md instead).
  • The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled.

If applicable

  • AI assistance recorded with Assisted-by: / Co-Authored-By: trailers.
  • Flags noted, and the switch reference updated (docs/development.md, the README).
  • A row in docs/tested-configurations.md for each measured run.
  • A check under tests/ that reads changed source updated in this PR, not weakened.
  • Docs updated.
  • Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths.
## Why On 6 GB cards, Hyrule Field runs at about 18 fps on the Low (6GB) preset, which every 6 GB card with mesh shaders starts on. The game's largest memory pools don't fit in its 512 MB of VRAM mirrors (768 MB on Auto), so the GPU reads them over PCIe. Giving these cards a quarter of their VRAM, as larger cards get, more than doubles the frame rate. It costs VRAM headroom: on the card measured, the process then uses about 95% of what the driver allows, against 82% on main (below). Ported from https://github.com/NotRexed/Hookshot/pull/2834 (Hookshot, before the move). ## What changed - vk: new `gpu/vk/mirror_budget.h` (`budget_bytes` was named `preset_bytes` until 091daa9): - `auto_bytes()` is Auto's rule: a quarter of the card, at most 3 GB, and an eighth only on a small card, 4.5 GB or less (`small_card()`). It was an eighth on cards of 6.5 GB or less. - `budget_bytes()` caps a budget's mirrors at Auto's. That applies to a budget set by a Quality preset on any card. It also applies to one chosen on the VRAM budget row itself on a small card, so a 4 GB card on the row's 3.6 GB step stays at 512 MB as before. Its 4.8 GB-and-up steps, past what such a card has, also keep 512 now (1024-3072 on main). - vk: `mirror_budget()` in `gpu/vk/vk_memory.cpp` uses both, with the card's size from `device_vram_bytes()`. `RX_VK_MIRROR_MB` still wins over both. The comment above `mirror_on()` no longer names a 3072 MB default. - menu: `kVramMirrorMb` in `gpu/vk/vk_menu.cpp`: the 3.6 GB VRAM budget step (the Low (6GB) preset's) asks for 1536 MB of mirrors, not 512. Through `budget_bytes()` a 6 GB card gets Auto's quarter (1498 MB on the card measured, which reports 5992 MB), and a 4 GB card gets 512. - tests: `tests/test_mirror_budget.{py,cpp}` compiles and checks both rules, including the small-card cap on the row, and checks that `mirror_budget()` uses them. `tests/test_vram_budget_row.py` now expects Low's 1536. - docs: `RX_VK_MIRROR_MB` in `docs/development.md` says what the default is. `docs/tested-configurations.md` has the three runs below. **Out of scope, on purpose:** the row's mirrors are now non-monotonic. The 3.6 GB step (Low) asks for 1536 MB, more than the 4.8 GB step's (Balanced) 1024. Hyrule Field mirrors 1.3-1.5 GB here, so 8 GB cards on Auto or Balanced (RTX 3070, 4060, RX 6600) may still leave a pool in system memory. That is a question for a follow-up with measurements on an 8 GB card. ## Flags and settings | Flag | What it does | Default now | Recommendation | Helps on | Doesn't help on | Untested on | |---|---|---|---|---|---|---| | Auto's mirror budget (`mirror_budget.h` `auto_bytes`; `RX_VK_MIRROR_MB=<MB>` overrides it) | How much VRAM the game's pools are mirrored in | A quarter of a 4.5-6.5 GB card (about 1.5 GB on 6 GB), was an eighth (768 MB). Unchanged above and below | On by default | RTX 3060 Laptop 6 GB at the Low preset: 17.6 → 39.2 fps (measured) | Cards of 8 GB or more and of 4.5 GB or less (unchanged) | 6 GB cards that start on the Minimum tier because they have no mesh shaders, where `preset_caps_memory()` is false so Auto's rule applies (768 → 1536 MB): **GTX 1060 6 GB** (the README's minimum card) and **RX 5600 XT** (RDNA1, mirrors on). They run the compute fallback, with the least GPU headroom. Desktop 6 GB cards that drive monitors (RTX 2060, 1660 Super), whose allowance is smaller than this laptop's | | VRAM budget row, 3.6 GB step (the Low (6GB) preset) | The mirrors of that step | Asks 1536 MB (was 512), capped at Auto's on every card a preset set it on, and on small cards when chosen on the row | On | 6 GB cards | - | 4 GB cards in the game (the test covers the cap) | To go back to main's sizes for a run: `RX_VK_MIRROR_MB=512` (Low's former) or `768` (Auto's former on 6 GB). ## Measurements main: fbd4202 (2026-11-13) this PR: perf/low-vram-mirror-budget @ c17b339, on main fbd4202 (the code measured). Since rebased onto main d964886 as 38c1a48: the same diff, plus a comment and the tested-configurations row. Why the numbers still hold after the rebase: main's 9 commits since fbd4202 don't touch the mirrors, their budget or the VRAM budget row (`mirror_budget()`, `kVramMirrorMb`, `preset_caps_memory` and `device_vram_bytes` are unchanged, as is `MKWVK_API_VERSION` 21). They do touch occlusion queries and pipelines, which could move both sides' fps alike, not the gap between them. Hardware: NVIDIA GeForce RTX 3060 Laptop GPU 6 GB (Optimus: it drives no desktop; the driver reports 5992 MB and allows the process about 5.0 GB), driver 621.14, AMD Ryzen 7 6800HS, 16 GB RAM, Windows 11 Home (26200) Scene: Hyrule Field, walking the same route, about 2 minutes a run; output 2048x1152; **Quality preset Low (6GB)**, with its own settings: VRAM budget 3.6 GB, DLSS render size 1152x648 in every run (no Auto stepping); frame generation off, no cap Method: the launcher's v3.0.0-rc.3 game (source cb01b5a3, `MKWVK_API_VERSION` 21 as main) with its renderer, `longshot_vk.dll`, built alone from each commit (`RX_VK_ONLY=1 RX_RELEASE=1 python -I nx2/build.py --standalone`, llvm-mingw, NVIDIA DLSS SDK) and swapped in before each run. 6 runs, alternated main, PR, PR at 1024 MB (`RX_VK_MIRROR_MB=1024`), twice. Frame rates come from `longshot_log.txt`'s "N presents, X fps" lines (every 120 presents), counting only in-field lines (over 1000 draws a frame) and dropping the first one (loading). VRAM comes from the in-field `vk: VRAM: ... used of ... allowed` lines (every 600 frames). The median is taken per run and over both runs. How to reproduce: build the renderer alone as above from each commit, put it in a copy of a game folder's `bin/`, choose Quality preset Low (6GB), and walk through Hyrule Field. | | main @ fbd4202 | this PR @ c17b339 | this PR @ c17b339, `RX_VK_MIRROR_MB=1024` | |---|---|---|---| | fps, run 1 / run 2 (median) | 16.7 / 18.6 | 39.2 / 38.9 | 22.4 / 33.1 | | **fps, both runs (median)** | **17.6** | **39.2** | **30.7** | | Mirrors' budget | 512 MB | 1498 MB | 1024 MB | | Largest pool left in system memory | 224 MB | 92 MB | 152 MB | | **VRAM used of allowed, median (max)** | **4085 of 4998 MB, 82% (90%)** | **4818 of 5037 MB, 95% (98%)** | **4432 of 5007 MB, 89% (92%)** | | Textures held, median | 1619 MB | 1560 MB | 1624 MB | | Textures evicted and uploaded again from the game's memory (the always-on counter; the per-texture log, `RX_VK_LOG_REMADE`, was off) | 20 or more in run 1, 0 in run 2 | 0, 0 | 0, 0 | | `commit limit near` lines (system RAM: this PC has 16 GB), the least commit left | 7 and 2, 495 MB | 3 and 4, 575 MB | 2 and 3, 1015 MB | | Render-target pool "VRAM short: given back" lines | 0 | 5 | 0 | | VRAM compactions | 6 | 4 | 2 | | Vulkan errors, device lost, out of memory | none | none | none | **What it costs.** The extra GB comes out of the headroom. On this card the process goes from 82% to 95% of the driver's allowance, at most 98%, and the render-target pool gives images back when VRAM runs short (5 times in two runs, never on main). The textures' budget reads 0 MB on both sides because of system RAM, not VRAM: every run, main and PR alike, hit the commit-limit path (`commit limit near`) on this 16 GB PC, which sets the textures' budget down to 0. So "textures held" says little about the VRAM squeeze. What does: no texture was evicted and uploaded again in the PR's runs (20 or more in main's first run), as far as the always-on counter shows. The mirrors themselves are not squeezed when VRAM runs short, because `RX_VK_MIRROR_TIGHT` is opt-in. The texture cache takes the squeeze (Risk, below). No texture went missing and nothing failed here, but this laptop is close to a best case: its GPU drives no desktop. A desktop 6 GB card that drives monitors and shares VRAM with a browser has less allowance, and gets closer to the situation in https://git.magicbean.dev/MagicBeanDev/Longshot/issues/130 sooner (T-NWN5 asks for one). **1024 MB as a middle.** It keeps more headroom (89%) and still gains (30.7 fps median), but it is uneven: 22.4 in one run, 33.1 in the other. The pools are filled first come, first served, so which large pool is left out varies (152 MB in one run, 40 MB in the other). Auto's quarter is the size at which the scene's pools fit every time here. If reviewers prefer the headroom, 1024 on 6 GB cards is the alternative. Earlier runs, kept in `docs/tested-configurations.md`: the same card at a 33% render size (Auto's step-down) with main 0edc137's and this branch's renderers, 20.1 against 42.2 fps. Also rc.3's own renderer with only `RX_VK_MIRROR_MB` set, 18.8 against 38.6. **At higher render resolutions** (same card, same route; a full game build of 091daa9, main's mirrors by `RX_VK_MIRROR_MB=512` in it; rows in `docs/tested-configurations.md`). Here the VRAM is at its fullest: | | main (512 MB) | this PR (a quarter) | 1024 MB | this PR + `RX_VK_MIRROR_TIGHT=1` | |---|---|---|---|---| | Handheld, Native (DLSS at 1536x864): fps, 2 runs | 16.9 / 17.7 | **36.2 / 36.1** | 31.0 / 31.5 | 30.5 / 31.4 | | frames over 50 ms a run (75 s) | 973 / 936 | **50 / 47** | 76 / 86 | 95 / 70 | | VRAM used of allowed, median (max) | 80% (87%) | 96% (99%) | 89% (93%) | 95% (99%) | | Docked, Native (DLSS at 2048x1152): fps, 2 runs | 12.6 / 12.5 | **24.4 / 23.4** | - | - | | frames over 50 ms a run (60 s) | ~780 | **135-153** | - | - | At Docked native, VRAM went past the allowance for a moment (5200 of 5063 MB) and nothing failed. **Two alternatives were tried and dropped.** The 90% rule for mirrors (`RX_VK_MIRROR_TIGHT=1`) costs 5 fps and still ends at 95%. An adaptive budget that shrinks the mirrors when VRAM stays over 97% (kept on a branch, not in this PR) did worse: 20.9 against 24 fps docked, 31 against 34 at Low. VRAM sits at 95-99% with any budget, because the texture cache fills what is left. Lowering the budget frees no VRAM, since mirrors in use stay; it only keeps new pools out, so they are read over PCIe again. **Other testers so far** (in the team's Discord, not yet posted here as test-request results): - **RTX 3060 Laptop 6 GB, Linux (treverflume):** a large gain. Its crashes were the kernel's OOM killer (system RAM, 15 GB with other programs open). The same crash happens with rc.3's own renderer, so it is not this PR. - **GTX 1660 Ti 6 GB (site), with `RX_VK_MIRROR_MB` set:** Hyrule Field 12 to about 25 fps and dungeons 50-55 at 1080p. At its native render resolution, 1536 MB took VRAM to 5.2-5.4 GB and was slower than 1024. That is not reproduced on the laptop above, and the log with the `VRAM: ... used of ... allowed` lines is asked for (T-XFT3). ``` rev_main_2: vk: pool 0x108c000000 (224 MB) stays in system memory: the mirrors have 480 MB of 512 rev_main_1: vk: VRAM: 4085 MB used of 4987 MB allowed (699 MB of it free in the blocks); textures 1706 MB (budget 0 MB), ... rev_pr_2: vk: VRAM: 4940 MB used of 5022 MB allowed (603 MB of it free in the blocks); textures 1486 MB (budget 0 MB), ... rev_pr_2: vk: render target images: 0 reused from the pool, 260 made new, 142 destroyed from it; it holds 0 (0 MB), VRAM short: given back rev_pr_1: vk: 59 pools mirrored in VRAM (1496 MB) rev_1gb_1: vk: VRAM: 4320 MB used of 5007 MB allowed (509 MB of it free in the blocks); textures 1588 MB (budget 142 MB), ... median of the in-field "N presents, X fps" lines over both runs: main 17.6 (26 lines), PR 39.2 (34), 1024 MB 30.7 (25) ``` ## Testing **Tested:** - `python -I -B tests/run_all.py` on Windows 11 with the launcher's llvm-mingw, at the branch's head 091daa9 (on main d964886): 451 passed, 10 failed, 73 skipped. `tests/test_menu_i18n.py` fails on main d964886 itself (a pt_BR string no longer in `vk_menu.cpp`). The other 9 failed on main fbd4202 as well (447 passed there): `test_release`, `test_check_third_party`, `test_lift_cache`, `test_spawn_emit`, `test_portable_builder_home`, `test_portable_builder_startup`, `test_fpx_sticky`, `test_bake_portability` and `nx2/test_nup_paths`. None of them reads the files this PR changes. `test_mirror_budget` and `test_vram_budget_row` pass. - `python -I scripts/check_third_party.py`: 11 parts as their notes say. - Built on Windows: the renderer alone from this branch and from main with llvm-mingw (`RX_VK_ONLY`, `RX_RELEASE`, with NVIDIA DLSS), 0 failed, linked. - Built on Linux: the renderer alone (`longshot_vk.so`, as `scripts/linux/appimage.sh` builds it, with tools.lock's linux-x64 tools and `RX_MARCH=x86-64-v3 RX_VK_ONLY=1 RX_RELEASE=1`) in `scripts/linux/Dockerfile`'s Ubuntu 22.04 image, at a5ee84d (this branch's head, with the small-card cap): 0 failed, linked. `test_mirror_budget` passes there with g++. - In the game: 6 alternated runs on the Low preset and 4 earlier ones, in Hyrule Field on an RTX 3060 Laptop 6 GB (above). No crashes, no Vulkan errors, no texture problems seen. **Not tested:** - In the game on Linux, and a full game build of this branch (the renderer was built alone and run in rc.3's game). - Desktop 6 GB cards driving monitors, with a smaller allowance. - 6 GB cards without mesh shaders (GTX 1060 6 GB, RX 5600 XT) on the Minimum tier. - AMD and Intel 6 GB cards. - 4 GB cards in the game. - 8 GB cards' unchanged budgets (out of scope). **Needs other testers:** - A 6 GB card that drives a monitor, or an AMD or Intel one (RTX 2060, GTX 1660 Super/Ti, RX 5600 XT, RX 6600M, Arc A580): Hyrule Field on Low, main against this PR, with the `VRAM:` lines, the render-target pool lines and `RX_VK_LOG_REMADE=1`, expecting no device lost, no VRAM-full stutter and VRAM under the allowance (T-XFT3, replacing T-NWN5) - 4 GB card (GTX 1650, RX 5500M): Low preset, expecting the log's mirrors at 512 MB as on main (T-9799) ## Screenshots Nothing on screen ## Risk and rollback - **What could break.** The mirrors take up to about 1 GB more VRAM on 4.5-6.5 GB cards, and nothing gives that back when VRAM runs short. `mirrors_trim` only cuts away the parts of a mirror outside every game pool while VRAM is over 80%, and the 90% rule for mirrors exists only behind `RX_VK_MIRROR_TIGHT`, which is opt-in. What gives memory back is the texture cache (its 90% rule, and the compaction's hold at 85%) and the render-target pool. So the trade-off is "mirrors take the extra GB, and textures take the squeeze". The worst case is a desktop 6 GB card with a small allowance: textures re-uploaded more often or held at lower detail, or the driver over its limit as in https://git.magicbean.dev/MagicBeanDev/Longshot/issues/130. - **How to turn it off.** For one run, `RX_VK_MIRROR_MB=768` (Auto's former) or `512` (Low's former). Otherwise, reverting the commit is enough. ## Reviewer notes Start with `gpu/vk/mirror_budget.h`, where the rules and the reasons sit together. On cards of 8 GB or more nothing changes. On small cards (4.5 GB or less) Auto and the presets are unchanged, and the VRAM budget row is now capped at Auto's, which lowers its 4.8 GB-and-up steps there. Only the band between 4.5 and 6.5 GB moves from an eighth to a quarter. ## Issues See https://github.com/NotRexed/Hookshot/pull/2834 See https://git.magicbean.dev/MagicBeanDev/Longshot/issues/130 ## Checklist ### For everyone - [x] One topic. - [x] The title is a Conventional Commit (`type(scope): description`). - [x] Rebased on current `main`. - [x] `python -I -B tests/run_all.py` run and passing (any failure that also fails on `main` is named above). - [x] Tested in the game, or what wasn't is named under Testing. - [x] Testing filled in: what was tested, what wasn't, and what needs other testers (with test requests). - [x] Measured: main and this PR at named commits, on the same PC, scene and settings, at least 2 interleaved runs each, the median reported. - [x] No game files, dumps, build output or generated files. - [x] `third_party/` untouched (a new folder with a `PROVENANCE.md` instead). - [x] The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled. ### If applicable - [x] AI assistance recorded with `Assisted-by:` / `Co-Authored-By:` trailers. - [x] Flags noted, and the switch reference updated (docs/development.md, the README). - [x] A row in `docs/tested-configurations.md` for each measured run. - [x] A check under `tests/` that reads changed source updated in this PR, not weakened. - [x] Docs updated. - [x] Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths.
perf(vk): a 6 GB card mirrors a quarter of its VRAM, 1536 MB, not 512
Some checks failed
PR description / Description follows the template (pull_request_target) Successful in 17s
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been cancelled
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been cancelled
CI / Workflow lint (actionlint) (pull_request) Has been cancelled
Third-party parts / As their notes say (pull_request) Has been cancelled
Third-party parts / The same as upstream (pull_request) Has been cancelled
d27a462793
On a 6 GB card (RTX 3060 Laptop, RTX 2060) Auto gave the game's memory pools an
eighth of the card (768 MB) and the Low (6GB) preset, every 6 GB card's from its
first start, 512 MB. The game's largest pools then stayed in system memory and
the GPU read them over PCIe.

- gpu/vk/mirror_budget.h: Auto's rule (a quarter, at most 3 GB, an eighth only
  on cards of 4.5 GB or less) and a preset's cap (never more than Auto's).
- gpu/vk/vk_memory.cpp: mirror_budget() uses them.
- gpu/vk/vk_menu.cpp: the 3.6 GB VRAM budget step (Low) mirrors 1536 MB.
- tests/test_mirror_budget.{py,cpp}: the rules and their use; the VRAM budget
  row check updated.

Ported from https://github.com/NotRexed/Hookshot/pull/2834

Co-Authored-By: Claude Opus 5.5 <[email protected]>
docs(tested): RTX 3060 Laptop, main's and this branch's renderers built and swapped in Hyrule Field
Some checks failed
PR description / Description follows the template (pull_request_target) Successful in 24s
Website / Website on Cloudflare (pull_request_target) Has been skipped
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been cancelled
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been cancelled
CI / Workflow lint (actionlint) (pull_request) Has been cancelled
Third-party parts / As their notes say (pull_request) Has been cancelled
Third-party parts / The same as upstream (pull_request) Has been cancelled
2320b958e4
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Author
Contributor

Measured with builds: main 0edc137's and this branch's d27a462 renderers built alone (llvm-mingw, DLSS) and swapped into rc.3's game, 4 alternated runs in Hyrule Field on an RTX 3060 Laptop 6 GB: 20.1 fps median on main against 42.2 here; the budget 1498 MB (a quarter of the card as the driver reports it). Rows added to docs/tested-configurations.md (2320b95).

Measured with builds: main 0edc137's and this branch's d27a462 renderers built alone (llvm-mingw, DLSS) and swapped into rc.3's game, 4 alternated runs in Hyrule Field on an RTX 3060 Laptop 6 GB: 20.1 fps median on main against 42.2 here; the budget 1498 MB (a quarter of the card as the driver reports it). Rows added to docs/tested-configurations.md (2320b95).
Author
Contributor

Test requests opened: T-NWN5 (6 GB AMD or Intel) and T-9799 (4 GB card: mirrors stay at 512 MB). They need the testing wanted label to be claimable.

Test requests opened: T-NWN5 (6 GB AMD or Intel) and T-9799 (4 GB card: mirrors stay at 512 MB). They need the testing wanted label to be claimable.
Author
Contributor

Built on Linux: longshot_vk.so from 2320b95 in scripts/linux/Dockerfile's Ubuntu 22.04 image with tools.lock's tools, 0 failed; the two touched checks pass there with g++. Host-builds box ticked.

Built on Linux: longshot_vk.so from 2320b95 in scripts/linux/Dockerfile's Ubuntu 22.04 image with tools.lock's tools, 0 failed; the two touched checks pass there with g++. Host-builds box ticked.
i12bp8 changed title from WIP: perf(vk): a 6 GB card mirrors a quarter of its VRAM, 1536 MB, not 512 to perf(vk): a 6 GB card mirrors a quarter of its VRAM, 1536 MB, not 512 2026-11-13 17:02:35 +00:00
HumanBean added this to the v3.0.0 milestone 2026-11-13 17:37:39 +00:00
HumanBean force-pushed perf/low-vram-mirror-budget from 2320b958e4
Some checks failed
PR description / Description follows the template (pull_request_target) Successful in 24s
Website / Website on Cloudflare (pull_request_target) Has been skipped
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been cancelled
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been cancelled
CI / Workflow lint (actionlint) (pull_request) Has been cancelled
Third-party parts / As their notes say (pull_request) Has been cancelled
Third-party parts / The same as upstream (pull_request) Has been cancelled
to b2a011cfa2
All checks were successful
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 5s
CI / Workflow lint (actionlint) (pull_request) Successful in 8s
Third-party parts / As their notes say (pull_request) Successful in 8s
PR description / Description follows the template (pull_request_target) Successful in 3s
2026-11-13 17:40:38 +00:00
Compare
Owner

I rebased this onto main @ fbd42024 to clear the conflicts; it's pushed as b2a011cf. Your two commits are unchanged apart from the rebase. The code applied cleanly. Both conflicts were in docs/tested-configurations.md, where main had added a row at the same place as yours, and I kept every row. No tests were run here. Forgejo now shows it as mergeable.

I rebased this onto main @ fbd42024 to clear the conflicts; it's pushed as b2a011cf. Your two commits are unchanged apart from the rebase. The code applied cleanly. Both conflicts were in `docs/tested-configurations.md`, where main had added a row at the same place as yours, and I kept every row. No tests were run here. Forgejo now shows it as mergeable.
HumanBean left a comment

The change is clean. Auto's rule and the preset cap now live in mirror_budget.h, where they are tested on their own, and mirror_budget() still reads RX_VK_MIRROR_MB first, so the override wins as before. The fps gain on the RTX 3060 Laptop is large and was measured A/B: 20.1 against 42.2 fps, main, PR, main, PR. I checked the other sizes against the code:

  • Unchanged: Auto on cards of 4.5 GB or less (512 MB on 4 GB). Every card of 8 GB or more, on Auto and on every preset: preset_bytes can only lower a preset's value, never raise it. The Steam Deck and iGPUs, which have no mirrors (mirror_on is for discrete cards only), and GCN cards, which have them off. Low on a 4 GB card, capped back to 512. Low on an 8 GB PC with under 12 GB of RAM: preset_caps_memory is false there, so it gets Auto's 2048 as before.
  • Refusal paths: MIRROR_REFUSED is still set for a pool over 1 GB and for a failed allocation. A pool over the budget is still MIRROR_NONE and tried again. mirrors_trim and mirrors_give_back compare against the same mirror_budget(). The PR leaves all of these alone.
  • Checks: test_vram_budget_row is updated, not weakened, and test_mirror_trim's cut text is unchanged. No API or shader changes.

Must change

  1. The Risk section says something the code doesn't do. It says the extra GB is safe because "the existing VRAM pressure rules (mirror trim, the 90% rule) give memory back". Neither one shrinks the mirrors. The 90% rule for mirrors only exists behind RX_VK_MIRROR_TIGHT, which is opt-in, so it is off by default (vk_memory.cpp mirror_make). Mirror trim only cuts away the parts of a mirror that lie outside every game pool, while VRAM is over 80%. Live mirrors stay at their budget whatever the driver allows. What gives memory back is the texture cache: the 90% rule in the texture budget, and the compaction's hold at 85%. Please correct the description. The real trade-off is "mirrors take the extra GB, and textures take the squeeze".
  2. The budget arithmetic isn't measured, and the run was a best case.
    • Estimate for this laptop. On the Low (3.6 GB) budget, the preset's texture budget is min(3600 MB, 75% of the allowance). Assuming the driver allows roughly 5.5 GB of the 5992 MB, that gives 3600 MB of textures plus 1498 MB of mirrors, about 5.1 GB, or about 92% of the allowance. That is before render targets, DLSS, the swapchain and staging. On main it was 3600 + 512, about 4.1 GB. The 90% and 85% lines will therefore cut the texture budget.
    • Why the run hid it. It was taken at 33% render resolution (683x384), which is below what Auto/Low sets (DLSS Performance, or 75% without DLSS). It was also on an Optimus laptop, where the dGPU drives no desktop, so the allowance is about as large as it gets.
    • Why it matters. A desktop RTX 2060, 1660 Super or RX 5600 XT that drives monitors and shares VRAM with a browser has a smaller allowance. That is the situation in #130 (an 8 GB card, 1024 MB of mirrors, the allowance down to about 6 GB, the textures squeezed to a 1260 MB budget, device lost). A 6 GB card with 1.5 GB of fixed mirrors gets close to that situation sooner.
    • What I'm asking for. Please report memory from the runs already done: the VRAM: X MB used of Y allowed ... textures ... (budget ...) status lines, and the texture eviction and re-upload counts, main against PR. Please also add one A/B pair at the tier's own render resolution. AGENTS.md asks a new default to say what it costs where it wasn't measured. Right now the description doesn't say what happens to textures and VRAM use.
  3. "Cards of 4.5 GB or less get exactly what they got before" isn't true for the VRAM budget row. When a player chooses 3.6 GB on the row itself, opts.budget is 0. preset_bytes(…, false, …) then returns 1536 uncapped, and preset_caps_memory() is true when budget is 0. A 4 GB card on that step goes from 512 MB of mirrors to 1536, next to a 3600 MB texture budget that is clamped only to 75% of the allowance. That is past the card. tests/test_mirror_budget.cpp:26 pins this behaviour. Please either cap the row's choice at auto_bytes on cards of 4.5 GB or less, or keep the behaviour on purpose and say so in the description and the Flags table.

Should change

  • More cards get the new size than the Flags table names. Cards without mesh shaders start on the Minimum tier: the GTX 1060 6 GB (the README's minimum card), and the RX 5600 XT (RDNA1, so mirrors are on). preset_caps_memory() is false for a 6 GB card on Minimum, so these get Auto's rule, which goes from 768 to 1536 MB. Their texture budget is Auto's 45%. Please name them under "Helps on / Untested on". They run the compute fallback, so they have the least GPU headroom.
  • AMD and desktop 6 GB evidence is still outstanding. T-NWN5 (6 GB AMD or Intel) and T-9799 (4 GB) are open but can't be claimed without the testing wanted label. For a default that reaches every 6 GB card, a result from one 6 GB AMD card (or one desktop 6 GB card driving a monitor) should come in before merge.
  • The row is now non-monotonic. The 3.6 GB step (Low) mirrors 1536 MB and the 4.8 GB step (Balanced) 1024. Your own logs show Hyrule Field mirroring 1.27 to 1.48 GB, so 8 GB cards on Auto/Balanced (tier 3: RTX 3070, 4060, RX 6600) probably still leave a pool in system memory. This is a follow-up question rather than a blocker, but please say in the description that it is out of scope.
  • Nit, outside the diff. The comment above mirror_on() in vk_memory.cpp still says "RX_VK_MIRROR_MB: their budget (default 3072)".

Not checked: I ran nothing (no build, no tests, no game). Allowance figures are estimates from the code's rules, not from logs.

The change is clean. Auto's rule and the preset cap now live in `mirror_budget.h`, where they are tested on their own, and `mirror_budget()` still reads `RX_VK_MIRROR_MB` first, so the override wins as before. The fps gain on the RTX 3060 Laptop is large and was measured A/B: 20.1 against 42.2 fps, main, PR, main, PR. I checked the other sizes against the code: - **Unchanged:** Auto on cards of 4.5 GB or less (512 MB on 4 GB). Every card of 8 GB or more, on Auto and on every preset: `preset_bytes` can only lower a preset's value, never raise it. The Steam Deck and iGPUs, which have no mirrors (`mirror_on` is for discrete cards only), and GCN cards, which have them off. Low on a 4 GB card, capped back to 512. Low on an 8 GB PC with under 12 GB of RAM: `preset_caps_memory` is false there, so it gets Auto's 2048 as before. - **Refusal paths:** `MIRROR_REFUSED` is still set for a pool over 1 GB and for a failed allocation. A pool over the budget is still `MIRROR_NONE` and tried again. `mirrors_trim` and `mirrors_give_back` compare against the same `mirror_budget()`. The PR leaves all of these alone. - **Checks:** `test_vram_budget_row` is updated, not weakened, and `test_mirror_trim`'s cut text is unchanged. No API or shader changes. ## Must change 1. **The Risk section says something the code doesn't do.** It says the extra GB is safe because "the existing VRAM pressure rules (mirror trim, the 90% rule) give memory back". Neither one shrinks the mirrors. The 90% rule for mirrors only exists behind `RX_VK_MIRROR_TIGHT`, which is opt-in, so it is off by default (`vk_memory.cpp` `mirror_make`). Mirror trim only cuts away the parts of a mirror that lie outside every game pool, while VRAM is over 80%. Live mirrors stay at their budget whatever the driver allows. What gives memory back is the **texture cache**: the 90% rule in the texture budget, and the compaction's hold at 85%. Please correct the description. The real trade-off is "mirrors take the extra GB, and textures take the squeeze". 2. **The budget arithmetic isn't measured, and the run was a best case.** - **Estimate for this laptop.** On the Low (3.6 GB) budget, the preset's texture budget is min(3600 MB, 75% of the allowance). Assuming the driver allows roughly 5.5 GB of the 5992 MB, that gives 3600 MB of textures plus 1498 MB of mirrors, about 5.1 GB, or about 92% of the allowance. That is before render targets, DLSS, the swapchain and staging. On main it was 3600 + 512, about 4.1 GB. The 90% and 85% lines will therefore cut the texture budget. - **Why the run hid it.** It was taken at 33% render resolution (683x384), which is below what Auto/Low sets (DLSS Performance, or 75% without DLSS). It was also on an Optimus laptop, where the dGPU drives no desktop, so the allowance is about as large as it gets. - **Why it matters.** A desktop RTX 2060, 1660 Super or RX 5600 XT that drives monitors and shares VRAM with a browser has a smaller allowance. That is the situation in https://git.magicbean.dev/MagicBeanDev/Longshot/issues/130 (an 8 GB card, 1024 MB of mirrors, the allowance down to about 6 GB, the textures squeezed to a 1260 MB budget, device lost). A 6 GB card with 1.5 GB of fixed mirrors gets close to that situation sooner. - **What I'm asking for.** Please report memory from the runs already done: the `VRAM: X MB used of Y allowed ... textures ... (budget ...)` status lines, and the texture eviction and re-upload counts, main against PR. Please also add one A/B pair at the tier's own render resolution. AGENTS.md asks a new default to say what it costs where it wasn't measured. Right now the description doesn't say what happens to textures and VRAM use. 3. **"Cards of 4.5 GB or less get exactly what they got before" isn't true for the VRAM budget row.** When a player chooses 3.6 GB on the row itself, `opts.budget` is 0. `preset_bytes(…, false, …)` then returns 1536 uncapped, and `preset_caps_memory()` is true when `budget` is 0. A 4 GB card on that step goes from 512 MB of mirrors to 1536, next to a 3600 MB texture budget that is clamped only to 75% of the allowance. That is past the card. `tests/test_mirror_budget.cpp:26` pins this behaviour. Please either cap the row's choice at `auto_bytes` on cards of 4.5 GB or less, or keep the behaviour on purpose and say so in the description and the Flags table. ## Should change - **More cards get the new size than the Flags table names.** Cards without mesh shaders start on the Minimum tier: the GTX 1060 6 GB (the README's minimum card), and the RX 5600 XT (RDNA1, so mirrors are on). `preset_caps_memory()` is false for a 6 GB card on Minimum, so these get Auto's rule, which goes from 768 to 1536 MB. Their texture budget is Auto's 45%. Please name them under "Helps on / Untested on". They run the compute fallback, so they have the least GPU headroom. - **AMD and desktop 6 GB evidence is still outstanding.** T-NWN5 (6 GB AMD or Intel) and T-9799 (4 GB) are open but can't be claimed without the `testing wanted` label. For a default that reaches every 6 GB card, a result from one 6 GB AMD card (or one desktop 6 GB card driving a monitor) should come in before merge. - **The row is now non-monotonic.** The 3.6 GB step (Low) mirrors 1536 MB and the 4.8 GB step (Balanced) 1024. Your own logs show Hyrule Field mirroring 1.27 to 1.48 GB, so 8 GB cards on Auto/Balanced (tier 3: RTX 3070, 4060, RX 6600) probably still leave a pool in system memory. This is a follow-up question rather than a blocker, but please say in the description that it is out of scope. - **Nit, outside the diff.** The comment above `mirror_on()` in `vk_memory.cpp` still says "RX_VK_MIRROR_MB: their budget (default 3072)". Not checked: I ran nothing (no build, no tests, no game). Allowance figures are estimates from the code's rules, not from logs.
@ -0,0 +22,4 @@
/// The mirrors when a Quality preset set the budgets (by_preset): never more than Auto gives the card, so a preset made for a
/// larger card (Low's 1536 MB on a 4 GB one, preset_caps_memory lets Low's budgets through up to 6.75 GB) keeps a small card at
/// Auto's. A budget chosen on the VRAM budget row itself is the player's on any card, as chosen
inline uint64_t preset_bytes(uint64_t asked, bool by_preset, uint64_t vram) {
Owner

by_preset is opts.budget > 0, so a 3.6 GB step chosen on the VRAM budget row itself (budget 0) gets 1536 MB uncapped, on a 4 GB card too: it was 512 there. The description says cards of 4.5 GB or less get exactly what they got before. Either cap the row's choice at auto_bytes(vram) on cards of 4.5 GB or less, or keep this on purpose and say so in the description and the Flags table.

`by_preset` is `opts.budget > 0`, so a 3.6 GB step chosen on the VRAM budget row itself (budget 0) gets 1536 MB uncapped, on a 4 GB card too: it was 512 there. The description says cards of 4.5 GB or less get exactly what they got before. Either cap the row's choice at `auto_bytes(vram)` on cards of 4.5 GB or less, or keep this on purpose and say so in the description and the Flags table.
@ -2486,3 +2483,1 @@
// percent and the frame stuttering)
if (vram && vram <= (6ull << 30) + (512ull << 20)) return vram / 8;
return vram ? std::min<uint64_t>(vram / 4, 3072ull << 20) : (uint64_t)(3072ull << 20);
return mirror_rule::auto_bytes(device_vram_bytes());
Owner

This Auto path also reaches 6 GB cards without mesh shaders. They start on the Minimum tier, where preset_caps_memory() is false: the GTX 1060 6 GB (the README's minimum card) and the RX 5600 XT. These go from 768 to 1536 MB, unmeasured. Please name them in the Flags table.

This Auto path also reaches 6 GB cards without mesh shaders. They start on the Minimum tier, where `preset_caps_memory()` is false: the GTX 1060 6 GB (the README's minimum card) and the RX 5600 XT. These go from 768 to 1536 MB, unmeasured. Please name them in the Flags table.
@ -853,0 +854,4 @@
// PCIe, 30-32 GB of bound ranges every 600 submits in Hyrule Field on an RTX 3060 Laptop (6 GB), the GPU's frame 27 ms. A preset's
// mirrors are never more than Auto's on the card (mirror_budget.h preset_bytes): Low on a 4 GB card, which preset_caps_memory lets
// through, keeps 512
constexpr int kVramMirrorMb[kVramBudgets] = {0, 256, 1536, 1024, 1536, 2048, 2560, 3072};
Owner

Low (1536) now mirrors more than Balanced (1024). Your logs show Hyrule Field mirroring 1.27 to 1.48 GB, so 8 GB cards on Auto/Balanced (tier 3) probably still leave a pool in system memory. Fine as a follow-up, but please say in the description that it is out of scope.

Low (1536) now mirrors more than Balanced (1024). Your logs show Hyrule Field mirroring 1.27 to 1.48 GB, so 8 GB cards on Auto/Balanced (tier 3) probably still leave a pool in system memory. Fine as a follow-up, but please say in the description that it is out of scope.
@ -0,0 +23,4 @@
CHECK(preset_bytes(256 * MB, true, 4 * GB) == 256 * MB); // (Minimum: its own, smaller)
CHECK(preset_bytes(1024 * MB, true, 8 * GB) == 1024 * MB); // (Balanced on an 8 GB card: its own)
CHECK(preset_bytes(3072 * MB, true, 16 * GB) == 3072 * MB); // (Ultra on a 16 GB card)
CHECK(preset_bytes(1536 * MB, false, 4 * GB) == 1536 * MB); // (3.6 GB chosen on the row on a 4 GB card: the player's)
Owner

This pins 1536 MB of mirrors on a 4 GB card, next to a 3600 MB texture budget (clamped only to 75% of the allowance) when the row's 3.6 GB step is picked. If that behaviour changes (see the comment on preset_bytes), update this check to match.

This pins 1536 MB of mirrors on a 4 GB card, next to a 3600 MB texture budget (clamped only to 75% of the allowance) when the row's 3.6 GB step is picked. If that behaviour changes (see the comment on `preset_bytes`), update this check to match.
i12bp8 changed title from perf(vk): a 6 GB card mirrors a quarter of its VRAM, 1536 MB, not 512 to WIP: perf(vk): a 6 GB card mirrors a quarter of its VRAM, 1536 MB, not 512 2026-11-13 17:50:39 +00:00
i12bp8 changed title from WIP: perf(vk): a 6 GB card mirrors a quarter of its VRAM, 1536 MB, not 512 to WIP: perf(vk): a 6 GB card mirrors a quarter of its VRAM, not an eighth or 512 MB 2026-11-13 18:36:53 +00:00
Author
Contributor

Pushed c17b339 and a5ee84d after HumanBean's review: the VRAM budget row is capped at Auto's mirrors on small cards; the Risk section is corrected; new runs on the Low preset's own settings (main, PR, 1024 MB, alternated) with VRAM use. Title no longer says 1536 MB (a 6 GB card gets a quarter as the driver reports it, 1498 MB here). Point-by-point reply follows.

Pushed c17b339 and a5ee84d after HumanBean's review: the VRAM budget row is capped at Auto's mirrors on small cards; the Risk section is corrected; new runs on the Low preset's own settings (main, PR, 1024 MB, alternated) with VRAM use. Title no longer says 1536 MB (a 6 GB card gets a quarter as the driver reports it, 1498 MB here). Point-by-point reply follows.
i12bp8 force-pushed perf/low-vram-mirror-budget from a5ee84d90e
Some checks failed
PR description / Description follows the template (pull_request_target) Successful in 7s
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been cancelled
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been cancelled
CI / Workflow lint (actionlint) (pull_request) Has been cancelled
Third-party parts / As their notes say (pull_request) Has been cancelled
Third-party parts / The same as upstream (pull_request) Has been cancelled
to 38c1a4898b
Some checks failed
Website / Website on Cloudflare (pull_request) Blocked by required conditions
PR description / Description follows the template (pull_request_target) Successful in 9s
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been cancelled
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been cancelled
CI / Workflow lint (actionlint) (pull_request) Has been cancelled
Third-party parts / As their notes say (pull_request) Has been cancelled
Third-party parts / The same as upstream (pull_request) Has been cancelled
2026-11-13 18:40:57 +00:00
Compare
Author
Contributor

Rebased onto main d964886 (one conflict in docs/tested-configurations.md, all rows kept), pushed as 38c1a48. run_all: 451 passed, 10 failed; test_menu_i18n is new and fails on main d964886 itself. The description says why the fbd4202 measurements still hold.

Rebased onto main d964886 (one conflict in docs/tested-configurations.md, all rows kept), pushed as 38c1a48. run_all: 451 passed, 10 failed; test_menu_i18n is new and fails on main d964886 itself. The description says why the fbd4202 measurements still hold.
Author
Contributor

Thanks for the careful review, and for the rebase. Point by point:

Must change

  1. Risk section. You're right, and I've rewritten it. Nothing gives the mirrors' extra GB back when VRAM runs short: mirrors_trim only cuts the parts outside every game pool, and the 90% rule for mirrors is behind the opt-in RX_VK_MIRROR_TIGHT. It is the texture cache (its 90% rule and the compaction's hold at 85%) and the render-target pool that give way. The section now says "mirrors take the extra GB, and textures take the squeeze", names the worst case (a desktop 6 GB card with a small allowance, as in #130), and gives the switches to go back.

  2. Memory, and a run at the tier's own settings. I made new runs on the Low (6GB) preset with its own settings (DLSS render size 1152x648, fixed in every run, no Auto step-down), alternated main fbd4202, this PR c17b339, and this PR at RX_VK_MIRROR_MB=1024, twice each. From the in-field VRAM: lines:

    • main: 4085 of 4998 MB, 82% (max 90%), 17.6 fps, textures 1619 MB
    • PR: 4818 of 5037 MB, 95% (max 98%), 39.2 fps, textures 1560 MB, the render-target pool "VRAM short: given back" 5 times (never on main)
    • 1024 MB: 89% (max 92%), 30.7 fps but uneven (22.4 and 33.1)

    So your estimate was right: the PR takes this card to the edge of its allowance, with about 4% less held in textures. Nothing failed here, but this laptop is a best case (Optimus, it drives no desktop), and the description now says so. 1024 MB is listed as the alternative if you prefer the headroom. It gains less, and unevenly, because the pools are filled first come, first served. I'd keep the quarter, but it's your call: tell me and I'll switch 6 GB cards to 1024.

  3. 4 GB cards and the VRAM budget row. That was a real bug, thanks. c17b339 caps a budget chosen on the row itself at Auto's mirrors on a small card (small_card(), 4.5 GB or less), as a preset's already was. So a 4 GB card on the row's 3.6 GB step stays at 512 MB. tests/test_mirror_budget.cpp now checks that (and a smaller choice, and 6 and 8 GB cards on the row) instead of pinning 1536. One side effect: the row's 4.8 GB-and-up steps on a 4 GB card now also keep 512 (1024-3072 on main). That is said in What changed and Reviewer notes.

Should change

  • More cards. The Flags table now names the GTX 1060 6 GB and the RX 5600 XT on the Minimum tier, where Auto's rule applies (768 to 1536 MB), as untested, along with desktop 6 GB cards driving monitors.
  • AMD and desktop evidence. T-NWN5 (6 GB AMD or Intel) and T-9799 (4 GB) are claimable now that the label is on. Thanks.
  • Non-monotonic row. It's now stated as out of scope in What changed, with 8 GB cards on Auto or Balanced as the follow-up question.
  • Nit. The comment above mirror_on() now points to auto_bytes instead of saying "default 3072".

Also rebased onto main d964886 and pushed as 38c1a48. The title no longer says 1536 MB: a 6 GB card gets a quarter of what the driver reports, 1498 MB here.

Builds and checks:

  • run_all: 451 passed, 10 failed. The failures are the same as on main, plus test_menu_i18n, which fails on main d964886 itself.
  • The renderer builds with llvm-mingw on Windows, and in the Ubuntu 22.04 container on Linux with the small-card cap.
Thanks for the careful review, and for the rebase. Point by point: **Must change** 1. **Risk section.** You're right, and I've rewritten it. Nothing gives the mirrors' extra GB back when VRAM runs short: `mirrors_trim` only cuts the parts outside every game pool, and the 90% rule for mirrors is behind the opt-in `RX_VK_MIRROR_TIGHT`. It is the texture cache (its 90% rule and the compaction's hold at 85%) and the render-target pool that give way. The section now says "mirrors take the extra GB, and textures take the squeeze", names the worst case (a desktop 6 GB card with a small allowance, as in https://git.magicbean.dev/MagicBeanDev/Longshot/issues/130), and gives the switches to go back. 2. **Memory, and a run at the tier's own settings.** I made new runs on the Low (6GB) preset with its own settings (DLSS render size 1152x648, fixed in every run, no Auto step-down), alternated main fbd4202, this PR c17b339, and this PR at `RX_VK_MIRROR_MB=1024`, twice each. From the in-field `VRAM:` lines: - main: 4085 of 4998 MB, 82% (max 90%), 17.6 fps, textures 1619 MB - PR: 4818 of 5037 MB, 95% (max 98%), 39.2 fps, textures 1560 MB, the render-target pool "VRAM short: given back" 5 times (never on main) - 1024 MB: 89% (max 92%), 30.7 fps but uneven (22.4 and 33.1) So your estimate was right: the PR takes this card to the edge of its allowance, with about 4% less held in textures. Nothing failed here, but this laptop is a best case (Optimus, it drives no desktop), and the description now says so. 1024 MB is listed as the alternative if you prefer the headroom. It gains less, and unevenly, because the pools are filled first come, first served. I'd keep the quarter, but it's your call: tell me and I'll switch 6 GB cards to 1024. 3. **4 GB cards and the VRAM budget row.** That was a real bug, thanks. c17b339 caps a budget chosen on the row itself at Auto's mirrors on a small card (`small_card()`, 4.5 GB or less), as a preset's already was. So a 4 GB card on the row's 3.6 GB step stays at 512 MB. `tests/test_mirror_budget.cpp` now checks that (and a smaller choice, and 6 and 8 GB cards on the row) instead of pinning 1536. One side effect: the row's 4.8 GB-and-up steps on a 4 GB card now also keep 512 (1024-3072 on main). That is said in What changed and Reviewer notes. **Should change** - **More cards.** The Flags table now names the GTX 1060 6 GB and the RX 5600 XT on the Minimum tier, where Auto's rule applies (768 to 1536 MB), as untested, along with desktop 6 GB cards driving monitors. - **AMD and desktop evidence.** T-NWN5 (6 GB AMD or Intel) and T-9799 (4 GB) are claimable now that the label is on. Thanks. - **Non-monotonic row.** It's now stated as out of scope in What changed, with 8 GB cards on Auto or Balanced as the follow-up question. - **Nit.** The comment above `mirror_on()` now points to `auto_bytes` instead of saying "default 3072". Also rebased onto main d964886 and pushed as 38c1a48. The title no longer says 1536 MB: a 6 GB card gets a quarter of what the driver reports, 1498 MB here. Builds and checks: - `run_all`: 451 passed, 10 failed. The failures are the same as on main, plus `test_menu_i18n`, which fails on main d964886 itself. - The renderer builds with llvm-mingw on Windows, and in the Ubuntu 22.04 container on Linux with the small-card cap.
i12bp8 changed title from WIP: perf(vk): a 6 GB card mirrors a quarter of its VRAM, not an eighth or 512 MB to perf(vk): a 6 GB card mirrors a quarter of its VRAM, not an eighth or 512 MB 2026-11-13 18:41:33 +00:00
HumanBean left a comment

Re-review at 38c1a48. Code only: I didn't build it, run the tests or run the game.

Earlier points

  1. Risk section: addressed. It now says what the code does: the mirrors keep their budget, and the texture cache and the render-target pool give way. The same note is in mirror_budget.h.
  2. Memory, and a run at the tier's own settings: addressed. The new runs use Low's own DLSS 1152x648, alternate main, PR and 1024 MB, and report VRAM use, textures held, compactions and the render-target pool. The cost is now plain: 82% to 95% of the allowance, 98% at peak, on a best-case laptop. The eviction and re-upload counts I asked for aren't there (question below).
  3. The VRAM budget row on small cards: fixed in code. preset_bytes now caps a row choice at Auto's on a card of 4.5 GB or less. I checked the menu: picking a step on the row sets opts.budget = 0, and a preset sets it to the preset. So 4 GB cards are back to 512 on the 3.6 GB step, and the cards of 8 GB or more still get what they chose on the row. The side effect on the row's 4.8 GB and higher steps on a 4 GB card is stated. test_mirror_budget.cpp checks it now instead of pinning 1536.
  • More cards (GTX 1060 6 GB, RX 5600 XT on Minimum): addressed, named in the Flags table and under Not tested.
  • AMD or desktop 6 GB evidence: not yet. T-NWN5 and T-9799 are claimable now, but nobody has tested either one yet.
  • Non-monotonic row: addressed, stated as out of scope.
  • Nit on mirror_on(): addressed.

New since b2a011cf

  • The rebase is fine. Main's commits since fbd4202 don't touch mirror_budget(), preset_caps_memory(), device_vram_bytes() or kVramMirrorMb, so the reason given for keeping the fbd4202 numbers holds. The Measurements section names main @ and this PR @ hashes, and it says which hardware was measured and what the change costs elsewhere.
  • The checklist boxes hold, as far as I can read them off the diff and the description. The test_menu_i18n failure also happens on main.
  • Question: the quoted VRAM: lines show the textures' budget at 0 MB on both sides (textures 1706 MB (budget 0 MB) on main, 1486 MB (budget 0 MB) on the PR). Is that the commit-limit path (commit limit near lines) on this 16 GB PC? If so, system RAM sets the texture numbers at those moments, not VRAM, and "textures held" is a weak measure of the squeeze. The texture eviction and re-upload counts, main against PR, would settle it.
  • Suggestion for T-NWN5: ask the tester for the vk: VRAM: ... used of ... allowed lines and the render-target pool line too, not only fps and the mirrors. Also accept a desktop NVIDIA 6 GB card that drives a monitor (RTX 2060, 1660 Super). The risk is about memory, so fps alone won't show it.

1024 or a quarter

I'd keep the quarter. The 1024 runs gain less and unevenly (22.4 and 33.1), and the quarter's cost falls on the texture cache, which is built to give way. But 98% at peak on a laptop that drives no desktop is close. If a desktop 6 GB card's run shows device lost, heavy re-uploads or VRAM-full stutter, 6 GB cards should go to 1024.

Verdict

Comment, not approve yet. The code is right and every earlier point is addressed, except the one that needs hardware we don't have. I'll approve once T-NWN5 (or a desktop 6 GB run) comes back clean. If one has come back by then, please answer the question about the 0 MB budget too.

Re-review at 38c1a48. Code only: I didn't build it, run the tests or run the game. ## Earlier points 1. **Risk section:** addressed. It now says what the code does: the mirrors keep their budget, and the texture cache and the render-target pool give way. The same note is in `mirror_budget.h`. 2. **Memory, and a run at the tier's own settings:** addressed. The new runs use Low's own DLSS 1152x648, alternate main, PR and 1024 MB, and report VRAM use, textures held, compactions and the render-target pool. The cost is now plain: 82% to 95% of the allowance, 98% at peak, on a best-case laptop. The eviction and re-upload counts I asked for aren't there (question below). 3. **The VRAM budget row on small cards:** fixed in code. `preset_bytes` now caps a row choice at Auto's on a card of 4.5 GB or less. I checked the menu: picking a step on the row sets `opts.budget = 0`, and a preset sets it to the preset. So 4 GB cards are back to 512 on the 3.6 GB step, and the cards of 8 GB or more still get what they chose on the row. The side effect on the row's 4.8 GB and higher steps on a 4 GB card is stated. `test_mirror_budget.cpp` checks it now instead of pinning 1536. - **More cards (GTX 1060 6 GB, RX 5600 XT on Minimum):** addressed, named in the Flags table and under Not tested. - **AMD or desktop 6 GB evidence:** not yet. T-NWN5 and T-9799 are claimable now, but nobody has tested either one yet. - **Non-monotonic row:** addressed, stated as out of scope. - **Nit on `mirror_on()`:** addressed. ## New since b2a011cf - The rebase is fine. Main's commits since fbd4202 don't touch `mirror_budget()`, `preset_caps_memory()`, `device_vram_bytes()` or `kVramMirrorMb`, so the reason given for keeping the fbd4202 numbers holds. The Measurements section names `main @` and `this PR @` hashes, and it says which hardware was measured and what the change costs elsewhere. - The checklist boxes hold, as far as I can read them off the diff and the description. The `test_menu_i18n` failure also happens on main. - **Question:** the quoted `VRAM:` lines show the textures' budget at 0 MB on both sides (`textures 1706 MB (budget 0 MB)` on main, `1486 MB (budget 0 MB)` on the PR). Is that the commit-limit path (`commit limit near` lines) on this 16 GB PC? If so, system RAM sets the texture numbers at those moments, not VRAM, and "textures held" is a weak measure of the squeeze. The texture eviction and re-upload counts, main against PR, would settle it. - **Suggestion for T-NWN5:** ask the tester for the `vk: VRAM: ... used of ... allowed` lines and the render-target pool line too, not only fps and the mirrors. Also accept a desktop NVIDIA 6 GB card that drives a monitor (RTX 2060, 1660 Super). The risk is about memory, so fps alone won't show it. ## 1024 or a quarter I'd keep the quarter. The 1024 runs gain less and unevenly (22.4 and 33.1), and the quarter's cost falls on the texture cache, which is built to give way. But 98% at peak on a laptop that drives no desktop is close. If a desktop 6 GB card's run shows device lost, heavy re-uploads or VRAM-full stutter, 6 GB cards should go to 1024. ## Verdict Comment, not approve yet. The code is right and every earlier point is addressed, except the one that needs hardware we don't have. I'll approve once T-NWN5 (or a desktop 6 GB run) comes back clean. If one has come back by then, please answer the question about the 0 MB budget too.
@ -0,0 +30,4 @@
/// a small card at Auto's. Chosen on the VRAM budget row itself: the player's, except on a small card, where it is capped at Auto's
/// too (the row's 3.6 GB step on a 4 GB card was 512 MB before Low's mirrors went to 1536; its steps of 4.8 GB and more, past
/// what such a card has, now keep 512 as well)
inline uint64_t preset_bytes(uint64_t asked, bool by_preset, uint64_t vram) {
Owner

Nit: since c17b339 this also caps a budget chosen on the row (by_preset false, small card), so preset_bytes no longer says what it does. Something like budget_bytes would read better. Optional.

Nit: since c17b339 this also caps a budget chosen on the row (`by_preset` false, small card), so `preset_bytes` no longer says what it does. Something like `budget_bytes` would read better. Optional.
Author
Contributor

Pushed 091daa9: preset_bytes renamed budget_bytes (HumanBean's nit). Description: the 0 MB texture budget is the commit-limit path on this 16 GB PC on both sides; eviction counts added. T-NWN5 replaced by T-XFT3, which asks for the VRAM and render-target pool lines and accepts desktop NVIDIA 6 GB cards.

Pushed 091daa9: preset_bytes renamed budget_bytes (HumanBean's nit). Description: the 0 MB texture budget is the commit-limit path on this 16 GB PC on both sides; eviction counts added. T-NWN5 replaced by T-XFT3, which asks for the VRAM and render-target pool lines and accepts desktop NVIDIA 6 GB cards.
refactor(vk): mirror_budget.h's preset_bytes is budget_bytes: it caps a row's choice on a small card too
Some checks failed
PR description / Description follows the template (pull_request_target) Successful in 3s
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been cancelled
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been cancelled
CI / Workflow lint (actionlint) (pull_request) Has been cancelled
Third-party parts / As their notes say (pull_request) Has been cancelled
Third-party parts / The same as upstream (pull_request) Has been cancelled
091daa9505
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Author
Contributor

Thanks for the re-review.

The 0 MB texture budget: yes, it's the commit-limit path, on both sides. Every run, main and PR alike, logged commit limit near on this 16 GB PC (main 7 and 2 times, PR 3 and 4, 1024 MB 2 and 3). The least commit left was 495 MB on main and 575 MB on the PR, and that set the textures' budget to 0 MB at those moments. So, as you say, "textures held" doesn't measure the VRAM squeeze. The description now says that.

Evictions: the per-texture log (RX_VK_LOG_REMADE) was off in these runs, so I only have the always-on counter of evicted textures that were uploaded again from the game's memory:

  • main: 20 or more in run 1 (the counter's first 20 lines), 0 in run 2
  • PR: 0 and 0
  • 1024 MB: 0 and 0

So nothing points to the PR thrashing textures on this card. This laptop is still the best case, though, so the desktop run decides.

T-NWN5: replaced by T-XFT3, with your suggestions. It accepts a desktop NVIDIA 6 GB card that drives a monitor (RTX 2060, 1660 Super/Ti) as well as AMD and Intel. It asks for every VRAM: ... used of ... allowed line, the render-target pool lines, any commit limit near, device lost or VK_ERROR, and a run with RX_VK_LOG_REMADE=1. Its pass criteria are no device lost, no VRAM-full stutter, and VRAM under the allowance.

Nit: done in 091daa9. preset_bytes is now budget_bytes. run_all is unchanged: 451 passed, and the same 10 fail as on main.

Agreed on the fallback: if the desktop 6 GB run shows device lost, heavy re-uploads or VRAM-full stutter, I'll move 6 GB cards to 1024.

Thanks for the re-review. **The 0 MB texture budget:** yes, it's the commit-limit path, on both sides. Every run, main and PR alike, logged `commit limit near` on this 16 GB PC (main 7 and 2 times, PR 3 and 4, 1024 MB 2 and 3). The least commit left was 495 MB on main and 575 MB on the PR, and that set the textures' budget to 0 MB at those moments. So, as you say, "textures held" doesn't measure the VRAM squeeze. The description now says that. **Evictions:** the per-texture log (`RX_VK_LOG_REMADE`) was off in these runs, so I only have the always-on counter of evicted textures that were uploaded again from the game's memory: - main: 20 or more in run 1 (the counter's first 20 lines), 0 in run 2 - PR: 0 and 0 - 1024 MB: 0 and 0 So nothing points to the PR thrashing textures on this card. This laptop is still the best case, though, so the desktop run decides. **T-NWN5:** replaced by T-XFT3, with your suggestions. It accepts a desktop NVIDIA 6 GB card that drives a monitor (RTX 2060, 1660 Super/Ti) as well as AMD and Intel. It asks for every `VRAM: ... used of ... allowed` line, the render-target pool lines, any `commit limit near`, device lost or VK_ERROR, and a run with `RX_VK_LOG_REMADE=1`. Its pass criteria are no device lost, no VRAM-full stutter, and VRAM under the allowance. **Nit:** done in 091daa9. `preset_bytes` is now `budget_bytes`. `run_all` is unchanged: 451 passed, and the same 10 fail as on main. Agreed on the fallback: if the desktop 6 GB run shows device lost, heavy re-uploads or VRAM-full stutter, I'll move 6 GB cards to 1024.
i12bp8 changed title from perf(vk): a 6 GB card mirrors a quarter of its VRAM, not an eighth or 512 MB to WIP: perf(vk): a 6 GB card mirrors a quarter of its VRAM, not an eighth or 512 MB 2026-11-13 21:49:20 +00:00
Author
Contributor

Measured at higher render resolutions on the RTX 3060 Laptop (b956a08 adds the rows): at Docked + Native 2048x1152, main 12.5 fps against this PR's 24 (780 against ~145 frames over 50 ms a run), no errors; at Handheld Native 17 against 36. Two alternatives were tried and dropped: RX_VK_MIRROR_TIGHT=1 (-5 fps) and an adaptive budget that shrinks the mirrors when VRAM stays over 97% (-3 to -4 fps: VRAM sits at 95-99% with any budget, the texture cache fills what is left, and lowering the budget only keeps new pools out). Testers so far: treverflume (RTX 3060 Laptop, Linux) large gain, crashes were system-RAM OOM and also happen without this PR; site (GTX 1660 Ti) 12 to ~25 fps at 1080p, slower at native with 1536 than 1024: not reproduced here, log asked for.

Measured at higher render resolutions on the RTX 3060 Laptop (b956a08 adds the rows): at Docked + Native 2048x1152, main 12.5 fps against this PR's 24 (780 against ~145 frames over 50 ms a run), no errors; at Handheld Native 17 against 36. Two alternatives were tried and dropped: RX_VK_MIRROR_TIGHT=1 (-5 fps) and an adaptive budget that shrinks the mirrors when VRAM stays over 97% (-3 to -4 fps: VRAM sits at 95-99% with any budget, the texture cache fills what is left, and lowering the budget only keeps new pools out). Testers so far: treverflume (RTX 3060 Laptop, Linux) large gain, crashes were system-RAM OOM and also happen without this PR; site (GTX 1660 Ti) 12 to ~25 fps at 1080p, slower at native with 1536 than 1024: not reproduced here, log asked for.
docs(tested): RTX 3060 Laptop at Native render resolution, Handheld and Docked: main, this PR, 1024 MB, the 90% rule, an adaptive budget
Some checks are pending
PR description / Description follows the template (pull_request_target) Successful in 3s
CI / Conventional Commit title (pull_request) Blocked by required conditions
Third-party parts / As their notes say (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Blocked by required conditions
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Blocked by required conditions
CI / Workflow lint (actionlint) (pull_request) Blocked by required conditions
Third-party parts / The same as upstream (pull_request) Blocked by required conditions
Website / Website on Cloudflare (pull_request_target) Has been skipped
b956a08800
Co-Authored-By: Claude Opus 5.5 <[email protected]>
i12bp8 changed title from WIP: perf(vk): a 6 GB card mirrors a quarter of its VRAM, not an eighth or 512 MB to perf(vk): a 6 GB card mirrors a quarter of its VRAM, not an eighth or 512 MB 2026-11-13 22:58:24 +00:00
First-time contributor

T-XFT3 hardware result: pass on an RTX 4050 Laptop 6 GB driving the display, with a VRAM-headroom caveat.

I tested main 514591a against this PR 091daa9 on Windows 11 Home build 26200, i5-13420H, 31.6 GiB RAM, NVIDIA RTX 4050 Laptop GPU (6141 MiB reported, display active), driver 621.14. The gpu/vk tree on 514591a matches the PR base d9648863. I built each branch's Vulkan renderer DLL with the same Windows toolchain and used the same Longshot v3.0.0-rc.3 game/runtime. These are renderer-only builds, not full game builds.

Method: A-B-A-B; four isolated copies of the same Hyrule Field save; Edge open as usual; same 1920x1080 fullscreen Low (6 GB) options (identical SHA-256), 75% internal resolution with the game's upscaler, DLSS and frame generation off, VSync off. Each run had 20 repeats of 3 s forward + 3 s backward over the same short land path, with control overhead making the measured window 154-165 s. I checked screenshots during the route to confirm the character stayed on land. This tests one part of Hyrule Field, not transitions or a long tour of the map.

Run Mean FPS over route Median in-field N presents, X fps Median frame ms P99 frame ms Frames >50 ms / >100 ms Peak mirrors Peak VRAM used / allowed Tightest VRAM headroom
main 1 17.49 17.6 56.52 69.77 2665 / 3 512 MB 3571 / 5152 MB 1581 MB
PR 1 52.43 52.7 18.48 29.89 9 / 0 1480 MB 4602 / 5081 MB 456 MB
main 2 18.77 18.8 52.67 66.64 2501 / 1 512 MB 3581 / 5152 MB 1571 MB
PR 2 53.61 54.3 18.10 28.08 5 / 0 1480 MB 4641 / 5106 MB 440 MB

Median of the two run medians: 18.2 FPS main → 53.5 FPS PR (~2.94×). The 224 MB Field pool repeatedly stayed in system memory on main; the PR mirrored more of the live pools. There were no render target images: ... VRAM short, commit limit near, device-lost or VK_ERROR lines in any run, and the recorded VRAM use stayed below each logged allowance. The PR's smallest recorded headroom was 440 MB, so testing loads and transitions on other 6 GB cards is still useful.

RX_VK_LOG_REMADE=1 was set for all four runs. Each log reached 300 made again ... after it was evicted entries; the source caps that diagnostic at 300 per process, so the true totals are at least 300 and cannot be compared from these logs. There were no sampled EMPTY entries. The minimum physical memory available during the route was 5.5 / 2.2 / 5.2 / 5.2 GiB (main 1 / PR 1 / main 2 / PR 2); the first PR run was a cold-cache run, so I would not attribute that memory difference solely to this patch.

Exact VRAM and pool-budget lines requested by T-XFT3 follow. No game files, logs, caches or saves are attached.

main1: 26 VRAM/pool lines
vk: render target images: 0 reused from the pool, 11 made new, 0 destroyed from it; it holds 0 (0 MB)
vk: VRAM: 1824 MB used of 5152 MB allowed (870 MB of it free in the blocks); textures 134 MB (budget 3600 MB), 108 textures
vk: render target images: 0 reused from the pool, 11 made new, 0 destroyed from it; it holds 0 (0 MB)
vk: VRAM: 1824 MB used of 5152 MB allowed (872 MB of it free in the blocks); textures 136 MB (budget 3600 MB), 125 textures
vk: pool 0x108c000000 (224 MB) stays in system memory: the mirrors have 388 MB of 512
vk: pool 0x108c000000 (224 MB) stays in system memory: the mirrors have 388 MB of 512
vk: 22 pools mirrored in VRAM (388 MB)
vk: pool 0x108c000000 (224 MB) stays in system memory: the mirrors have 404 MB of 512
vk: pool 0x108c000000 (224 MB) stays in system memory: the mirrors have 416 MB of 512
vk: pool 0x108c000000 (224 MB) stays in system memory: the mirrors have 416 MB of 512
vk: pool 0x108c000000 (224 MB) stays in system memory: the mirrors have 416 MB of 512
vk: pool 0x117d000000 (64 MB) stays in system memory: the mirrors have 480 MB of 512
vk: pool 0x117d000000 (76 MB) stays in system memory: the mirrors have 480 MB of 512
vk: 29 pools mirrored in VRAM (512 MB)
vk: 29 pools mirrored in VRAM (480 MB)
vk: 31 pools mirrored in VRAM (512 MB)
vk: render target images: 260 reused from the pool, 296 made new, 84 destroyed from it; it holds 32 (38 MB)
vk: VRAM: 3441 MB used of 5152 MB allowed (286 MB of it free in the blocks); textures 1662 MB (budget 3600 MB), 1068 textures
vk: render target images: 1414 reused from the pool, 455 made new, 242 destroyed from it; it holds 112 (166 MB)
vk: VRAM: 3571 MB used of 5152 MB allowed (408 MB of it free in the blocks); textures 1540 MB (budget 3600 MB), 992 textures
vk: render target images: 2624 reused from the pool, 631 made new, 396 destroyed from it; it holds 80 (113 MB)
vk: VRAM: 3571 MB used of 5152 MB allowed (376 MB of it free in the blocks); textures 1622 MB (budget 3600 MB), 1046 textures
vk: render target images: 3804 reused from the pool, 795 made new, 588 destroyed from it; it holds 103 (154 MB)
vk: VRAM: 3545 MB used of 5152 MB allowed (409 MB of it free in the blocks); textures 1548 MB (budget 3600 MB), 999 textures
vk: render target images: 4993 reused from the pool, 975 made new, 747 destroyed from it; it holds 61 (92 MB)
vk: VRAM: 3545 MB used of 5152 MB allowed (382 MB of it free in the blocks); textures 1636 MB (budget 3600 MB), 1066 textures
pr1: 47 VRAM/pool lines
vk: render target images: 0 reused from the pool, 7 made new, 0 destroyed from it; it holds 0 (0 MB)
vk: VRAM: 1787 MB used of 5152 MB allowed (929 MB of it free in the blocks); textures 95 MB (budget 3600 MB), 58 textures
vk: render target images: 0 reused from the pool, 11 made new, 0 destroyed from it; it holds 0 (0 MB)
vk: VRAM: 1819 MB used of 5152 MB allowed (872 MB of it free in the blocks); textures 136 MB (budget 3600 MB), 121 textures
vk: pool 0x7fc0000000 (32 MB) stays in system memory: the mirrors have 1456 MB of 1480
vk: pool 0x7fc0000000 (32 MB) stays in system memory: the mirrors have 1476 MB of 1480
vk: 47 pools mirrored in VRAM (1476 MB)
vk: pool 0x7fc0000000 (32 MB) stays in system memory: the mirrors have 1476 MB of 1480
vk: pool 0x7fde800000 (8 MB) stays in system memory: the mirrors have 1476 MB of 1480
vk: pool 0x7fc0000000 (32 MB) stays in system memory: the mirrors have 1476 MB of 1480
vk: pool 0x7fde800000 (8 MB) stays in system memory: the mirrors have 1476 MB of 1480
vk: pool 0x7fc0000000 (32 MB) stays in system memory: the mirrors have 1476 MB of 1480
vk: pool 0x7fb3800000 (8 MB) stays in system memory: the mirrors have 1476 MB of 1480
vk: 49 pools mirrored in VRAM (1212 MB)
vk: 61 pools mirrored in VRAM (1480 MB)
vk: render target images: 5 reused from the pool, 180 made new, 40 destroyed from it; it holds 30 (47 MB)
vk: VRAM: 4466 MB used of 5064 MB allowed (313 MB of it free in the blocks); textures 1574 MB (budget 1218 MB), 939 textures
vk: render target images: 5 reused from the pool, 180 made new, 82 destroyed from it; it holds 0 (0 MB)
vk: VRAM: 4466 MB used of 5047 MB allowed (537 MB of it free in the blocks); textures 1433 MB (budget 1189 MB), 805 textures
vk: render target images: 439 reused from the pool, 350 made new, 168 destroyed from it; it holds 78 (124 MB)
vk: VRAM: 4600 MB used of 5081 MB allowed (510 MB of it free in the blocks); textures 1456 MB (budget 1086 MB), 846 textures
vk: render target images: 948 reused from the pool, 605 made new, 395 destroyed from it; it holds 43 (59 MB)
vk: VRAM: 4472 MB used of 5047 MB allowed (372 MB of it free in the blocks); textures 1530 MB (budget 1280 MB), 902 textures
vk: render target images: 1892 reused from the pool, 815 made new, 585 destroyed from it; it holds 88 (129 MB)
vk: VRAM: 4600 MB used of 5069 MB allowed (447 MB of it free in the blocks); textures 1502 MB (budget 1138 MB), 881 textures
vk: render target images: 2411 reused from the pool, 972 made new, 867 destroyed from it; it holds 0 (0 MB)
vk: VRAM: 4472 MB used of 5073 MB allowed (512 MB of it free in the blocks); textures 1438 MB (budget 1208 MB), 841 textures
vk: render target images: 3112 reused from the pool, 1246 made new, 1032 destroyed from it; it holds 50 (70 MB)
vk: VRAM: 4472 MB used of 5047 MB allowed (348 MB of it free in the blocks); textures 1532 MB (budget 1284 MB), 894 textures
vk: render target images: 3859 reused from the pool, 1417 made new, 1215 destroyed from it; it holds 101 (162 MB)
vk: VRAM: 4600 MB used of 5073 MB allowed (483 MB of it free in the blocks); textures 1433 MB (budget 1077 MB), 832 textures
vk: render target images: 4342 reused from the pool, 1582 made new, 1475 destroyed from it; it holds 0 (0 MB)
vk: VRAM: 4472 MB used of 5081 MB allowed (502 MB of it free in the blocks); textures 1449 MB (budget 1225 MB), 843 textures
vk: render target images: 5140 reused from the pool, 1852 made new, 1628 destroyed from it; it holds 90 (134 MB)
vk: VRAM: 4600 MB used of 5056 MB allowed (453 MB of it free in the blocks); textures 1491 MB (budget 1119 MB), 867 textures
vk: render target images: 5772 reused from the pool, 2033 made new, 1886 destroyed from it; it holds 50 (77 MB)
vk: VRAM: 4472 MB used of 5073 MB allowed (441 MB of it free in the blocks); textures 1431 MB (budget 1202 MB), 829 textures
vk: render target images: 6364 reused from the pool, 2287 made new, 2096 destroyed from it; it holds 12 (14 MB)
vk: VRAM: 4472 MB used of 5047 MB allowed (373 MB of it free in the blocks); textures 1555 MB (budget 1305 MB), 915 textures
vk: render target images: 7157 reused from the pool, 2507 made new, 2293 destroyed from it; it holds 113 (180 MB)
vk: VRAM: 4602 MB used of 5081 MB allowed (455 MB of it free in the blocks); textures 1435 MB (budget 1081 MB), 835 textures
vk: render target images: 7641 reused from the pool, 2668 made new, 2559 destroyed from it; it holds 0 (0 MB)
vk: VRAM: 4474 MB used of 5035 MB allowed (479 MB of it free in the blocks); textures 1452 MB (budget 1188 MB), 848 textures
vk: render target images: 8470 reused from the pool, 2955 made new, 2723 destroyed from it; it holds 64 (93 MB)
vk: VRAM: 4602 MB used of 5081 MB allowed (420 MB of it free in the blocks); textures 1544 MB (budget 1191 MB), 903 textures
vk: render target images: 9069 reused from the pool, 3126 made new, 2959 destroyed from it; it holds 69 (107 MB)
vk: VRAM: 4602 MB used of 5060 MB allowed (519 MB of it free in the blocks); textures 1431 MB (budget 1062 MB), 832 textures
main2: 28 VRAM/pool lines
vk: render target images: 0 reused from the pool, 7 made new, 0 destroyed from it; it holds 0 (0 MB)
vk: VRAM: 1824 MB used of 5152 MB allowed (929 MB of it free in the blocks); textures 95 MB (budget 3600 MB), 60 textures
vk: render target images: 0 reused from the pool, 11 made new, 0 destroyed from it; it holds 0 (0 MB)
vk: VRAM: 1824 MB used of 5152 MB allowed (872 MB of it free in the blocks); textures 136 MB (budget 3600 MB), 121 textures
vk: pool 0x108c000000 (224 MB) stays in system memory: the mirrors have 400 MB of 512
vk: pool 0x108c000000 (224 MB) stays in system memory: the mirrors have 400 MB of 512
vk: 24 pools mirrored in VRAM (400 MB)
vk: pool 0x108c000000 (224 MB) stays in system memory: the mirrors have 416 MB of 512
vk: pool 0x108c000000 (224 MB) stays in system memory: the mirrors have 428 MB of 512
vk: pool 0x108c000000 (224 MB) stays in system memory: the mirrors have 428 MB of 512
vk: pool 0x108c000000 (224 MB) stays in system memory: the mirrors have 428 MB of 512
vk: pool 0x117d000000 (64 MB) stays in system memory: the mirrors have 492 MB of 512
vk: pool 0x117d000000 (76 MB) stays in system memory: the mirrors have 492 MB of 512
vk: 29 pools mirrored in VRAM (512 MB)
vk: 29 pools mirrored in VRAM (480 MB)
vk: 31 pools mirrored in VRAM (512 MB)
vk: render target images: 16 reused from the pool, 169 made new, 34 destroyed from it; it holds 37 (59 MB)
vk: VRAM: 3447 MB used of 5152 MB allowed (362 MB of it free in the blocks); textures 1564 MB (budget 3600 MB), 961 textures
vk: render target images: 770 reused from the pool, 364 made new, 146 destroyed from it; it holds 115 (172 MB)
vk: VRAM: 3451 MB used of 5152 MB allowed (232 MB of it free in the blocks); textures 1581 MB (budget 3600 MB), 997 textures
vk: render target images: 1879 reused from the pool, 546 made new, 310 destroyed from it; it holds 131 (198 MB)
vk: VRAM: 3581 MB used of 5152 MB allowed (333 MB of it free in the blocks); textures 1583 MB (budget 3600 MB), 999 textures
vk: render target images: 2980 reused from the pool, 724 made new, 501 destroyed from it; it holds 117 (177 MB)
vk: VRAM: 3553 MB used of 5152 MB allowed (345 MB of it free in the blocks); textures 1589 MB (budget 3600 MB), 1000 textures
vk: render target images: 4127 reused from the pool, 895 made new, 677 destroyed from it; it holds 67 (108 MB)
vk: VRAM: 3553 MB used of 5152 MB allowed (351 MB of it free in the blocks); textures 1651 MB (budget 3600 MB), 1045 textures
vk: render target images: 5369 reused from the pool, 1082 made new, 850 destroyed from it; it holds 59 (87 MB)
vk: VRAM: 3553 MB used of 5152 MB allowed (333 MB of it free in the blocks); textures 1690 MB (budget 3600 MB), 1070 textures
pr2: 47 VRAM/pool lines
vk: render target images: 0 reused from the pool, 11 made new, 0 destroyed from it; it holds 0 (0 MB)
vk: VRAM: 1824 MB used of 5152 MB allowed (870 MB of it free in the blocks); textures 134 MB (budget 3600 MB), 108 textures
vk: render target images: 0 reused from the pool, 11 made new, 0 destroyed from it; it holds 0 (0 MB)
vk: VRAM: 1824 MB used of 5152 MB allowed (876 MB of it free in the blocks); textures 132 MB (budget 3600 MB), 124 textures
vk: pool 0x102fc00000 (52 MB) stays in system memory: the mirrors have 1468 MB of 1480
vk: pool 0x100dc00000 (20 MB) stays in system memory: the mirrors have 1468 MB of 1480
vk: pool 0x7ffe000000 (24 MB) stays in system memory: the mirrors have 1468 MB of 1480
vk: pool 0x102fc00000 (52 MB) stays in system memory: the mirrors have 1468 MB of 1480
vk: pool 0x100dc00000 (20 MB) stays in system memory: the mirrors have 1468 MB of 1480
vk: pool 0x7ffe000000 (24 MB) stays in system memory: the mirrors have 1468 MB of 1480
vk: 56 pools mirrored in VRAM (1468 MB)
vk: pool 0x102fc00000 (52 MB) stays in system memory: the mirrors have 1468 MB of 1480
vk: 57 pools mirrored in VRAM (1276 MB)
vk: pool 0x7f52400000 (8 MB) stays in system memory: the mirrors have 1476 MB of 1480
vk: 68 pools mirrored in VRAM (1480 MB)
vk: render target images: 7 reused from the pool, 200 made new, 96 destroyed from it; it holds 0 (0 MB)
vk: VRAM: 4467 MB used of 5070 MB allowed (457 MB of it free in the blocks); textures 1529 MB (budget 1207 MB), 896 textures
vk: render target images: 15 reused from the pool, 223 made new, 120 destroyed from it; it holds 0 (0 MB)
vk: VRAM: 4467 MB used of 5047 MB allowed (544 MB of it free in the blocks); textures 1442 MB (budget 1190 MB), 818 textures
vk: render target images: 643 reused from the pool, 484 made new, 265 destroyed from it; it holds 58 (79 MB)
vk: VRAM: 4601 MB used of 5073 MB allowed (488 MB of it free in the blocks); textures 1546 MB (budget 1168 MB), 900 textures
vk: render target images: 1338 reused from the pool, 708 made new, 492 destroyed from it; it holds 115 (180 MB)
vk: VRAM: 4601 MB used of 5044 MB allowed (495 MB of it free in the blocks); textures 1435 MB (budget 1052 MB), 836 textures
vk: render target images: 1915 reused from the pool, 884 made new, 770 destroyed from it; it holds 0 (0 MB)
vk: VRAM: 4473 MB used of 5073 MB allowed (530 MB of it free in the blocks); textures 1452 MB (budget 1219 MB), 850 textures
vk: render target images: 2787 reused from the pool, 1172 made new, 933 destroyed from it; it holds 60 (81 MB)
vk: VRAM: 4601 MB used of 5047 MB allowed (463 MB of it free in the blocks); textures 1566 MB (budget 1184 MB), 915 textures
vk: render target images: 3419 reused from the pool, 1356 made new, 1175 destroyed from it; it holds 82 (131 MB)
vk: VRAM: 4601 MB used of 5090 MB allowed (539 MB of it free in the blocks); textures 1436 MB (budget 1089 MB), 835 textures
vk: render target images: 4007 reused from the pool, 1607 made new, 1408 destroyed from it; it holds 16 (18 MB)
vk: VRAM: 4601 MB used of 5073 MB allowed (528 MB of it free in the blocks); textures 1559 MB (budget 1200 MB), 916 textures
vk: render target images: 4884 reused from the pool, 1817 made new, 1593 destroyed from it; it holds 83 (121 MB)
vk: VRAM: 4601 MB used of 5061 MB allowed (484 MB of it free in the blocks); textures 1502 MB (budget 1132 MB), 874 textures
vk: render target images: 5411 reused from the pool, 1975 made new, 1878 destroyed from it; it holds 0 (0 MB)
vk: VRAM: 4473 MB used of 5063 MB allowed (550 MB of it free in the blocks); textures 1432 MB (budget 1195 MB), 825 textures
vk: render target images: 6173 reused from the pool, 2251 made new, 2021 destroyed from it; it holds 63 (84 MB)
vk: VRAM: 4641 MB used of 5106 MB allowed (477 MB of it free in the blocks); textures 1544 MB (budget 1171 MB), 901 textures
vk: render target images: 6895 reused from the pool, 2445 made new, 2222 destroyed from it; it holds 122 (198 MB)
vk: VRAM: 4641 MB used of 5081 MB allowed (474 MB of it free in the blocks); textures 1434 MB (budget 1042 MB), 832 textures
vk: render target images: 7392 reused from the pool, 2599 made new, 2493 destroyed from it; it holds 0 (0 MB)
vk: VRAM: 4513 MB used of 5052 MB allowed (529 MB of it free in the blocks); textures 1450 MB (budget 1160 MB), 842 textures
vk: render target images: 8113 reused from the pool, 2872 made new, 2655 destroyed from it; it holds 61 (88 MB)
vk: VRAM: 4513 MB used of 5073 MB allowed (351 MB of it free in the blocks); textures 1526 MB (budget 1254 MB), 892 textures
vk: render target images: 8758 reused from the pool, 3075 made new, 2875 destroyed from it; it holds 99 (157 MB)
vk: VRAM: 4641 MB used of 5081 MB allowed (500 MB of it free in the blocks); textures 1437 MB (budget 1042 MB), 840 textures
vk: render target images: 9330 reused from the pool, 3299 made new, 3129 destroyed from it; it holds 20 (28 MB)
vk: VRAM: 4513 MB used of 5063 MB allowed (431 MB of it free in the blocks); textures 1506 MB (budget 1225 MB), 889 textures
T-XFT3 hardware result: **pass on an RTX 4050 Laptop 6 GB driving the display**, with a VRAM-headroom caveat. I tested main `514591a` against this PR `091daa9` on Windows 11 Home build 26200, i5-13420H, 31.6 GiB RAM, NVIDIA RTX 4050 Laptop GPU (6141 MiB reported, display active), driver 621.14. The `gpu/vk` tree on `514591a` matches the PR base `d9648863`. I built each branch's Vulkan renderer DLL with the same Windows toolchain and used the same Longshot v3.0.0-rc.3 game/runtime. These are renderer-only builds, not full game builds. Method: A-B-A-B; four isolated copies of the same Hyrule Field save; Edge open as usual; same 1920x1080 fullscreen **Low (6 GB)** options (identical SHA-256), 75% internal resolution with the game's upscaler, DLSS and frame generation off, VSync off. Each run had 20 repeats of 3 s forward + 3 s backward over the same short land path, with control overhead making the measured window 154-165 s. I checked screenshots during the route to confirm the character stayed on land. This tests one part of Hyrule Field, not transitions or a long tour of the map. | Run | Mean FPS over route | Median in-field `N presents, X fps` | Median frame ms | P99 frame ms | Frames >50 ms / >100 ms | Peak mirrors | Peak VRAM used / allowed | Tightest VRAM headroom | | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | | main 1 | 17.49 | 17.6 | 56.52 | 69.77 | 2665 / 3 | 512 MB | 3571 / 5152 MB | 1581 MB | | PR 1 | 52.43 | 52.7 | 18.48 | 29.89 | 9 / 0 | 1480 MB | 4602 / 5081 MB | 456 MB | | main 2 | 18.77 | 18.8 | 52.67 | 66.64 | 2501 / 1 | 512 MB | 3581 / 5152 MB | 1571 MB | | PR 2 | 53.61 | 54.3 | 18.10 | 28.08 | 5 / 0 | 1480 MB | 4641 / 5106 MB | 440 MB | Median of the two run medians: **18.2 FPS main → 53.5 FPS PR** (~2.94×). The 224 MB Field pool repeatedly stayed in system memory on main; the PR mirrored more of the live pools. There were **no** `render target images: ... VRAM short`, `commit limit near`, device-lost or `VK_ERROR` lines in any run, and the recorded VRAM use stayed below each logged allowance. The PR's smallest recorded headroom was 440 MB, so testing loads and transitions on other 6 GB cards is still useful. `RX_VK_LOG_REMADE=1` was set for all four runs. Each log reached **300** `made again ... after it was evicted` entries; the source caps that diagnostic at 300 per process, so the true totals are **at least 300** and cannot be compared from these logs. There were no `sampled EMPTY` entries. The minimum physical memory available during the route was 5.5 / 2.2 / 5.2 / 5.2 GiB (main 1 / PR 1 / main 2 / PR 2); the first PR run was a cold-cache run, so I would not attribute that memory difference solely to this patch. Exact VRAM and pool-budget lines requested by T-XFT3 follow. No game files, logs, caches or saves are attached. <details><summary>main1: 26 VRAM/pool lines</summary> ```text vk: render target images: 0 reused from the pool, 11 made new, 0 destroyed from it; it holds 0 (0 MB) vk: VRAM: 1824 MB used of 5152 MB allowed (870 MB of it free in the blocks); textures 134 MB (budget 3600 MB), 108 textures vk: render target images: 0 reused from the pool, 11 made new, 0 destroyed from it; it holds 0 (0 MB) vk: VRAM: 1824 MB used of 5152 MB allowed (872 MB of it free in the blocks); textures 136 MB (budget 3600 MB), 125 textures vk: pool 0x108c000000 (224 MB) stays in system memory: the mirrors have 388 MB of 512 vk: pool 0x108c000000 (224 MB) stays in system memory: the mirrors have 388 MB of 512 vk: 22 pools mirrored in VRAM (388 MB) vk: pool 0x108c000000 (224 MB) stays in system memory: the mirrors have 404 MB of 512 vk: pool 0x108c000000 (224 MB) stays in system memory: the mirrors have 416 MB of 512 vk: pool 0x108c000000 (224 MB) stays in system memory: the mirrors have 416 MB of 512 vk: pool 0x108c000000 (224 MB) stays in system memory: the mirrors have 416 MB of 512 vk: pool 0x117d000000 (64 MB) stays in system memory: the mirrors have 480 MB of 512 vk: pool 0x117d000000 (76 MB) stays in system memory: the mirrors have 480 MB of 512 vk: 29 pools mirrored in VRAM (512 MB) vk: 29 pools mirrored in VRAM (480 MB) vk: 31 pools mirrored in VRAM (512 MB) vk: render target images: 260 reused from the pool, 296 made new, 84 destroyed from it; it holds 32 (38 MB) vk: VRAM: 3441 MB used of 5152 MB allowed (286 MB of it free in the blocks); textures 1662 MB (budget 3600 MB), 1068 textures vk: render target images: 1414 reused from the pool, 455 made new, 242 destroyed from it; it holds 112 (166 MB) vk: VRAM: 3571 MB used of 5152 MB allowed (408 MB of it free in the blocks); textures 1540 MB (budget 3600 MB), 992 textures vk: render target images: 2624 reused from the pool, 631 made new, 396 destroyed from it; it holds 80 (113 MB) vk: VRAM: 3571 MB used of 5152 MB allowed (376 MB of it free in the blocks); textures 1622 MB (budget 3600 MB), 1046 textures vk: render target images: 3804 reused from the pool, 795 made new, 588 destroyed from it; it holds 103 (154 MB) vk: VRAM: 3545 MB used of 5152 MB allowed (409 MB of it free in the blocks); textures 1548 MB (budget 3600 MB), 999 textures vk: render target images: 4993 reused from the pool, 975 made new, 747 destroyed from it; it holds 61 (92 MB) vk: VRAM: 3545 MB used of 5152 MB allowed (382 MB of it free in the blocks); textures 1636 MB (budget 3600 MB), 1066 textures ``` </details> <details><summary>pr1: 47 VRAM/pool lines</summary> ```text vk: render target images: 0 reused from the pool, 7 made new, 0 destroyed from it; it holds 0 (0 MB) vk: VRAM: 1787 MB used of 5152 MB allowed (929 MB of it free in the blocks); textures 95 MB (budget 3600 MB), 58 textures vk: render target images: 0 reused from the pool, 11 made new, 0 destroyed from it; it holds 0 (0 MB) vk: VRAM: 1819 MB used of 5152 MB allowed (872 MB of it free in the blocks); textures 136 MB (budget 3600 MB), 121 textures vk: pool 0x7fc0000000 (32 MB) stays in system memory: the mirrors have 1456 MB of 1480 vk: pool 0x7fc0000000 (32 MB) stays in system memory: the mirrors have 1476 MB of 1480 vk: 47 pools mirrored in VRAM (1476 MB) vk: pool 0x7fc0000000 (32 MB) stays in system memory: the mirrors have 1476 MB of 1480 vk: pool 0x7fde800000 (8 MB) stays in system memory: the mirrors have 1476 MB of 1480 vk: pool 0x7fc0000000 (32 MB) stays in system memory: the mirrors have 1476 MB of 1480 vk: pool 0x7fde800000 (8 MB) stays in system memory: the mirrors have 1476 MB of 1480 vk: pool 0x7fc0000000 (32 MB) stays in system memory: the mirrors have 1476 MB of 1480 vk: pool 0x7fb3800000 (8 MB) stays in system memory: the mirrors have 1476 MB of 1480 vk: 49 pools mirrored in VRAM (1212 MB) vk: 61 pools mirrored in VRAM (1480 MB) vk: render target images: 5 reused from the pool, 180 made new, 40 destroyed from it; it holds 30 (47 MB) vk: VRAM: 4466 MB used of 5064 MB allowed (313 MB of it free in the blocks); textures 1574 MB (budget 1218 MB), 939 textures vk: render target images: 5 reused from the pool, 180 made new, 82 destroyed from it; it holds 0 (0 MB) vk: VRAM: 4466 MB used of 5047 MB allowed (537 MB of it free in the blocks); textures 1433 MB (budget 1189 MB), 805 textures vk: render target images: 439 reused from the pool, 350 made new, 168 destroyed from it; it holds 78 (124 MB) vk: VRAM: 4600 MB used of 5081 MB allowed (510 MB of it free in the blocks); textures 1456 MB (budget 1086 MB), 846 textures vk: render target images: 948 reused from the pool, 605 made new, 395 destroyed from it; it holds 43 (59 MB) vk: VRAM: 4472 MB used of 5047 MB allowed (372 MB of it free in the blocks); textures 1530 MB (budget 1280 MB), 902 textures vk: render target images: 1892 reused from the pool, 815 made new, 585 destroyed from it; it holds 88 (129 MB) vk: VRAM: 4600 MB used of 5069 MB allowed (447 MB of it free in the blocks); textures 1502 MB (budget 1138 MB), 881 textures vk: render target images: 2411 reused from the pool, 972 made new, 867 destroyed from it; it holds 0 (0 MB) vk: VRAM: 4472 MB used of 5073 MB allowed (512 MB of it free in the blocks); textures 1438 MB (budget 1208 MB), 841 textures vk: render target images: 3112 reused from the pool, 1246 made new, 1032 destroyed from it; it holds 50 (70 MB) vk: VRAM: 4472 MB used of 5047 MB allowed (348 MB of it free in the blocks); textures 1532 MB (budget 1284 MB), 894 textures vk: render target images: 3859 reused from the pool, 1417 made new, 1215 destroyed from it; it holds 101 (162 MB) vk: VRAM: 4600 MB used of 5073 MB allowed (483 MB of it free in the blocks); textures 1433 MB (budget 1077 MB), 832 textures vk: render target images: 4342 reused from the pool, 1582 made new, 1475 destroyed from it; it holds 0 (0 MB) vk: VRAM: 4472 MB used of 5081 MB allowed (502 MB of it free in the blocks); textures 1449 MB (budget 1225 MB), 843 textures vk: render target images: 5140 reused from the pool, 1852 made new, 1628 destroyed from it; it holds 90 (134 MB) vk: VRAM: 4600 MB used of 5056 MB allowed (453 MB of it free in the blocks); textures 1491 MB (budget 1119 MB), 867 textures vk: render target images: 5772 reused from the pool, 2033 made new, 1886 destroyed from it; it holds 50 (77 MB) vk: VRAM: 4472 MB used of 5073 MB allowed (441 MB of it free in the blocks); textures 1431 MB (budget 1202 MB), 829 textures vk: render target images: 6364 reused from the pool, 2287 made new, 2096 destroyed from it; it holds 12 (14 MB) vk: VRAM: 4472 MB used of 5047 MB allowed (373 MB of it free in the blocks); textures 1555 MB (budget 1305 MB), 915 textures vk: render target images: 7157 reused from the pool, 2507 made new, 2293 destroyed from it; it holds 113 (180 MB) vk: VRAM: 4602 MB used of 5081 MB allowed (455 MB of it free in the blocks); textures 1435 MB (budget 1081 MB), 835 textures vk: render target images: 7641 reused from the pool, 2668 made new, 2559 destroyed from it; it holds 0 (0 MB) vk: VRAM: 4474 MB used of 5035 MB allowed (479 MB of it free in the blocks); textures 1452 MB (budget 1188 MB), 848 textures vk: render target images: 8470 reused from the pool, 2955 made new, 2723 destroyed from it; it holds 64 (93 MB) vk: VRAM: 4602 MB used of 5081 MB allowed (420 MB of it free in the blocks); textures 1544 MB (budget 1191 MB), 903 textures vk: render target images: 9069 reused from the pool, 3126 made new, 2959 destroyed from it; it holds 69 (107 MB) vk: VRAM: 4602 MB used of 5060 MB allowed (519 MB of it free in the blocks); textures 1431 MB (budget 1062 MB), 832 textures ``` </details> <details><summary>main2: 28 VRAM/pool lines</summary> ```text vk: render target images: 0 reused from the pool, 7 made new, 0 destroyed from it; it holds 0 (0 MB) vk: VRAM: 1824 MB used of 5152 MB allowed (929 MB of it free in the blocks); textures 95 MB (budget 3600 MB), 60 textures vk: render target images: 0 reused from the pool, 11 made new, 0 destroyed from it; it holds 0 (0 MB) vk: VRAM: 1824 MB used of 5152 MB allowed (872 MB of it free in the blocks); textures 136 MB (budget 3600 MB), 121 textures vk: pool 0x108c000000 (224 MB) stays in system memory: the mirrors have 400 MB of 512 vk: pool 0x108c000000 (224 MB) stays in system memory: the mirrors have 400 MB of 512 vk: 24 pools mirrored in VRAM (400 MB) vk: pool 0x108c000000 (224 MB) stays in system memory: the mirrors have 416 MB of 512 vk: pool 0x108c000000 (224 MB) stays in system memory: the mirrors have 428 MB of 512 vk: pool 0x108c000000 (224 MB) stays in system memory: the mirrors have 428 MB of 512 vk: pool 0x108c000000 (224 MB) stays in system memory: the mirrors have 428 MB of 512 vk: pool 0x117d000000 (64 MB) stays in system memory: the mirrors have 492 MB of 512 vk: pool 0x117d000000 (76 MB) stays in system memory: the mirrors have 492 MB of 512 vk: 29 pools mirrored in VRAM (512 MB) vk: 29 pools mirrored in VRAM (480 MB) vk: 31 pools mirrored in VRAM (512 MB) vk: render target images: 16 reused from the pool, 169 made new, 34 destroyed from it; it holds 37 (59 MB) vk: VRAM: 3447 MB used of 5152 MB allowed (362 MB of it free in the blocks); textures 1564 MB (budget 3600 MB), 961 textures vk: render target images: 770 reused from the pool, 364 made new, 146 destroyed from it; it holds 115 (172 MB) vk: VRAM: 3451 MB used of 5152 MB allowed (232 MB of it free in the blocks); textures 1581 MB (budget 3600 MB), 997 textures vk: render target images: 1879 reused from the pool, 546 made new, 310 destroyed from it; it holds 131 (198 MB) vk: VRAM: 3581 MB used of 5152 MB allowed (333 MB of it free in the blocks); textures 1583 MB (budget 3600 MB), 999 textures vk: render target images: 2980 reused from the pool, 724 made new, 501 destroyed from it; it holds 117 (177 MB) vk: VRAM: 3553 MB used of 5152 MB allowed (345 MB of it free in the blocks); textures 1589 MB (budget 3600 MB), 1000 textures vk: render target images: 4127 reused from the pool, 895 made new, 677 destroyed from it; it holds 67 (108 MB) vk: VRAM: 3553 MB used of 5152 MB allowed (351 MB of it free in the blocks); textures 1651 MB (budget 3600 MB), 1045 textures vk: render target images: 5369 reused from the pool, 1082 made new, 850 destroyed from it; it holds 59 (87 MB) vk: VRAM: 3553 MB used of 5152 MB allowed (333 MB of it free in the blocks); textures 1690 MB (budget 3600 MB), 1070 textures ``` </details> <details><summary>pr2: 47 VRAM/pool lines</summary> ```text vk: render target images: 0 reused from the pool, 11 made new, 0 destroyed from it; it holds 0 (0 MB) vk: VRAM: 1824 MB used of 5152 MB allowed (870 MB of it free in the blocks); textures 134 MB (budget 3600 MB), 108 textures vk: render target images: 0 reused from the pool, 11 made new, 0 destroyed from it; it holds 0 (0 MB) vk: VRAM: 1824 MB used of 5152 MB allowed (876 MB of it free in the blocks); textures 132 MB (budget 3600 MB), 124 textures vk: pool 0x102fc00000 (52 MB) stays in system memory: the mirrors have 1468 MB of 1480 vk: pool 0x100dc00000 (20 MB) stays in system memory: the mirrors have 1468 MB of 1480 vk: pool 0x7ffe000000 (24 MB) stays in system memory: the mirrors have 1468 MB of 1480 vk: pool 0x102fc00000 (52 MB) stays in system memory: the mirrors have 1468 MB of 1480 vk: pool 0x100dc00000 (20 MB) stays in system memory: the mirrors have 1468 MB of 1480 vk: pool 0x7ffe000000 (24 MB) stays in system memory: the mirrors have 1468 MB of 1480 vk: 56 pools mirrored in VRAM (1468 MB) vk: pool 0x102fc00000 (52 MB) stays in system memory: the mirrors have 1468 MB of 1480 vk: 57 pools mirrored in VRAM (1276 MB) vk: pool 0x7f52400000 (8 MB) stays in system memory: the mirrors have 1476 MB of 1480 vk: 68 pools mirrored in VRAM (1480 MB) vk: render target images: 7 reused from the pool, 200 made new, 96 destroyed from it; it holds 0 (0 MB) vk: VRAM: 4467 MB used of 5070 MB allowed (457 MB of it free in the blocks); textures 1529 MB (budget 1207 MB), 896 textures vk: render target images: 15 reused from the pool, 223 made new, 120 destroyed from it; it holds 0 (0 MB) vk: VRAM: 4467 MB used of 5047 MB allowed (544 MB of it free in the blocks); textures 1442 MB (budget 1190 MB), 818 textures vk: render target images: 643 reused from the pool, 484 made new, 265 destroyed from it; it holds 58 (79 MB) vk: VRAM: 4601 MB used of 5073 MB allowed (488 MB of it free in the blocks); textures 1546 MB (budget 1168 MB), 900 textures vk: render target images: 1338 reused from the pool, 708 made new, 492 destroyed from it; it holds 115 (180 MB) vk: VRAM: 4601 MB used of 5044 MB allowed (495 MB of it free in the blocks); textures 1435 MB (budget 1052 MB), 836 textures vk: render target images: 1915 reused from the pool, 884 made new, 770 destroyed from it; it holds 0 (0 MB) vk: VRAM: 4473 MB used of 5073 MB allowed (530 MB of it free in the blocks); textures 1452 MB (budget 1219 MB), 850 textures vk: render target images: 2787 reused from the pool, 1172 made new, 933 destroyed from it; it holds 60 (81 MB) vk: VRAM: 4601 MB used of 5047 MB allowed (463 MB of it free in the blocks); textures 1566 MB (budget 1184 MB), 915 textures vk: render target images: 3419 reused from the pool, 1356 made new, 1175 destroyed from it; it holds 82 (131 MB) vk: VRAM: 4601 MB used of 5090 MB allowed (539 MB of it free in the blocks); textures 1436 MB (budget 1089 MB), 835 textures vk: render target images: 4007 reused from the pool, 1607 made new, 1408 destroyed from it; it holds 16 (18 MB) vk: VRAM: 4601 MB used of 5073 MB allowed (528 MB of it free in the blocks); textures 1559 MB (budget 1200 MB), 916 textures vk: render target images: 4884 reused from the pool, 1817 made new, 1593 destroyed from it; it holds 83 (121 MB) vk: VRAM: 4601 MB used of 5061 MB allowed (484 MB of it free in the blocks); textures 1502 MB (budget 1132 MB), 874 textures vk: render target images: 5411 reused from the pool, 1975 made new, 1878 destroyed from it; it holds 0 (0 MB) vk: VRAM: 4473 MB used of 5063 MB allowed (550 MB of it free in the blocks); textures 1432 MB (budget 1195 MB), 825 textures vk: render target images: 6173 reused from the pool, 2251 made new, 2021 destroyed from it; it holds 63 (84 MB) vk: VRAM: 4641 MB used of 5106 MB allowed (477 MB of it free in the blocks); textures 1544 MB (budget 1171 MB), 901 textures vk: render target images: 6895 reused from the pool, 2445 made new, 2222 destroyed from it; it holds 122 (198 MB) vk: VRAM: 4641 MB used of 5081 MB allowed (474 MB of it free in the blocks); textures 1434 MB (budget 1042 MB), 832 textures vk: render target images: 7392 reused from the pool, 2599 made new, 2493 destroyed from it; it holds 0 (0 MB) vk: VRAM: 4513 MB used of 5052 MB allowed (529 MB of it free in the blocks); textures 1450 MB (budget 1160 MB), 842 textures vk: render target images: 8113 reused from the pool, 2872 made new, 2655 destroyed from it; it holds 61 (88 MB) vk: VRAM: 4513 MB used of 5073 MB allowed (351 MB of it free in the blocks); textures 1526 MB (budget 1254 MB), 892 textures vk: render target images: 8758 reused from the pool, 3075 made new, 2875 destroyed from it; it holds 99 (157 MB) vk: VRAM: 4641 MB used of 5081 MB allowed (500 MB of it free in the blocks); textures 1437 MB (budget 1042 MB), 840 textures vk: render target images: 9330 reused from the pool, 3299 made new, 3129 destroyed from it; it holds 20 (28 MB) vk: VRAM: 4513 MB used of 5063 MB allowed (431 MB of it free in the blocks); textures 1506 MB (budget 1225 MB), 889 textures ``` </details>
First-time contributor

Follow-up to my T-XFT3 hardware result: a longer normal-play stability observation with the local renderer built from PR head 091daa9 in Longshot v3.0.0-rc.3. Same Windows 11 / RTX 4050 Laptop 6 GB / NVIDIA 621.14 machine, with the GPU driving the display.

This session used the player's Custom settings: 1920x1080 output, Handheld profile, Auto (dynamic) render resolution, the game's own upscaler, built-in frame generation 2x, fullscreen. These settings and the changing scenes are not comparable to the controlled Low/75% Hyrule Field A-B-A-B FPS test above. I make no FPS improvement claim from this observation.

The game was still running and responsive at 19 min 41 s of process uptime. I sampled it every ~30 s for the final 14 min 46 s (30 samples), seeing Goron City and Lost Woods. Process private memory ranged 18.636–19.893 GiB; minimum available physical RAM was 6.725 GiB; peak system commit was 40.48 / 63.64 GiB. This is too short and too variable to establish whether memory leaks over time. No device-lost or commit limit near line appeared in the current game log.

The tighter VRAM result is worth flagging: at the 19:53 UTC-4 cutoff, across 100 vk: VRAM: samples in this session's log, the peak and smallest recorded margin were 4829 MB used of 5052 MB allowed (223 MB margin). The render-target pool also reported one pressure event:

vk: render target images: 7214 reused from the pool, 3403 made new, 3165 destroyed from it; it holds 0 (0 MB), VRAM short: given back
vk: VRAM: 4749 MB used of 5070 MB allowed (428 MB of it free in the blocks); textures 2037 MB (budget 1460 MB), 1148 textures

The short controlled Field test had no VRAM short line; this longer, differently configured play did. There was no crash or GPU-device loss in the observed window, but the remaining VRAM margin is small and this pool gave memory back once. Longer play and other 6 GB cards are still important for the memory-headroom risk. No game files, logs, caches or saves are attached.

Follow-up to my T-XFT3 hardware result: a longer **normal-play stability observation** with the local renderer built from PR head `091daa9` in Longshot v3.0.0-rc.3. Same Windows 11 / RTX 4050 Laptop 6 GB / NVIDIA 621.14 machine, with the GPU driving the display. This session used the player's **Custom** settings: 1920x1080 output, Handheld profile, Auto (dynamic) render resolution, the game's own upscaler, built-in frame generation 2x, fullscreen. These settings and the changing scenes are **not comparable** to the controlled Low/75% Hyrule Field A-B-A-B FPS test above. I make no FPS improvement claim from this observation. The game was still running and responsive at **19 min 41 s** of process uptime. I sampled it every ~30 s for the final **14 min 46 s** (30 samples), seeing Goron City and Lost Woods. Process private memory ranged **18.636–19.893 GiB**; minimum available physical RAM was **6.725 GiB**; peak system commit was **40.48 / 63.64 GiB**. This is too short and too variable to establish whether memory leaks over time. No device-lost or `commit limit near` line appeared in the current game log. The tighter VRAM result is worth flagging: at the 19:53 UTC-4 cutoff, across 100 `vk: VRAM:` samples in this session's log, the peak and smallest recorded margin were **4829 MB used of 5052 MB allowed (223 MB margin)**. The render-target pool also reported one pressure event: ```text vk: render target images: 7214 reused from the pool, 3403 made new, 3165 destroyed from it; it holds 0 (0 MB), VRAM short: given back vk: VRAM: 4749 MB used of 5070 MB allowed (428 MB of it free in the blocks); textures 2037 MB (budget 1460 MB), 1148 textures ``` The short controlled Field test had no `VRAM short` line; this longer, differently configured play did. There was no crash or GPU-device loss in the observed window, but the remaining VRAM margin is small and this pool gave memory back once. Longer play and other 6 GB cards are still important for the memory-headroom risk. No game files, logs, caches or saves are attached.
HumanBean approved these changes 2026-11-14 00:37:33 +00:00
HumanBean left a comment

Re-review of PR 237 at b956a08. I only read the code: I didn't build it, run the checks or run the game.

Since the last review (38c1a48): 091daa9 is only the rename preset_bytes → budget_bytes. b956a08 only adds the Native-resolution rows to docs/tested-configurations.md. Neither changes behaviour. I read the logic again against the menu:

  • A row step sets opts.budget = 0, so it is capped only on a small card.
  • A preset, or Auto's tier, sets opts.budget = p, so its budget is capped at auto_bytes.
  • preset_caps_memory() still decides whether a preset's mirrors apply at all.
  • RX_VK_MIRROR_MB still wins over all of these.

Cards of 8 GB or more are unchanged, and so is Auto on 4 GB. Low on a 4 GB card stays at 512.

The condition from my last review is met. T-XFT3 came back as a pass on a 6 GB card that drives the display (Rogasse, RTX 4050 Laptop, 621.14). Over A-B-A-B runs it went from 18.2 to 53.5 fps, with no device lost, no VRAM short and no commit limit near in the controlled runs. Rogasse's longer free-play session had a smallest margin of 223 MB and one render-target VRAM short: given back, and nothing failed. That matches the trade-off the Risk section describes. AMD/Intel 6 GB cards and 4 GB cards (T-9799) remain untested, and the description says so.

Before merging (small, nothing blocking):

  • The description's Other testers so far and Needs other testers still list T-XFT3 as needed. Please add Rogasse's pass and the 223 MB / one VRAM short observation, so the description matches the PR as it is now.
  • main has moved two unrelated commits past d9648863 (the setup's MAX_PATH fix and the Discord link). It is still mergeable, so this only needs a rebase if the merge asks for one.
  • CI for b956a08 has been queued since 22:56Z and hasn't run yet. The author's local run_all covers the box for now. It's worth a look once the runners pick it up.

Three nits are inline.

Re-review of PR 237 at b956a08. I only read the code: I didn't build it, run the checks or run the game. **Since the last review (38c1a48):** 091daa9 is only the rename `preset_bytes` → `budget_bytes`. b956a08 only adds the Native-resolution rows to `docs/tested-configurations.md`. Neither changes behaviour. I read the logic again against the menu: - A row step sets `opts.budget = 0`, so it is capped only on a small card. - A preset, or Auto's tier, sets `opts.budget = p`, so its budget is capped at `auto_bytes`. - `preset_caps_memory()` still decides whether a preset's mirrors apply at all. - `RX_VK_MIRROR_MB` still wins over all of these. Cards of 8 GB or more are unchanged, and so is Auto on 4 GB. Low on a 4 GB card stays at 512. **The condition from my last review is met.** T-XFT3 came back as a pass on a 6 GB card that drives the display (Rogasse, RTX 4050 Laptop, 621.14). Over A-B-A-B runs it went from 18.2 to 53.5 fps, with no device lost, no `VRAM short` and no `commit limit near` in the controlled runs. Rogasse's longer free-play session had a smallest margin of 223 MB and one render-target `VRAM short: given back`, and nothing failed. That matches the trade-off the Risk section describes. AMD/Intel 6 GB cards and 4 GB cards (T-9799) remain untested, and the description says so. **Before merging (small, nothing blocking):** - The description's *Other testers so far* and *Needs other testers* still list T-XFT3 as needed. Please add Rogasse's pass and the 223 MB / one `VRAM short` observation, so the description matches the PR as it is now. - main has moved two unrelated commits past d9648863 (the setup's MAX_PATH fix and the Discord link). It is still mergeable, so this only needs a rebase if the merge asks for one. - CI for b956a08 has been queued since 22:56Z and hasn't run yet. The author's local `run_all` covers the box for now. It's worth a look once the runners pick it up. Three nits are inline.
@ -251,3 +251,3 @@
| `RX_VK_NATIVE_FADD=0` or `1` | The shaders' float adds to storage buffers: 1 the GPU's own atomic where it has `VK_EXT_shader_atomic_float`'s `shaderBufferFloat32AtomicAdd`, 0 a compare-exchange loop on every GPU, the extension not enabled. By default the GPU's own on NVIDIA's driver only, the one it was tried on in the game; on AMD and Intel the loop until runs there show the native add works (the log's `vk: float atomic add on storage buffers:` line says which, and why). The loop gives up after 1024 attempts, and an add that gives up is lost |
| `RX_VK_UB_WORD_HASH=0` | A/B test: use the old low-address-bit index for cached uniform words (default: mix upper address bits to reduce aligned-buffer collisions) |
| `RX_VK_MIRROR=0`, `RX_VK_MIRROR_MB=<MB>` | No VRAM mirrors of the game's memory pools (on for discrete cards); their budget |
| `RX_VK_MIRROR=0`, `RX_VK_MIRROR_MB=<MB>` | No VRAM mirrors of the game's memory pools (on for discrete cards); their budget (by default a quarter of the card, at most 3 GB, an eighth on cards of 4.5 GB or less, and a Quality preset's never more than that: `gpu/vk/mirror_budget.h`) |
Owner

Nit: this says a preset's budget is capped, but the cap also applies to a step chosen on the VRAM budget row on a card of 4.5 GB or less (budget_bytes, small_card). Its 4.8 GB-and-up steps keep 512 there now. Worth half a sentence, since that's where a player would look.

Nit: this says a preset's budget is capped, but the cap also applies to a step chosen on the VRAM budget row on a card of 4.5 GB or less (`budget_bytes`, `small_card`). Its 4.8 GB-and-up steps keep 512 there now. Worth half a sentence, since that's where a player would look.
@ -854,3 +854,3 @@
constexpr int kVramMinimumOnly = 1; // (2.2 GB: set by the Minimum (4GB) preset only; the row's own steps go past it)
constexpr int kVramTextureMb[kVramBudgets] = {0, 2200, 3600, 4800, 6144, 7500, 9216, 11000};
constexpr int kVramMirrorMb[kVramBudgets] = {0, 256, 512, 1024, 1536, 2048, 2560, 3072};
// (longshot) 3.6 GB's mirrors, the Low (6GB) preset's and so every 6 GB card's from its first start (menu_first_start_for_gpu): 1536
Owner

Nit: "every 6 GB card's from its first start" isn't quite right. A 6 GB card without mesh shaders (GTX 1060 6 GB, RX 5600 XT) starts on the Minimum tier. It gets the quarter through Auto's rule (preset_caps_memory() is false there), not through this step. Maybe: "every 6 GB card with mesh shaders".

Nit: "every 6 GB card's from its first start" isn't quite right. A 6 GB card without mesh shaders (GTX 1060 6 GB, RX 5600 XT) starts on the Minimum tier. It gets the quarter through Auto's rule (`preset_caps_memory()` is false there), not through this step. Maybe: "every 6 GB card with mesh shaders".
@ -0,0 +9,4 @@
import tempfile
from pathlib import Path
RUN_ALL_ARGS = ["--cxx", "g++"]
Owner

Nit: most C++ checks use "{cxx}", so run_all picks clang++, g++ or c++, whichever is there. With a hard g++, this check is skipped on a PC that has only clang++, such as some llvm-mingw installs. The code is plain C++20, so {cxx} should work.

Nit: most C++ checks use `"{cxx}"`, so run_all picks clang++, g++ or c++, whichever is there. With a hard `g++`, this check is skipped on a PC that has only clang++, such as some llvm-mingw installs. The code is plain C++20, so `{cxx}` should work.
HumanBean deleted branch perf/low-vram-mirror-budget 2026-11-14 00:41:07 +00:00
Some checks are pending
PR description / Description follows the template (pull_request_target) Successful in 3s
CI / Conventional Commit title (pull_request) Blocked by required conditions
Third-party parts / As their notes say (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Blocked by required conditions
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Blocked by required conditions
CI / Workflow lint (actionlint) (pull_request) Blocked by required conditions
Third-party parts / The same as upstream (pull_request) Blocked by required conditions
Website / Website on Cloudflare (pull_request_target) Has been skipped
Some workflows are waiting to be reviewed.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
3 participants
Notifications
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/Longshot!237
No description provided.