fix(macos): on unified memory the GPU's allowance is what the Mac's RAM leaves, not Metal's working set #642
No reviewers
Labels
No labels
area/accessibility
area/audio
area/input
area/install
area/menu
area/renderer
area/saves
area/upscaling
beanstalk: approved @ 1697603
beanstalk: approved @ 2c5a62e
beanstalk: approved @ 4fd82b7
beanstalk: approved @ 64d6d09
beanstalk: approved @ 6f91fae
beanstalk: approved @ 70a78f9
beanstalk: approved @ 8d361dc
beanstalk: approved @ 9d8356e
beanstalk: approved @ abf2227
beanstalk: approved @ d82f202
beanstalk: approved @ e5f1e40
beanstalk: approved @ ee8ab5f
ci: approved
good first issue
gpu/amd
gpu/igpu
gpu/intel
gpu/nvidia
help wanted
kind
bug
kind
chore
kind
docs
kind
feature
kind
performance
kind
question
needs measurements
platform/linux
platform/macos
platform/steam-deck
platform/windows
preview
priority
critical
priority
high
priority
low
regression
status
blocked
status
confirmed
status
duplicate
status
invalid
status
needs-info
status
wontfix
testing wanted
ci: approved
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!642
Loading…
Reference in a new issue
No description provided.
Delete branch "mooki/Longshot:mac/gpu-budget"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
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_usageingpu/vk/vk_vma.cpp), so notexture 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 inreports/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 memoryline splits the GPU's memory into the renderer's blocks and Metal's and MoltenVK's own. All of itis inside
#if defined(__APPLE__).gpu/vk/system_memory.h(new), the one macOS reader: memory pressure level, memory available withoutcompressing or swapping (in the kernel's page size,
vm.pagesize, so it is right under Rosetta), RAM, swap and thisprocess's footprint. Elsewhere it reads nothing.
gpu/vk/unified_budget.h(new, no Vulkan):unified()(every heap device-local plus a device-localhost-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, andGate(open at the first ask, then at most every N ms; a clock step back opens it too).vram_usage()ingpu/vk/vk_vma.cppanswers the unified allowance, the system read at most every 100 ms undera mutex; prints the
vk: unified memoryline at the first ask and for each 1 GB it moves (at most one a second).unified_memory_watch()ingpu/vk/vk_texture.cppevery present (a driver read at most every 250 ms); idlestaging arenas are not kept while short.
gpu/vk/vk_memory.cpp: while short, spare capture buffers are not kept or made ahead. Spares are GPU-idle byconstruction; kept captures, textures in use, imports, streams and arenas in use are untouched.
gpu/vk/VULKAN.md(textures paragraph);docs/development.md(theRX_VK_UNIFIED_BUDGETrow, this PR's owndocs commit).
tests/unified_budget_test.cpp,tests/test_unified_budget.py: fake devices (MoltenVK on a 64 GB and a 16 GBMac, 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
Gateat clocks of 0, 1 s, 568,435 s and-5 ms, and source checks: the call sites, no
INT64_MINgate left invk_vma.cpp,vk_work.hstill 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 memoryline read the system (allowed10.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 deviceloss: 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_MINoverflow fixed byd16b3cc2), so it behaved as main does: thedriver'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 memorylines in longshot_log.txt over each run (run #23: 11 lines; #24: 13; #26: 48 over15 min in play); one run each
How to reproduce:
RX_VK_UNIFIED_BUDGET=0gives the before behaviour on the same build; read thevk: unified memorylinesRun 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.
RX_VK_UNIFIED_BUDGET=0: the GPU's allowance is the driver's againvk: unified memoryline=0only to compareTesting
Tested: on macOS 26.7.1, Apple M4 Max, at
263e462d, this PR's head before the rebases ontoe090cc48andaa95d35f(main
6c9edffeplus this PR):tests/test_unified_budget.pywith the native compiler (arm64) and withclang++ -arch x86_64(run under Rosetta): pass;on main
6c9edffewith 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_pressureandtest_preset_texture_budgetthroughtools/ls-suite.py: pass.test_mirror_trimfails as it does on main6c9edffeonmacOS (the SDK's
wait()againstmirror_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.cppcompiled for arm64 and x86_64 at this head with main's renderer flags(
nx2/build.py'svflags, plus-arch;reports/PubWave-files/gpu-budget/compile-263e462d/).git merge-treeof this head with each open macOS PR's head (#431, #437, #438, #479, #480, #490, #491, #492): noconflict.
play): the lines read the system throughout, clean ends, no device loss.
aa95d35f, at09978824(263e462dbefore the rebases ontoe090cc48andaa95d35f):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.pyon wave C1's merge2540345b, which carriesthis 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 thishead's version of every changed file, compared as tokens)
vk_memory.cppandvk_vma.cppare main's text, the two newheaders are included only inside
__APPLE__blocks, andvk_texture.cpp's only difference isconst 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 newname is inside an
__APPLE__block.Must be tested:
vk: unified memorylines whoseallowance 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_VERSIONchange.On macOS a new log line appears.
Reviewer notes
1326bfac..263e462d(5 commits): the system-memory reader (shared with the giant-compile memory gate, a macOS PR tofollow that stacks on this one), the rule,
vk_work.hkept asvk_vma.cpp's last include (test_recorder_sync), theread-gate fix after run #23, and the docs row.
6c9edffe; no dependency. The four topic commits were cherry-picked with-xfrommacos/gpu-budgetwith noconflict (
vk_vma.cppmerged automatically). The reader's header comment already names its other users andtests/giant_memory_test.cpp, which come with the giant-compile memory gate PR.git merge-tree).Issues
See #420
Checklist
Everyone
type(scope): description) of the status line's type.main: onaa95d35f(#761, 2026-11-15), head09978824, with one conflict:docs/development.md, #735's newRX_VK_VRAM_MBrow kept and this PR'sRX_VK_UNIFIED_BUDGETrow after it(
texture_budget.hstill reads the unified allowance); this PR's own commits, old → new:1326bfac→ba195209,68850004→632636ee,e27f7ea7→0e63a1dc,d16b3cc2→5240eade,263e462d→09978824; the commit nameselsewhere in this description are from before the rebase.
python -I -B tests/run_all.pypasses (any failure that also fails onmainis named). (At263e462d: 531 passed, 33 failed, 17 skipped, against main 6c9edffe's 531/32/17 on this Mac: the 32 also fail on main,test_unified_budgetis new and passes, andtest_gpu_hang_watchfailed 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 mainaa95d35f(head09978824): wave C1's merge2540345b, 556/33/17 against main aa95d35f's 543/33/17, no new failure.)third_party/untouched (a new folder with aPROVENANCE.mdinstead).When it applies
Assisted-by:/Co-Authored-By:trailers.mainand passing here.tests/that reads changed source updated in this PR, not weakened.WIP: fix(macos): on unified memory the GPU's allowance is what the Mac's RAM leaves, not Metal's working setto fix(macos): on unified memory the GPU's allowance is what the Mac's RAM leaves, not Metal's working set263e462d14ce758a263bce758a263b09978824a0Folded into #846 (feat(macos): Vulkan on Metal: pinned host imports, the unified-memory budget, Metal's cache folder and KosmicKrisp)
Pull request closed