feat(menu): a VRAM budget row of its own (builds on #1358) #1859

Merged
Joey-Magnus merged 3 commits from feat/vram-budget-row into main 2026-11-11 18:52:36 +00:00
Joey-Magnus commented 2026-11-10 18:45:10 +00:00 (Migrated from github.com)

Players can't choose a VRAM limit: the texture and mirror budgets come only from the Quality preset. On a 12 GB card, for example, "Custom" means the automatic 6 GB and "High (12GB)" means 7.5 GB, nothing in between and nothing lower (null and Gummi in #dev-work). This adds a VRAM budget row of its own.

Built on #1358, which is now merged (DarkzGG's work, squashed into main as 26b2f744). This branch is rebased onto main and carries only the row.

The row (Graphics, next to Render resolution):

  • Choices: Auto (the renderer's own rule: 70% of what the driver allows, 45% on cards of 8 GB or less, at most 6 GB) or a texture budget of 3.6 / 4.8 / 6 / 7.5 / 9 / 11 GB. The row's own steps skip 2.2 GB: that's the Minimum (4GB) preset's budget only, and the row shows it as "2.2 GB (4GB preset)" when that preset sets it. Measured on a 12 GB card (castle-grounds walk, two runs each): 2.2 GB evicted and remade textures 2-3x as often as Auto, with 1% lows of 4.8-7.0 fps (hckrman101's question in #dev-work). The mirror budget is scaled from it (256 MB … 3 GB). The presets' own budgets are among the choices.
  • A preset sets the row. Each preset gives exactly the budgets it gave before. Choosing Custom on the preset row clears it to Auto, as before.
  • Changing another row (preset → Custom) keeps it (#1358).
  • A budget chosen on the row is the player's (opts.budget = 0), so it applies on any card. A preset's own budget still applies only on the card size it is for (#1358's preset_caps_memory).
  • The row is preset_exempt: changing it leaves the preset alone. It's saved as vram_budget= like any row. An options file from before the row takes its preset's (memory_budget's) budgets. memory_budget is always saved, 0 included, so a budget chosen on the row with a preset on stays the player's after a restart.
  • RX_VK_VRAM_MB / RX_VK_MIRROR_MB, the integrated-GPU cap and the 75%-of-allowed safety clamp all apply as before.

Tests

  • New tests/test_vram_budget_row.py. It builds the menu's functions and preset_caps_memory from the source and checks:
    • each preset's budgets are unchanged, Custom on the preset row is Auto, and the row's values are right;
    • High's own 7.5 GB is ignored on a 16 GB card (as before) but a size chosen on the row applies there;
    • the row-change handler, the row's preset_exempt, and the old-file fallback (source checks).
      It fails without the row and passes here.
  • test_framegen_preset.py and test_rumble_menu.py compile the row-change handler, so their stubs gain vram_budget_sync.
  • run_all.py: 278 passed. 2 fail on main too (test_present_ahead.py, test_present_semaphores.py: main's renderer doesn't link since a259978, see #dev-work). For the same reason a standalone build of this branch stops at the link with those two symbols. The row itself compiles: I built it on a tree from before a259978.
  • In game (Linux, RTX 4070 Ti 12 GB, 595.99.02), on that tree:
    • an options file from before the row with quality_preset=4: budget 7299 MB (High's 7.5 GB under the 75% clamp, as before);
    • quality_preset=0 with vram_budget=3: budget 4800 MB, kept in the file.

Not on screen beyond the new row. The High preset's 7.5 GB on 12 GB cards at DLAA is a separate question (null's numbers); this PR doesn't change it.

🤖 Generated with Claude Code

Players can't choose a VRAM limit: the texture and mirror budgets come only from the Quality preset. On a 12 GB card, for example, "Custom" means the automatic 6 GB and "High (12GB)" means 7.5 GB, nothing in between and nothing lower (null and Gummi in #dev-work). This adds a **VRAM budget** row of its own. **Built on #1358**, which is now merged (DarkzGG's work, squashed into main as 26b2f744). This branch is rebased onto main and carries only the row. **The row** (Graphics, next to Render resolution): - **Choices:** Auto (the renderer's own rule: 70% of what the driver allows, 45% on cards of 8 GB or less, at most 6 GB) or a texture budget of 3.6 / 4.8 / 6 / 7.5 / 9 / 11 GB. The row's own steps skip 2.2 GB: that's the Minimum (4GB) preset's budget only, and the row shows it as "2.2 GB (4GB preset)" when that preset sets it. Measured on a 12 GB card (castle-grounds walk, two runs each): 2.2 GB evicted and remade textures 2-3x as often as Auto, with 1% lows of 4.8-7.0 fps (hckrman101's question in #dev-work). The mirror budget is scaled from it (256 MB … 3 GB). The presets' own budgets are among the choices. - **A preset sets the row.** Each preset gives exactly the budgets it gave before. Choosing Custom on the preset row clears it to Auto, as before. - **Changing another row** (preset → Custom) keeps it (#1358). - **A budget chosen on the row is the player's** (`opts.budget = 0`), so it applies on any card. A preset's own budget still applies only on the card size it is for (#1358's `preset_caps_memory`). - **The row is `preset_exempt`:** changing it leaves the preset alone. It's saved as `vram_budget=` like any row. An options file from before the row takes its preset's (`memory_budget`'s) budgets. `memory_budget` is always saved, 0 included, so a budget chosen on the row with a preset on stays the player's after a restart. - `RX_VK_VRAM_MB` / `RX_VK_MIRROR_MB`, the integrated-GPU cap and the 75%-of-allowed safety clamp all apply as before. **Tests** - New `tests/test_vram_budget_row.py`. It builds the menu's functions and `preset_caps_memory` from the source and checks: - each preset's budgets are unchanged, Custom on the preset row is Auto, and the row's values are right; - High's own 7.5 GB is ignored on a 16 GB card (as before) but a size chosen on the row applies there; - the row-change handler, the row's `preset_exempt`, and the old-file fallback (source checks). It fails without the row and passes here. - `test_framegen_preset.py` and `test_rumble_menu.py` compile the row-change handler, so their stubs gain `vram_budget_sync`. - `run_all.py`: 278 passed. 2 fail on main too (`test_present_ahead.py`, `test_present_semaphores.py`: main's renderer doesn't link since a259978, see #dev-work). For the same reason a standalone build of this branch stops at the link with those two symbols. The row itself compiles: I built it on a tree from before a259978. - In game (Linux, RTX 4070 Ti 12 GB, 595.99.02), on that tree: - an options file from before the row with `quality_preset=4`: `budget 7299 MB` (High's 7.5 GB under the 75% clamp, as before); - `quality_preset=0` with `vram_budget=3`: `budget 4800 MB`, kept in the file. Not on screen beyond the new row. The High preset's 7.5 GB on 12 GB cards at DLAA is a separate question (null's numbers); this PR doesn't change it. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
hckrman101 (Migrated from github.com) reviewed 2026-11-10 20:53:15 +00:00
hckrman101 (Migrated from github.com) left a comment

Read through the diff and the new test. It's a tidy change and the preset-to-row mapping checks out (each preset still gives its old budgets, 2.2 GB skipped by the row's steps). One real problem, below.

On the #1358 questions: this PR does carry #1358's two commits (8379f61a, d6cbbee2, same SHAs) plus three merges of main, so it works without #1358 landing first. If #1358 merges first, the shared hunks (opts.budget, preset_caps_memory, memory_budget) are identical, so it should shrink cleanly. No collision I can find: the row is not given a numeric id, its ini key comes from the name (vram_budget), and nothing on main or in the open PRs I searched uses that key or opts.budget.

Bug: a budget chosen on the row is forgotten after a restart when a preset is on. The row is preset_exempt, so with High (12GB) on, choosing 4.8 GB sets opts.budget = 0 and leaves preset = 4. save() only writes memory_budget when opts.budget > 0, so nothing marks the row as the player's. At the next start if (opts.preset > 0) opts.budget = opts.preset; puts it back to 4. On a card bigger than that preset's size (e.g. High or Low on a 16 GB card, Low on 12 GB), preset_caps_memory() is false again, so the saved vram_budget=3 shows in the menu but the renderer uses the card's own budget. The in-game check in the description (quality_preset=0, vram_budget=3) doesn't reach this. Suggest writing memory_budget=0 explicitly and only deriving opts.budget from the preset when the file has no memory_budget line (same flag pattern as has_vram_budget), plus a case in the test.

CI: the failures are test_bake_mimo / test_bake_skeleton (clang++ has no C++23 on the runner) on Linux and test_bake_ds (libzstd.so) / test_log_batch (a timing check) on Windows, none of them touched by this PR; test_vram_budget_row and the two stubbed tests pass. Builds and AppImage are green.

Read through the diff and the new test. It's a tidy change and the preset-to-row mapping checks out (each preset still gives its old budgets, 2.2 GB skipped by the row's steps). One real problem, below. On the #1358 questions: this PR does carry #1358's two commits (8379f61a, d6cbbee2, same SHAs) plus three merges of main, so it works without #1358 landing first. If #1358 merges first, the shared hunks (`opts.budget`, `preset_caps_memory`, `memory_budget`) are identical, so it should shrink cleanly. No collision I can find: the row is not given a numeric id, its ini key comes from the name (`vram_budget`), and nothing on main or in the open PRs I searched uses that key or `opts.budget`. **Bug: a budget chosen on the row is forgotten after a restart when a preset is on.** The row is `preset_exempt`, so with High (12GB) on, choosing 4.8 GB sets `opts.budget = 0` and leaves `preset = 4`. `save()` only writes `memory_budget` when `opts.budget > 0`, so nothing marks the row as the player's. At the next start `if (opts.preset > 0) opts.budget = opts.preset;` puts it back to 4. On a card bigger than that preset's size (e.g. High or Low on a 16 GB card, Low on 12 GB), `preset_caps_memory()` is false again, so the saved `vram_budget=3` shows in the menu but the renderer uses the card's own budget. The in-game check in the description (`quality_preset=0`, `vram_budget=3`) doesn't reach this. Suggest writing `memory_budget=0` explicitly and only deriving `opts.budget` from the preset when the file has no `memory_budget` line (same flag pattern as `has_vram_budget`), plus a case in the test. CI: the failures are test_bake_mimo / test_bake_skeleton (clang++ has no C++23 on the runner) on Linux and test_bake_ds (libzstd.so) / test_log_batch (a timing check) on Windows, none of them touched by this PR; test_vram_budget_row and the two stubbed tests pass. Builds and AppImage are green.
hckrman101 (Migrated from github.com) commented 2026-11-10 20:53:15 +00:00

This is where the row's choice gets lost: with a preset on, opts.budget is always the preset's after a restart, even if the player set the row (budget 0, nothing saved to say so). Only take the preset's here when the file has no memory_budget line, and write memory_budget=0 in save() when a preset is on and budget is 0.

This is where the row's choice gets lost: with a preset on, `opts.budget` is always the preset's after a restart, even if the player set the row (budget 0, nothing saved to say so). Only take the preset's here when the file has no `memory_budget` line, and write `memory_budget=0` in save() when a preset is on and budget is 0.
hckrman101 commented 2026-11-11 15:51:01 +00:00 (Migrated from github.com)

Heads-up: this conflicts with main now. Could you rebase onto main and resolve the conflicts? Thanks!

Heads-up: this conflicts with main now. Could you rebase onto main and resolve the conflicts? Thanks!
Joey-Magnus (Migrated from github.com) reviewed 2026-11-11 18:41:49 +00:00
Joey-Magnus (Migrated from github.com) commented 2026-11-11 18:41:49 +00:00

Fixed in f1ffd279 as suggested: save() always writes memory_budget (0 included), and the preset only supplies opts.budget when the file has no memory_budget line (has_memory_budget, the same pattern as has_vram_budget).

Fixed in f1ffd279 as suggested: `save()` always writes `memory_budget` (0 included), and the preset only supplies `opts.budget` when the file has no `memory_budget` line (`has_memory_budget`, the same pattern as `has_vram_budget`).
Joey-Magnus commented 2026-11-11 18:41:51 +00:00 (Migrated from github.com)

Thanks, good catch. Fixed in f1ffd279: save() always writes memory_budget, 0 included, and menu_init takes the preset's budget only when the file has no memory_budget line.

test_vram_budget_row has a restart case built from vk_menu.cpp's own save and load lines:

  • High on a 16 GB card, with 4.8 GB chosen on the row, still has budget 0, vram_budget 3 and 4800 MB after the restart, and preset_caps_memory() holds.
  • A file from before memory_budget still takes the preset's budget.

It fails without the fix (on the first case) and passes with it.

Rebased onto current main. #1358 was squashed into 26b2f744, so I replayed only this PR's two commits on main and dropped #1358's original commits and the three merges. The conflicts were mechanical: main's snap_render/env_release block next to this row's handler, main's env_override lines, and the env_release stubs in two tests.

tests/run_all.py on Linux: 393 passed, 0 failed.

Thanks, good catch. Fixed in f1ffd279: `save()` always writes `memory_budget`, 0 included, and `menu_init` takes the preset's budget only when the file has no `memory_budget` line. `test_vram_budget_row` has a restart case built from `vk_menu.cpp`'s own save and load lines: - High on a 16 GB card, with 4.8 GB chosen on the row, still has `budget 0`, `vram_budget 3` and 4800 MB after the restart, and `preset_caps_memory()` holds. - A file from before `memory_budget` still takes the preset's budget. It fails without the fix (on the first case) and passes with it. Rebased onto current `main`. #1358 was squashed into 26b2f744, so I replayed only this PR's two commits on main and dropped #1358's original commits and the three merges. The conflicts were mechanical: main's `snap_render`/`env_release` block next to this row's handler, main's `env_override` lines, and the `env_release` stubs in two tests. `tests/run_all.py` on Linux: 393 passed, 0 failed.
hckrman101 (Migrated from github.com) reviewed 2026-11-11 18:50:03 +00:00
hckrman101 (Migrated from github.com) commented 2026-11-11 18:50:03 +00:00

Confirmed in f1ffd279: memory_budget is written every time, and the preset only fills it in when the file has no such line. The restart case in the test covers it. Thanks!

Confirmed in f1ffd279: `memory_budget` is written every time, and the preset only fills it in when the file has no such line. The restart case in the test covers it. Thanks!
hckrman101 (Migrated from github.com) approved these changes 2026-11-11 18:50:12 +00:00
hckrman101 (Migrated from github.com) left a comment

Re-checked at f1ffd279 after the rebase. The restart bug from my last review is fixed, exactly as suggested: save() always writes memory_budget (0 included), and menu_init only falls back to the preset's budget when the file has no memory_budget line (has_memory_budget). The new restart case in test_vram_budget_row (High on a 16 GB card, 4.8 GB chosen on the row) uses vk_menu.cpp's own save, parse and fallback lines, so it follows the source and would catch a regression. The case for a file from before memory_budget still gets the preset's budgets.

The rest checks out:

  • #1358 is merged (26b2f744), and the branch now has only this PR's three commits on top of main. The row hunks are the same as the version I reviewed, apart from the fix.
  • It works with #2376: memory_budget is in save()'s mine list, so writing it every time still leaves one line in the file, and test_options_budget_once passes.
  • Older files are fine. Files from main always had a memory_budget line whenever a preset was on, so has_memory_budget can't change what they load. The first-start preset and RX_VK_LOW_SPEC both go through apply_preset/sync_preset_budgets, so the row follows them.
  • No settings id or ini key collisions. The row has no numeric id (its key is vram_budget), and the PR touches only vk_menu.cpp/.h and tests.
  • No overlap with #2198, which works on the texture budget further down in vk_texture.cpp/vk_vma.cpp and only reads the budget this row sets. #2401 is closed.
  • test_framegen_preset and test_rumble_menu, which compile set_row from vk_menu.cpp, have their stubs updated and pass.

CI: the only failure is test_native_astc_texture_count.py on Windows (scanf deprecated under -Werror), and main fails it too at this branch's base (2ed98c46). test_vram_budget_row passes on both Linux and Windows, and the builds and the AppImage are green. The branch is mergeable.

One small request: the new row is on screen, so per CONTRIBUTING a screenshot of the Graphics tab with it would be good to add to the description (for example with a preset on and the row showing "2.2 GB (4GB preset)", or a size chosen on the row). Not a blocker on the code side.

Re-checked at f1ffd279 after the rebase. The restart bug from my last review is fixed, exactly as suggested: `save()` always writes `memory_budget` (0 included), and `menu_init` only falls back to the preset's budget when the file has no `memory_budget` line (`has_memory_budget`). The new restart case in `test_vram_budget_row` (High on a 16 GB card, 4.8 GB chosen on the row) uses `vk_menu.cpp`'s own save, parse and fallback lines, so it follows the source and would catch a regression. The case for a file from before `memory_budget` still gets the preset's budgets. The rest checks out: - #1358 is merged (26b2f744), and the branch now has only this PR's three commits on top of main. The row hunks are the same as the version I reviewed, apart from the fix. - It works with #2376: `memory_budget` is in save()'s `mine` list, so writing it every time still leaves one line in the file, and `test_options_budget_once` passes. - Older files are fine. Files from main always had a `memory_budget` line whenever a preset was on, so `has_memory_budget` can't change what they load. The first-start preset and `RX_VK_LOW_SPEC` both go through `apply_preset`/`sync_preset_budgets`, so the row follows them. - No settings id or ini key collisions. The row has no numeric id (its key is `vram_budget`), and the PR touches only `vk_menu.cpp`/`.h` and tests. - No overlap with #2198, which works on the texture budget further down in `vk_texture.cpp`/`vk_vma.cpp` and only reads the budget this row sets. #2401 is closed. - `test_framegen_preset` and `test_rumble_menu`, which compile `set_row` from `vk_menu.cpp`, have their stubs updated and pass. CI: the only failure is `test_native_astc_texture_count.py` on Windows (`scanf` deprecated under -Werror), and main fails it too at this branch's base (2ed98c46). `test_vram_budget_row` passes on both Linux and Windows, and the builds and the AppImage are green. The branch is mergeable. One small request: the new row is on screen, so per CONTRIBUTING a screenshot of the Graphics tab with it would be good to add to the description (for example with a preset on and the row showing "2.2 GB (4GB preset)", or a size chosen on the row). Not a blocker on the code side.
Arkex-ai commented 2026-11-11 19:20:06 +00:00 (Migrated from github.com)

Windows follow-up after the merge: tests/test_vram_budget_row.py fails to compile on main 3e95ca68 with llvm-mingw Clang under its -Wall -Wextra -Werror fixture flags:

fixture.cpp:89:15: error: unused variable 'kVramMinimumOnly' [-Werror,-Wunused-const-variable]
constexpr int kVramMinimumOnly = 1;

I saw the same failure in a full main-baseline suite and in the separate Builder-runtime candidate based on it. This is fixture compilation, not a measured VRAM-row runtime regression. Are you already covering the follow-up? I can validate the Windows fixture once adjusted.

Windows follow-up after the merge: `tests/test_vram_budget_row.py` fails to compile on main `3e95ca68` with llvm-mingw Clang under its `-Wall -Wextra -Werror` fixture flags: ```text fixture.cpp:89:15: error: unused variable 'kVramMinimumOnly' [-Werror,-Wunused-const-variable] constexpr int kVramMinimumOnly = 1; ``` I saw the same failure in a full main-baseline suite and in the separate Builder-runtime candidate based on it. This is fixture compilation, not a measured VRAM-row runtime regression. Are you already covering the follow-up? I can validate the Windows fixture once adjusted.
Commenting is not possible because the repository is archived.
No description provided.