feat(macos): RX_VK_METAL_CACHE_DIR keeps Metal's shader cache in a chosen folder #653
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!653
Loading…
Reference in a new issue
No description provided.
Delete branch "mooki/Longshot:mac/metal-cache"
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: feat · Risk: low · Tested on: macOS 26.7.1, Apple M4 Max (MoltenVK 1.4.2) · Needs: a windowed macOS start
Why
On a Mac, Metal keeps the GPU code it compiles for the game's shaders in a per-user folder under
/var/folders, outside thegame folder. A start that runs in a sandbox which allows only the game's own folder (as the macOS port's test runner does)
cannot open that cache: Metal compiled every pipeline again on every start, and the log filled with
fopen failed for list file: errno = 1(2,489 such lines in one start, about 1.5 per compile). There was no way to pointMetal elsewhere: Metal has no cache-path variable, and the per-user folder ignores
HOME,TMPDIRandCFFIXED_USER_HOME. This PR adds an opt-in switch that tells Metal which folder to use. It is part of the macOS port. See#420.
What changed
RX_VK_METAL_CACHE_DIR=<folder>(macOS only, off by default) hands that folder to Metal before the Vulkan library isloaded, and after the device is made logs whether Metal took it. Unset or empty, nothing is loaded, called or logged.
Every refusal is logged once and the start goes on as without the switch. All of it is inside
#if defined(__APPLE__).gpu/vk/metal_cache.h(new).ask()checks the folder (absolute, at most 1,023 bytes, an existingdirectory: the game never creates it), looks up Metal's exported
MTLSetShaderCachePath/MTLGetShaderCachePathandCoreFoundation's string calls with
dlopen/dlsym, and makes the call with a CFString it releases.check()comparesMTLGetShaderCachePath()with the folder aftercreate_device. No new link dependency and no Objective-C.gpu/vk/vk_main.cpp:load_global()'s macOS branch asks right beforemkwvk::host->vulkan_library()(Metal fixes its folder on first use and silently ignores a later call), and
mkwvk_init()checks right aftercreate_device. Log lines:vk: Metal shader cache: asked for <folder> (…), thenvk: Metal shader cache: <folder> (MTLGetShaderCachePath, as RX_VK_METAL_CACHE_DIR asked), orvk: RX_VK_METAL_CACHE_DIR NOT applied: ….docs/development.md, theRX_VK_METAL_CACHE_DIRrow.tests/test_metal_cache_dir.pycompiles the realload_global()and header against made-up Metal,CoreFoundation and host loader calls on any OS.
MTLSetShaderCachePathis Apple SPI: exported by Metal and listed in the SDK'sMetal.tbd, declared in WebKit'sMetalSPI.hand used by Chromium, but in no public header. If a macOS update removes it, the switch logsNOT appliedandthe start compiles as it does without the switch.
Measurements
Verdict: works: Metal took the chosen folder in every start that set it (game runs #5-#9, arm64 and x86_64 under
Rosetta), and the denied cache opens went from about 1.5 per compile to 0. In a game-free MoltenVK probe a second process
made each pipeline from that folder 83× faster (2.6 ms against 214.8 ms), and even the first process compiled about twice as
fast as with Metal's folder denied (215 against 395 ms). Not measured in the game against main: main does not run on macOS.
Baseline: main does not start on macOS, so the game columns are the macOS port's integration builds, arm64, MoltenVK
1.4.2, the runner's sandbox (which denies
/var/folders): before is run #4 onaefe6b0a, the last build without thiscommit; after are runs #5-#8 on
f32adfa2anda7711f3c, which carry it (the topic'sd7586df3) and set the switch to afolder in the run tree. The probe below is the same call made from a standalone MoltenVK process, with the switch's two
"before" cases (unset, and the call made after
vkCreateDevice, which Metal ignores).Hardware: macOS 26.7.1 (25G241), Apple M4 Max (40-core GPU), MoltenVK 1.4.2, 16-core CPU, 64 GB
Scene: game runs #4-#8 from a fresh profile (#6 and #8 warm: seeded from #5 and #7), windowless, to 180 s of preparation;
the probe: 24 distinct synthetic compute pipelines per process, no
VkPipelineCacheMethod: game runs: the renderer's two
vk: Metal shader cachelines, the OS log'serrno = 1count and the runner'spublished Metal files, one run each; probe
reports/MetalCacheDir-files/probe/runs-01(onegpuslot admission,2026-11-14 07:52 UTC, 2 processes per route, A then B; per-pipeline times from each process's own
SUMMARYline, themedian over 24 pipelines;
run.json323d4ef4…)How to reproduce: set
RX_VK_METAL_CACHE_DIRto an existing folder and readlongshot_log.txt'svk: Metal shader cachelines; the probe:
reports/2026-11-14-metal-cache-dir.md§5 (the exactdriver.py runcommand)vk: Metal shader cachelinesfopen failed for list file: errno = 1functions.data235,225,088 B,libraries.data404,455,424 Bfunctions.data379,764,736 B,libraries.data603,750,400 BThe probe (
metal-cache-probe.cpp, MoltenVK 1.4.2 compute pipelines, process A then B):default)setpath-late)setpath)clonefile(carried)MTLGetShaderCachePathin A / BRun reports:
reports/2026-11-14-metal-cache-dir.md(§1 the denied opens, §6 the probe),-metal-cache-impl.md,-mac-startup-05.md(#5 cold, #6 warm),-mac-startup-07.md(#7 cold, #8 warm: the carried folder opened with noinvalidation),
-mac-startup-09.md(#9, x86_64 under Rosetta). The lines above are quoted from them (renderer lines andcounts only).
What it does not do: Metal holds at most 1 GiB per cache file and never evicts what the running start itself touched, so a
start whose pipelines pass 1 GiB stores nothing after that, folder or not (runs #17-#22 reached the cap:
functions.data/libraries.dataabout 1.12 GB;reports/2026-11-14-metal-cache2.md§1). The follow-up #667keeps MoltenVK's pipelines in Metal binary archives in a second folder for that.
Flags and settings
One new switch, macOS only, off by default (the owner's decision: keep it opt-in).
RX_VK_METAL_CACHE_DIR=<folder>/var/foldersNOT appliedline and unchanged/var/folders, caches carried between runs (measured); doesn't help: a normal player start, whose per-user folder already works; untested: a start that made a window first (SDL), other macOS buildsTesting
Tested: on macOS 26.7.1, Apple M4 Max, at
fc8ee75d, this PR's head before the rebases ontoe090cc48andaa95d35f(#438 at
0f2262a3plus this commit):tests/test_metal_cache_dir.py --cxx /usr/bin/clang++(arm64) and--cxxclang++ -arch x86_64(the fixture run underRosetta): pass. The same test pointed at the base
0f2262a3(--source) fails on both arches, and at main6c9edffeon arm64 (header missing, no ask in
load_global, no check inmkwvk_init, no docs row). Logs:reports/PubWave-files/PubMetal/proof/mc-*.log.fails the test,
reports/MetalCacheImpl-files/negative-*.log) carry over: this commit's added and removed lines, itstest, its header and
load_global()'s block are byte-identical to the topic'sd7586df3(checked,reports/PubWave-files/PubMetal/mc-non-apple-text.txt).vk_main.cppcompiled for arm64 and x86_64 with main's renderer flags (nx2/build.py'svflagsplus-arch):exit 0 both; each object holds the switch and both SPI names (
reports/PubWave-files/PubMetal/objects-mc/).vk_main.cpp's Linux and Windows text (every__APPLE__block resolved away) is byte-identical to0f2262a3, and the newmetal_cache.his included only inside an__APPLE__block: Windows and Linux compile exactly what they compile without thisPR (
unifdef -U__APPLE__token check,reports/PubWave-files/offapple/metal-cache.txt), so the host-build box does not apply.tools/ls-suite.py:test_metal_cache_dir,test_vk_darwin_admission,test_renderer_load_error,test_gpu_choice,test_vendor_fixes,test_load_default,test_log_levels,test_check_pr_description,test_dev_packspass;test_crash_dialogfails as it does on main6c9edffeon macOS(
reports/PubWave-files/PubMetal/named-mc/).each time (no
NOT appliedline); #6 and #8 opened the folder carried from #5 and #7 with no invalidation. Later runnerstarts (#21, #22) kept setting it; their Metal files reached the 1 GiB cap described above.
aa95d35f, atdb50cf3d(fc8ee75dbefore 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: a start that creates its window before the renderer loads Vulkan (every run here was windowless; if SDL
already used Metal, the check logs
NOT applied); macOS versions other than 26.7.1; an Intel Mac. Windows and Linux were notbuilt or run, and need nothing: this PR's own range is main's text there (
vk_main.cppthe same off Apple,metal_cache.hincluded only inside
__APPLE__;reports/PubWave-files/offapple/metal-cache.txt), and #438, which this branch carries, hasits own test request.
Must be tested:
RX_VK_METAL_CACHE_DIRset to an existing empty folder, expectingvk: Metal shader cache: <folder> (MTLGetShaderCachePath, …)and files in the folder after quitting (by hand: a Mac tester, the log and thefolder).
Risks
What could break: only starts that set the switch on macOS: a future macOS without the SPI, or a host that used Metal
before the renderer, logs
NOT appliedand compiles as without it. With the switch unset nothing runs.Turning it off: leave
RX_VK_METAL_CACHE_DIRunset (the default), or revert the PR.Compatibility: Nothing changes for players: the switch is off by default and Apple-only.
vk_main.cppandmetal_cache.hare not hashed into the shader stamp, so no cache is translated again; noMKWVK_API_VERSIONchange.Reviewer notes
0f2262a3..fc8ee75d(one commit, cherry-picked with-xfrommacos/metal-cached7586df3with no conflict).load_global()'s macOS branch andmkwvk::host->vulkan_library()come from it. Thebranch carries #438's four commits (
6c9edffe..0f2262a3) until #438 merges; then rebase withgit rebase --onto main 0f2262a3 mac/metal-cache.metal_cache.h'sask()andcheck(), then the two call sites invk_main.cpp.6c9edffe: none. With the open macOS PRs (git merge-tree): none with #431, #437, #479, #490, #491,#492; #480 conflicts in
host/posix/win32_posix.c, which comes from #438's commits, not from this one (#438's own headmeets the same conflict). With later PRs: #651 (renderer threads' 16 MiB stack) edits
vk_main.cppnear these lines andrebases after this one (textual only). #667 stacks on this PR.
Issues
See #420
Checklist
Everyone
type(scope): description) of the status line's type.main: onaa95d35f(#761, 2026-11-15), headdb50cf3d, on #438681e3708, without aconflict; this PR's own commits, old → new:
fc8ee75d→db50cf3d; the commit names elsewhere in this descriptionare from before the rebase.
python -I -B tests/run_all.pypasses (any failure that also fails onmainis named). (On the wave's merge of every macOS PR built with this one,453eb678: 553 passed, 32 failed, 18 skipped against main 6c9edffe's 531/32/17 on this Mac; the failures are main's own excepttest_split_ub_flush, which #658's tree at the time broke and its head fixes;test_collect_widenow passes; the new tests pass andtest_darwin_audio_service, Darwin-only, is skipped by the runner;reports/PubWave-files/waveA/. After the rebase onto mainaa95d35f(headdb50cf3d): 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.WIP: feat(macos): RX_VK_METAL_CACHE_DIR keeps Metal's shader cache in a chosen folderto feat(macos): RX_VK_METAL_CACHE_DIR keeps Metal's shader cache in a chosen folderfc8ee75dc7c39e0fb4e7c39e0fb4e7db50cf3d26Folded into #846 (feat(macos): Vulkan on Metal: pinned host imports, the unified-memory budget, Metal's cache folder and KosmicKrisp)
Pull request closed