fix(macos): on unified memory the GPU's allowance is what the Mac's RAM leaves, not Metal's working set #642

Closed
mooki wants to merge 5 commits from mooki/Longshot:mac/gpu-budget into main
Member

Type: fix · Risk: low · Tested on: macOS 26.7.1, Apple M4 Max (MoltenVK 1.4.2) · Needs: a Mac with less RAM

Why

On a Mac the GPU has no memory of its own: it shares the system's RAM. MoltenVK reports Metal's recommended working set as
the GPU's budget, 53,084 MB on a 64 GB Mac, so the renderer's texture and capture rules believed they had 53 GB while the
Mac itself was running out of memory. In the Mac's run #22 GPU memory went from 19.8 to 53.1 GB in 24 s, wired memory reached
58 of 64 GB and Metal aborted the game's command buffer for want of memory (the device was lost). Nothing in the renderer
saw it coming, and its log could not say whose memory it was. This PR gives the renderer the Mac's real allowance and a line
that says what holds the GPU's memory. It is part of the macOS port. See #420.

How to reproduce

Seen on: macOS 26.7.1, Apple M4 Max, 64 GB, MoltenVK 1.4.2 (main does not run on macOS yet: seen on the macOS port's
integration builds, which carry the open macOS PRs)
Settings and switches: defaults
Steps: continue from a save in Hyrule Field, press START and A on the title, and read the log's vk: memory lines.
Expected: the renderer's GPU memory allowance stays inside what the Mac has free, and the log says how much of the GPU's
memory is the renderer's.
Actual: the allowance is the driver's 53,084 MB however full the Mac is (vram_usage in gpu/vk/vk_vma.cpp), so no
texture or capture rule ever gives memory back, and no line separates the renderer's memory from Metal's own (run #22,
reports/2026-11-14-mac-startup-22.md; analysis in reports/2026-11-14-gpu-budget.md §1-§4).

What changed

On a Mac's unified memory the renderer's GPU allowance becomes the least of three: the driver's allowance, RAM less this
process's CPU memory less a reserve for the rest of the Mac, and what the GPU holds now plus what the system has available
less a margin. Every existing give-back rule goes by it, spare capture buffers and idle staging are not kept past 90% of it,
and a vk: unified memory line splits the GPU's memory into the renderer's blocks and Metal's and MoltenVK's own. All of it
is inside #if defined(__APPLE__).

  • system memory: gpu/vk/system_memory.h (new), the one macOS reader: memory pressure level, memory available without
    compressing or swapping (in the kernel's page size, vm.pagesize, so it is right under Rosetta), RAM, swap and this
    process's footprint. Elsewhere it reads nothing.
  • the rule: gpu/vk/unified_budget.h (new, no Vulkan): unified() (every heap device-local plus a device-local
    host-visible coherent type: MoltenVK on a Mac's own GPU, never a discrete GPU or a Linux iGPU with a system heap),
    compute() (reserve max(RAM/4, 8 GB), margin max(RAM/8, 2 GB), never under 1 GB), short_of (over 90%), line_due, and
    Gate (open at the first ask, then at most every N ms; a clock step back opens it too).
  • allowance: vram_usage() in gpu/vk/vk_vma.cpp answers the unified allowance, the system read at most every 100 ms under
    a mutex; prints the vk: unified memory line at the first ask and for each 1 GB it moves (at most one a second).
  • presents: unified_memory_watch() in gpu/vk/vk_texture.cpp every present (a driver read at most every 250 ms); idle
    staging arenas are not kept while short.
  • captures: gpu/vk/vk_memory.cpp: while short, spare capture buffers are not kept or made ahead. Spares are GPU-idle by
    construction; kept captures, textures in use, imports, streams and arenas in use are untouched.
  • docs: gpu/vk/VULKAN.md (textures paragraph); docs/development.md (the RX_VK_UNIFIED_BUDGET row, this PR's own
    docs commit).
  • tests: tests/unified_budget_test.cpp, tests/test_unified_budget.py: fake devices (MoltenVK on a 64 GB and a 16 GB
    Mac, MoltenVK on a discrete GPU, RTX 4090 with BAR, RADV iGPU, no host-visible type, no heap), run #22's numbers through
    the rule, the floor, an unread system, the line cadence, the 90% boundary, the Gate at clocks of 0, 1 s, 568,435 s and
    -5 ms, and source checks: the call sites, no INT64_MIN gate left in vk_vma.cpp, vk_work.h still the last include,
    and every new name inside an __APPLE__ block.

Measurements

Verdict: works as designed on the Mac: in runs #24-#26 every vk: unified memory line read the system (allowed
10.2-20.8 GB against the driver's 53,084 MB), and in run #23 its split was what showed that 50.9 of 54.7 GB in use was
Metal's and MoltenVK's own, not the renderer's (that growth is MoltenVK's 8-bit index temporaries, fixed by a macOS PR to follow,
not by this PR). Nothing had to be given back in #24-#26 (no short: line). It does not by itself prevent run #22's device
loss: the renderer owned about 4 GB there.

Baseline: main does not run on macOS yet, so both columns are the macOS port's integration builds, arm64, MoltenVK 1.4.2,
the field-dawn save, START then A on the title. Before: integration 552cc3d4 (run #23) carries this PR's first three
commits, whose read gate never opened (the INT64_MIN overflow fixed by d16b3cc2), so it behaved as main does: the
driver's allowance. After: integration e7730869 (run #24) and 6962f8fb (run #26), with all of this PR.

Hardware: macOS 26.7.1, Apple M4 Max (40-core GPU), MoltenVK 1.4.2, 16-core CPU, 64 GB
Scene: Hyrule Field from the field-dawn save, 1280x720 windowless, default settings
Method: the renderer's vk: unified memory lines in longshot_log.txt over each run (run #23: 11 lines; #24: 13; #26: 48 over
15 min in play); one run each
How to reproduce: RX_VK_UNIFIED_BUDGET=0 gives the before behaviour on the same build; read the vk: unified memory lines

Before: integration @ 552cc3d4 (run #23) After: integration @ e7730869 (run #24) After: integration @ 6962f8fb (run #26)
allowance the renderer used 53,084 MB, the driver's, every line ("the system's memory unread") 10,160-15,881 MB, every line read the system 10.2-20.8 GB, every line read the system
GPU memory in use, last line 54,684 MB: the renderer's blocks 3,824, Metal's and MoltenVK's own 50,860 (device lost) 5,596 MB: blocks 4,016, the rest 1,580 7.7-8.2 GB: blocks 4.4-4.7 GB, the rest 0.3 GB
give-back past 90% never (the driver's allowance) not needed not needed
#23 (552cc3d4): allowed 53084 MB, the driver's allowance (the system's memory unread); in use 54684 MB: the renderer's blocks 3824 MB, the rest 50860 MB
#24 (e7730869) L32930: allowed 14547 MB (driver 53084; by RAM 37282; by free 14547: 0 in use, 22739 available - 8192 kept)
#24 (e7730869) L33822: allowed 10160 ...; in use 5596: blocks 4016, rest 1580

Run reports: reports/2026-11-14-mac-startup-22.md, -23.md, -24.md (r24-unified-lines.txt), -25.md, -26.md
(r26-unified-lines.txt); the lines above are quoted from them (renderer lines only).

Flags and settings

One new switch, macOS only.

Flag What it is Before this PR After this PR Effect Helps / doesn't help / untested Recommendation
RX_VK_UNIFIED_BUDGET =0: the GPU's allowance is the driver's again (no switch; the driver's allowance) on, on a Mac's unified memory; ignored elsewhere the renderer's budgets follow what the Mac has free; the vk: unified memory line helps: a Mac under memory pressure (attribution, give-back); doesn't help: memory Metal or MoltenVK hold themselves; untested: Macs with 8-32 GB, KosmicKrisp on for everyone on macOS; =0 only to compare

Testing

Tested: on macOS 26.7.1, Apple M4 Max, at 263e462d, this PR's head before the rebases onto e090cc48 and aa95d35f
(main 6c9edffe plus this PR):

  • tests/test_unified_budget.py with the native compiler (arm64) and with clang++ -arch x86_64 (run under Rosetta): pass;
    on main 6c9edffe with only the two test files added: fails (vk_vma.cpp: vram_usage() no longer has: blocks += …).
    Logs: reports/PubWave-files/proof/gb-unified-*.log.
  • test_unified_budget, test_recorder_sync, test_retired_give_back, test_vram_compact, test_vram_pressure and
    test_preset_texture_budget through tools/ls-suite.py: pass. test_mirror_trim fails as it does on main 6c9edffe on
    macOS (the SDK's wait() against mirror_trim::wait; the macOS host PR #490 fixes it), not from this PR
    (reports/PubWave-files/gpu-budget/related-d16b3cc2/).
  • vk_vma.cpp, vk_memory.cpp, vk_texture.cpp compiled for arm64 and x86_64 at this head with main's renderer flags
    (nx2/build.py's vflags, plus -arch; reports/PubWave-files/gpu-budget/compile-263e462d/).
  • git merge-tree of this head with each open macOS PR's head (#431, #437, #438, #479, #480, #490, #491, #492): no
    conflict.
  • In the game (arm64, MoltenVK, integration builds carrying these commits): runs #24, #25 (15 min in play) and #26 (15 min in
    play): the lines read the system throughout, clean ends, no device loss.
  • After the rebase onto main aa95d35f, at 09978824 (263e462d before the rebases onto e090cc48 and aa95d35f):
    Sweep3's checks of this head (the C/C++ it changes compiled, and its own tests run, on arm64 and x86_64 where they
    apply) gave the same results as before; python -I -B tests/run_all.py on wave C1's merge 2540345b, which carries
    this head: 556/33/17 (passed/failed/skipped) against main aa95d35f's 543/33/17 on this Mac, no new failure
    (reports/2026-11-15-rebase-sweep-3.md).

Not tested: the game on x86_64 under Rosetta (that run is pending), KosmicKrisp, a Mac with 8-32 GB of RAM where the rule
gives back early. Windows and Linux were not built or run, and need nothing: off Apple (unifdef -U__APPLE__ on main's and this
head's version of every changed file, compared as tokens) vk_memory.cpp and vk_vma.cpp are main's text, the two new
headers are included only inside __APPLE__ blocks, and vk_texture.cpp's only difference is const uint64_t keep_now = keep;
used where main uses keep, the same value (reports/PubWave-files/offapple/gpu-budget.txt); the test also checks every new
name is inside an __APPLE__ block.

Must be tested:

  • macOS on an Apple silicon Mac with 16 GB: 15 minutes in play from a save, expecting vk: unified memory lines whose
    allowance stays under RAM, short: give-back lines only under pressure, and no device loss (by hand: a Mac tester).

Risks

What could break: on a Mac short of memory the textures, spare capture buffers and idle staging go back sooner, which can
cost a re-upload hitch; a wrong system read would lower the allowance (floor 1 GB). Other platforms: nothing (Apple-only code).

Turning it off: RX_VK_UNIFIED_BUDGET=0, or revert the PR.

Compatibility: Nothing changes for players on Windows and Linux; no shader stamp, cache format or MKWVK_API_VERSION change.
On macOS a new log line appears.

Reviewer notes

  • Review 1326bfac..263e462d (5 commits): the system-memory reader (shared with the giant-compile memory gate, a macOS PR to
    follow that stacks on this one), the rule, vk_work.h kept as vk_vma.cpp's last include (test_recorder_sync), the
    read-gate fix after run #23, and the docs row.
  • Based on main 6c9edffe; no dependency. The four topic commits were cherry-picked with -x from macos/gpu-budget with no
    conflict (vk_vma.cpp merged automatically). The reader's header comment already names its other users and
    tests/giant_memory_test.cpp, which come with the giant-compile memory gate PR.
  • Conflicts with main: none. With the open macOS PRs (#431, #437, #438, #479, #480, #490, #491, #492): none (git merge-tree).

Issues

See #420

Checklist

Everyone

  • One topic.
  • The title is a Conventional Commit (type(scope): description) of the status line's type.
  • Why gives the reason plainly and links the issues that fit (the tracker searched).
  • Measurements has a verdict, or one line saying why there is nothing to measure.
  • Testing names what was tested, what wasn't, and what must still be tested, where and how. (No test request: nothing changes for Windows or Linux; the macOS item is by hand.)
  • Rebased on current main: on aa95d35f (#761, 2026-11-15), head 09978824, with one conflict:
    docs/development.md, #735's new RX_VK_VRAM_MB row kept and this PR's RX_VK_UNIFIED_BUDGET row after it
    (texture_budget.h still reads the unified allowance); this PR's own commits, old → new: 1326bfac → ba195209,
    68850004 → 632636ee, e27f7ea7 → 0e63a1dc, d16b3cc2 → 5240eade, 263e462d → 09978824; the commit names
    elsewhere in this description are from before the rebase.
  • python -I -B tests/run_all.py passes (any failure that also fails on main is named). (At 263e462d: 531 passed, 33 failed, 17 skipped, against main 6c9edffe's 531/32/17 on this Mac: the 32 also fail on main, test_unified_budget is new and passes, and test_gpu_hang_watch failed under a load of 43 and passes on its own at this head; reports/PubWave-files/gpu-budget/full-263e462d/, hang-watch-rerun/. After the rebase onto main aa95d35f (head 09978824): wave C1's merge 2540345b, 556/33/17 against main aa95d35f's 543/33/17, no new failure.)
  • 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.

When it applies

  • AI assistance recorded with Assisted-by: / Co-Authored-By: trailers.
  • Switches documented (docs/development.md; the README for a menu row) and shipped at their recommended value.
  • A game-free test added for the fix, failing on main and passing here.
  • A check under tests/ that reads changed source updated in this PR, not weakened.
  • Docs updated.
**Type:** fix · **Risk:** low · **Tested on:** macOS 26.7.1, Apple M4 Max (MoltenVK 1.4.2) · **Needs:** a Mac with less RAM ## Why On a Mac the GPU has no memory of its own: it shares the system's RAM. MoltenVK reports Metal's recommended working set as the GPU's budget, 53,084 MB on a 64 GB Mac, so the renderer's texture and capture rules believed they had 53 GB while the Mac itself was running out of memory. In the Mac's run #22 GPU memory went from 19.8 to 53.1 GB in 24 s, wired memory reached 58 of 64 GB and Metal aborted the game's command buffer for want of memory (the device was lost). Nothing in the renderer saw it coming, and its log could not say whose memory it was. This PR gives the renderer the Mac's real allowance and a line that says what holds the GPU's memory. It is part of the macOS port. See #420. ## How to reproduce **Seen on:** macOS 26.7.1, Apple M4 Max, 64 GB, MoltenVK 1.4.2 (main does not run on macOS yet: seen on the macOS port's integration builds, which carry the open macOS PRs) **Settings and switches:** defaults **Steps:** continue from a save in Hyrule Field, press START and A on the title, and read the log's `vk:` memory lines. **Expected:** the renderer's GPU memory allowance stays inside what the Mac has free, and the log says how much of the GPU's memory is the renderer's. **Actual:** the allowance is the driver's 53,084 MB however full the Mac is (`vram_usage` in `gpu/vk/vk_vma.cpp`), so no texture or capture rule ever gives memory back, and no line separates the renderer's memory from Metal's own (run #22, `reports/2026-11-14-mac-startup-22.md`; analysis in `reports/2026-11-14-gpu-budget.md` §1-§4). ## What changed On a Mac's unified memory the renderer's GPU allowance becomes the least of three: the driver's allowance, RAM less this process's CPU memory less a reserve for the rest of the Mac, and what the GPU holds now plus what the system has available less a margin. Every existing give-back rule goes by it, spare capture buffers and idle staging are not kept past 90% of it, and a `vk: unified memory` line splits the GPU's memory into the renderer's blocks and Metal's and MoltenVK's own. All of it is inside `#if defined(__APPLE__)`. - system memory: `gpu/vk/system_memory.h` (new), the one macOS reader: memory pressure level, memory available without compressing or swapping (in the kernel's page size, `vm.pagesize`, so it is right under Rosetta), RAM, swap and this process's footprint. Elsewhere it reads nothing. - the rule: `gpu/vk/unified_budget.h` (new, no Vulkan): `unified()` (every heap device-local plus a device-local host-visible coherent type: MoltenVK on a Mac's own GPU, never a discrete GPU or a Linux iGPU with a system heap), `compute()` (reserve max(RAM/4, 8 GB), margin max(RAM/8, 2 GB), never under 1 GB), `short_of` (over 90%), `line_due`, and `Gate` (open at the first ask, then at most every N ms; a clock step back opens it too). - allowance: `vram_usage()` in `gpu/vk/vk_vma.cpp` answers the unified allowance, the system read at most every 100 ms under a mutex; prints the `vk: unified memory` line at the first ask and for each 1 GB it moves (at most one a second). - presents: `unified_memory_watch()` in `gpu/vk/vk_texture.cpp` every present (a driver read at most every 250 ms); idle staging arenas are not kept while short. - captures: `gpu/vk/vk_memory.cpp`: while short, spare capture buffers are not kept or made ahead. Spares are GPU-idle by construction; kept captures, textures in use, imports, streams and arenas in use are untouched. - docs: `gpu/vk/VULKAN.md` (textures paragraph); `docs/development.md` (the `RX_VK_UNIFIED_BUDGET` row, this PR's own docs commit). - tests: `tests/unified_budget_test.cpp`, `tests/test_unified_budget.py`: fake devices (MoltenVK on a 64 GB and a 16 GB Mac, MoltenVK on a discrete GPU, RTX 4090 with BAR, RADV iGPU, no host-visible type, no heap), run #22's numbers through the rule, the floor, an unread system, the line cadence, the 90% boundary, the `Gate` at clocks of 0, 1 s, 568,435 s and -5 ms, and source checks: the call sites, no `INT64_MIN` gate left in `vk_vma.cpp`, `vk_work.h` still the last include, and every new name inside an `__APPLE__` block. ## Measurements **Verdict:** works as designed on the Mac: in runs #24-#26 every `vk: unified memory` line read the system (allowed 10.2-20.8 GB against the driver's 53,084 MB), and in run #23 its split was what showed that 50.9 of 54.7 GB in use was Metal's and MoltenVK's own, not the renderer's (that growth is MoltenVK's 8-bit index temporaries, fixed by a macOS PR to follow, not by this PR). Nothing had to be given back in #24-#26 (no `short:` line). It does not by itself prevent run #22's device loss: the renderer owned about 4 GB there. Baseline: main does not run on macOS yet, so both columns are the macOS port's integration builds, arm64, MoltenVK 1.4.2, the field-dawn save, START then A on the title. Before: integration 552cc3d4 (run #23) carries this PR's first three commits, whose read gate never opened (the `INT64_MIN` overflow fixed by `d16b3cc2`), so it behaved as main does: the driver's allowance. After: integration e7730869 (run #24) and 6962f8fb (run #26), with all of this PR. Hardware: macOS 26.7.1, Apple M4 Max (40-core GPU), MoltenVK 1.4.2, 16-core CPU, 64 GB Scene: Hyrule Field from the field-dawn save, 1280x720 windowless, default settings Method: the renderer's `vk: unified memory` lines in longshot_log.txt over each run (run #23: 11 lines; #24: 13; #26: 48 over 15 min in play); one run each How to reproduce: `RX_VK_UNIFIED_BUDGET=0` gives the before behaviour on the same build; read the `vk: unified memory` lines | | Before: integration @ 552cc3d4 (run #23) | After: integration @ e7730869 (run #24) | After: integration @ 6962f8fb (run #26) | |---|---|---|---| | allowance the renderer used | 53,084 MB, the driver's, every line ("the system's memory unread") | 10,160-15,881 MB, every line read the system | 10.2-20.8 GB, every line read the system | | GPU memory in use, last line | 54,684 MB: the renderer's blocks 3,824, Metal's and MoltenVK's own 50,860 (device lost) | 5,596 MB: blocks 4,016, the rest 1,580 | 7.7-8.2 GB: blocks 4.4-4.7 GB, the rest 0.3 GB | | give-back past 90% | never (the driver's allowance) | not needed | not needed | ``` #23 (552cc3d4): allowed 53084 MB, the driver's allowance (the system's memory unread); in use 54684 MB: the renderer's blocks 3824 MB, the rest 50860 MB #24 (e7730869) L32930: allowed 14547 MB (driver 53084; by RAM 37282; by free 14547: 0 in use, 22739 available - 8192 kept) #24 (e7730869) L33822: allowed 10160 ...; in use 5596: blocks 4016, rest 1580 ``` Run reports: `reports/2026-11-14-mac-startup-22.md`, `-23.md`, `-24.md` (`r24-unified-lines.txt`), `-25.md`, `-26.md` (`r26-unified-lines.txt`); the lines above are quoted from them (renderer lines only). ### Flags and settings One new switch, macOS only. | Flag | What it is | Before this PR | After this PR | Effect | Helps / doesn't help / untested | Recommendation | |---|---|---|---|---|---|---| | `RX_VK_UNIFIED_BUDGET` | `=0`: the GPU's allowance is the driver's again | (no switch; the driver's allowance) | on, on a Mac's unified memory; ignored elsewhere | the renderer's budgets follow what the Mac has free; the `vk: unified memory` line | helps: a Mac under memory pressure (attribution, give-back); doesn't help: memory Metal or MoltenVK hold themselves; untested: Macs with 8-32 GB, KosmicKrisp | on for everyone on macOS; `=0` only to compare | ## Testing **Tested:** on macOS 26.7.1, Apple M4 Max, at `263e462d`, this PR's head before the rebases onto e090cc48 and aa95d35f (main 6c9edffe plus this PR): - `tests/test_unified_budget.py` with the native compiler (arm64) and with `clang++ -arch x86_64` (run under Rosetta): pass; on main 6c9edffe with only the two test files added: fails (`vk_vma.cpp: vram_usage() no longer has: blocks += …`). Logs: `reports/PubWave-files/proof/gb-unified-*.log`. - `test_unified_budget`, `test_recorder_sync`, `test_retired_give_back`, `test_vram_compact`, `test_vram_pressure` and `test_preset_texture_budget` through `tools/ls-suite.py`: pass. `test_mirror_trim` fails as it does on main 6c9edffe on macOS (the SDK's `wait()` against `mirror_trim::wait`; the macOS host PR #490 fixes it), not from this PR (`reports/PubWave-files/gpu-budget/related-d16b3cc2/`). - `vk_vma.cpp`, `vk_memory.cpp`, `vk_texture.cpp` compiled for arm64 and x86_64 at this head with main's renderer flags (`nx2/build.py`'s `vflags`, plus `-arch`; `reports/PubWave-files/gpu-budget/compile-263e462d/`). - `git merge-tree` of this head with each open macOS PR's head (#431, #437, #438, #479, #480, #490, #491, #492): no conflict. - In the game (arm64, MoltenVK, integration builds carrying these commits): runs #24, #25 (15 min in play) and #26 (15 min in play): the lines read the system throughout, clean ends, no device loss. - **After the rebase onto main aa95d35f**, at `09978824` (`263e462d` before the rebases onto e090cc48 and aa95d35f): Sweep3's checks of this head (the C/C++ it changes compiled, and its own tests run, on arm64 and x86_64 where they apply) gave the same results as before; `python -I -B tests/run_all.py` on wave C1's merge `2540345b`, which carries this head: 556/33/17 (passed/failed/skipped) against main aa95d35f's 543/33/17 on this Mac, no new failure (`reports/2026-11-15-rebase-sweep-3.md`). **Not tested:** the game on x86_64 under Rosetta (that run is pending), KosmicKrisp, a Mac with 8-32 GB of RAM where the rule gives back early. Windows and Linux were not built or run, and need nothing: off Apple (`unifdef -U__APPLE__` on main's and this head's version of every changed file, compared as tokens) `vk_memory.cpp` and `vk_vma.cpp` are main's text, the two new headers are included only inside `__APPLE__` blocks, and `vk_texture.cpp`'s only difference is `const uint64_t keep_now = keep;` used where main uses `keep`, the same value (`reports/PubWave-files/offapple/gpu-budget.txt`); the test also checks every new name is inside an `__APPLE__` block. **Must be tested:** - macOS on an Apple silicon Mac with 16 GB: 15 minutes in play from a save, expecting `vk: unified memory` lines whose allowance stays under RAM, `short:` give-back lines only under pressure, and no device loss (by hand: a Mac tester). ## Risks **What could break:** on a Mac short of memory the textures, spare capture buffers and idle staging go back sooner, which can cost a re-upload hitch; a wrong system read would lower the allowance (floor 1 GB). Other platforms: nothing (Apple-only code). **Turning it off:** `RX_VK_UNIFIED_BUDGET=0`, or revert the PR. **Compatibility:** Nothing changes for players on Windows and Linux; no shader stamp, cache format or `MKWVK_API_VERSION` change. On macOS a new log line appears. ## Reviewer notes - Review `1326bfac..263e462d` (5 commits): the system-memory reader (shared with the giant-compile memory gate, a macOS PR to follow that stacks on this one), the rule, `vk_work.h` kept as `vk_vma.cpp`'s last include (`test_recorder_sync`), the read-gate fix after run #23, and the docs row. - Based on main 6c9edffe; no dependency. The four topic commits were cherry-picked with `-x` from `macos/gpu-budget` with no conflict (`vk_vma.cpp` merged automatically). The reader's header comment already names its other users and `tests/giant_memory_test.cpp`, which come with the giant-compile memory gate PR. - Conflicts with main: none. With the open macOS PRs (#431, #437, #438, #479, #480, #490, #491, #492): none (`git merge-tree`). ## Issues See #420 ## Checklist ### Everyone - [x] One topic. - [x] The title is a Conventional Commit (`type(scope): description`) of the status line's type. - [x] Why gives the reason plainly and links the issues that fit (the tracker searched). - [x] Measurements has a verdict, or one line saying why there is nothing to measure. - [x] Testing names what was tested, what wasn't, and what must still be tested, where and how. (No test request: nothing changes for Windows or Linux; the macOS item is by hand.) - [x] Rebased on current `main`: on aa95d35f (#761, 2026-11-15), head `09978824`, with one conflict: `docs/development.md`, #735's new `RX_VK_VRAM_MB` row kept and this PR's `RX_VK_UNIFIED_BUDGET` row after it (`texture_budget.h` still reads the unified allowance); this PR's own commits, old → new: `1326bfac` → `ba195209`, `68850004` → `632636ee`, `e27f7ea7` → `0e63a1dc`, `d16b3cc2` → `5240eade`, `263e462d` → `09978824`; the commit names elsewhere in this description are from before the rebase. - [x] `python -I -B tests/run_all.py` passes (any failure that also fails on `main` is named). (At `263e462d`: 531 passed, 33 failed, 17 skipped, against main 6c9edffe's 531/32/17 on this Mac: the 32 also fail on main, `test_unified_budget` is new and passes, and `test_gpu_hang_watch` failed under a load of 43 and passes on its own at this head; `reports/PubWave-files/gpu-budget/full-263e462d/`, `hang-watch-rerun/`. After the rebase onto main aa95d35f (head `09978824`): wave C1's merge `2540345b`, 556/33/17 against main aa95d35f's 543/33/17, no new failure.) - [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. ### When it applies - [x] AI assistance recorded with `Assisted-by:` / `Co-Authored-By:` trailers. - [x] Switches documented (docs/development.md; the README for a menu row) and shipped at their recommended value. - [x] A game-free test added for the fix, failing on `main` and passing here. - [x] A check under `tests/` that reads changed source updated in this PR, not weakened. - [x] Docs updated.
Header-only, no Vulkan: the kernel's memory pressure level (kern.memorystatus_vm_pressure_level: 1
normal, 2 warning, 4 critical), the memory available without compressing or swapping anyone's pages
((vm.page_free_count + vm.page_pageable_external_count + vm.page_purgeable_count) * vm.pagesize, the
builder gate's estimate in nx2/darwin_memory.py), physical memory, swap and this process's footprint
(TASK_VM_INFO phys_footprint). The counts are in the kernel's pages: vm.pagesize, not hw.pagesize,
which is 4096 under Rosetta on an Apple silicon Mac. Read on macOS only; known is false elsewhere.

For the giant compile gate behind the game and the device-loss lines (macos/giant-mem-gate), and the
GPU budget work (macos/gpu-budget). Its checks are in tests/giant_memory_test.cpp on
macos/giant-mem-gate.

Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 <[email protected]>
(cherry picked from commit 41b04c1982f0ec40a97a1763d1c6fac46a16de3e)
(cherry picked from commit 96266f6502855309d918528e7b5c5a96d818684e)
MoltenVK 1.4.2 gives Apple silicon one device-local heap the size of RAM and, through VK_EXT_memory_budget, Metal's
recommendedMaxWorkingSetSize as its budget: 53,084 MB of a 64 GB Mac, whatever else holds the RAM. Every rule that
gives memory back near the allowance (the textures' budget, the render targets' pool, the compaction) waited for that,
while the process's own CPU memory and the rest of the Mac took the same RAM. Run 22 reached 53.1 GB of GPU memory 24 s
into the save's load, wired 58 of 64 GB, critical pressure, and Metal aborted a command buffer for want of memory.

On unified memory (every heap device-local, a host-visible coherent type: unified_budget.h, Apple only) vram_usage now
answers the least of the driver's allowance; RAM less the process's CPU memory (footprint less the GPU's count) less a
reserve for the rest of the Mac (a quarter of RAM, 8 GB at least); and what the GPU holds plus what the system has
available less a headroom kept available (an eighth of RAM, 2 GB at least), read through system_memory.h at most every
100 ms. Past 90% of it the spare capture buffers are given back and not made ahead, and idle staging arenas are not
kept: each is made again when needed. A "vk: unified memory" line says the allowance and what holds the GPU's memory
(the renderer's VMA blocks, or Metal's and MoltenVK's own) on the first read and whenever either moves by 1 GB, at most
once a second; the textures' frame asks again every present (at most every 250 ms). RX_VK_UNIFIED_BUDGET=0: the
driver's allowance, as before. Every change is inside #if defined(__APPLE__); tests/test_unified_budget.py checks
that, and runs the rule with made-up heaps and RAM sizes and run 22's numbers.

Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 <[email protected]>
(cherry picked from commit efabae733d40fd39bdff727b14cc998c2b56cd24)
The unified budget's Apple includes went after it; tests/test_recorder_sync.py requires vk_work.h last (RX_VK_RECORDER=defer
drains before the destructions below it).

Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 <[email protected]>
(cherry picked from commit f23c9194a466c44259acf36817a249d586260c24)
Run 23 (552cc3d4) printed "the system's memory unread" on all 11 "vk: unified memory" lines and kept the driver's 53,084 MB
allowance. The system-memory read and the presents' watch were gated by now - last with last starting at INT64_MIN:
steady_clock counts from boot, so now - INT64_MIN overflowed (undefined; built, a large negative number), the gates never
opened, unified_sys.known stayed false and unified_budget::compute returned the driver's allowance. The reader itself works
in the sandbox (the giant gate's line in the same run read it).

unified_budget::Gate opens at its first ask whatever the clock reads, then at most every kReadMs (100) or kWatchMs (250);
a clock that went back opens it too. tests/unified_budget_test.cpp shows the old sum shut for good and the Gate's
openings; tests/test_unified_budget.py requires both Gates and refuses INT64_MIN or the old subtractions in vk_vma.cpp
(it fails on f23c9194).

Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 <[email protected]>
(cherry picked from commit 7009f858d1333c2ec72cc70e7d18b81c5f948b1d)
docs(macos): RX_VK_UNIFIED_BUDGET in the switch table
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 4s
CI / Workflow lint (actionlint) (pull_request) Successful in 6s
Third-party parts / As their notes say (pull_request) Successful in 9s
PR description / Description follows the template (pull_request_target) Successful in 3s
263e462d14
The unified-memory budget's off switch was named only in gpu/vk/VULKAN.md. docs/development.md's "Switches for
testing and debugging" now has its row beside RX_VK_VRAM_MB: what it turns off, the default rule and the log line.

Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 <[email protected]>
mooki changed title from WIP: fix(macos): on unified memory the GPU's allowance is what the Mac's RAM leaves, not Metal's working set to fix(macos): on unified memory the GPU's allowance is what the Mac's RAM leaves, not Metal's working set 2026-11-15 02:44:45 +00:00
mooki force-pushed mac/gpu-budget from 263e462d14
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 4s
CI / Workflow lint (actionlint) (pull_request) Successful in 6s
Third-party parts / As their notes say (pull_request) Successful in 9s
PR description / Description follows the template (pull_request_target) Successful in 3s
to ce758a263b
Some checks failed
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
Website / Website on Cloudflare (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 15s
CI / Workflow lint (actionlint) (pull_request) Successful in 23s
Guide screenshots / Guide images and local UI source review (pull_request) Failing after 21s
Third-party parts / As their notes say (pull_request) Successful in 33s
PR description / Description follows the template (pull_request_target) Successful in 5s
2026-11-15 07:14:37 +00:00
Compare
mooki force-pushed mac/gpu-budget from ce758a263b
Some checks failed
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
Website / Website on Cloudflare (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 15s
CI / Workflow lint (actionlint) (pull_request) Successful in 23s
Guide screenshots / Guide images and local UI source review (pull_request) Failing after 21s
Third-party parts / As their notes say (pull_request) Successful in 33s
PR description / Description follows the template (pull_request_target) Successful in 5s
to 09978824a0
Some checks failed
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
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
Third-party parts / The same as upstream (pull_request) Has been skipped
Website / Website on Cloudflare (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 3s
CI / Workflow lint (actionlint) (pull_request) Successful in 7s
Guide screenshots / Guide images and local UI source review (pull_request) Failing after 6s
Third-party parts / As their notes say (pull_request) Successful in 9s
PR description / Description follows the template (pull_request_target) Successful in 4s
Website / Website on Cloudflare (pull_request_target) Has been skipped
2026-11-15 13:09:46 +00:00
Compare
Author
Member

Folded into #846 (feat(macos): Vulkan on Metal: pinned host imports, the unified-memory budget, Metal's cache folder and KosmicKrisp)

Folded into #846 (feat(macos): Vulkan on Metal: pinned host imports, the unified-memory budget, Metal's cache folder and KosmicKrisp)
mooki closed this pull request 2026-11-15 18:07:41 +00:00
Some checks failed
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
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
Third-party parts / The same as upstream (pull_request) Has been skipped
Website / Website on Cloudflare (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 3s
CI / Workflow lint (actionlint) (pull_request) Successful in 7s
Guide screenshots / Guide images and local UI source review (pull_request) Failing after 6s
Third-party parts / As their notes say (pull_request) Successful in 9s
PR description / Description follows the template (pull_request_target) Successful in 4s
Website / Website on Cloudflare (pull_request_target) Has been skipped

Pull request closed

Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
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!642
No description provided.