WIP: fix(macos): Metal's shader cache folder asked at the host's start, before SDL's window #786
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!786
Loading…
Reference in a new issue
No description provided.
Delete branch "mooki/Longshot:mac/windowed-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: fix · Risk: low · Tested on: macOS 26.7.1, Apple M4 Max (MoltenVK main 5189f8de with the argbuf-slots patch), game-free on arm64 and x86_64 under Rosetta; GPU probe WC-01 on arm64 · Needs: macOS, Apple silicon (a windowed start with the switch set); Windows and Linux, any GPU (build and start)
Why
RX_VK_METAL_CACHE_DIR(macOS only, off by default, #653) asks Metal to keep its shader cache in a folder the user namesinstead of its own per-user folder. Metal fixes that folder the first time anything in the process uses it, and in a windowed
start the renderer asked only after SDL had started its video, loaded the Vulkan library and made the window: by then the
folder was fixed, and the switch was lost (
vk: RX_VK_METAL_CACHE_DIR NOT applied: …). Players are not affected: nothingsets the switch for them, and Metal's own folder, which is writable outside a sandbox, caches a player's windowed start as it
does a windowless one. It matters to whoever sets the switch, as the Mac's test runner does: its sandbox denies Metal's
default folder, so the first windowed run (#35) cached nothing anywhere and its precompile took 1,464 s against 391 s for the
windowless run before it (#32). This PR asks Metal for the folder in
main(), right afterlongshot_env.txtand the Vulkandriver's choice, before SDL, the Vulkan library or any other thread, on macOS only. It is part of the macOS port. See #420.
How to reproduce
Seen on: macOS 26.7.1, Apple M4 Max, MoltenVK main 5189f8de with the argbuf-slots patch; main does not start on macOS
yet: seen on the macOS port's integration build 3f872b0c (run #35), which carries the open macOS PRs
Settings and switches:
RX_VK_METAL_CACHE_DIR=<an existing folder>(the runner sets it), windowedSteps: start the game in its window with the switch set, and read the Metal cache line after the device is made.
Expected:
vk: Metal shader cache: <the folder> (MTLGetShaderCachePath, as RX_VK_METAL_CACHE_DIR asked), and the nextstart warm from it.
Actual: run #35:
vk: RX_VK_METAL_CACHE_DIR NOT applied: Metal's shader cache is /var/folders/<…>/C//com.apple.metal, not …/cache/metal (Metal chose its folder before the switch was applied); nothing cached (reports/2026-11-15-windowed-metal-cache.md§1-2).
What changed
Only where and when the folder is asked for changes; the logic and its log lines are the same. Unset or empty, nothing is
loaded, called or logged, as before. Windows and Linux compile the same text as before.
host/hos_main.cmain():hos_darwin_metal_cache_ask()inside the macOS block, right afterhos_darwin_vulkan_choose_driver(): afterlongshot_env.txt(which may set the switch), beforehos_display_init(SDL),the game's thread, the window and anything that loads the Vulkan library.
--prepare-shadersand headless starts pass the samepoint.
host/posix/darwin_metal_cache.h(new;gpu/vk/metal_cache.hmoved withgit mvand written in C): the checks, thedlopen/dlsymlookups, the CFString handling and every log string as they were; the path a 1,024-byte array (at most1,023 bytes, as before).
host/posix/darwin_vulkan.{c,h}:hos_darwin_metal_cache_ask()keeps what it asked;hos_darwin_metal_cache_check()logs the result on the renderer's thread, which starts after the ask.
gpu/vk/vk_api.h,host/rx_backend.c: a new macOSMkwVkHostslot,metal_cache_check, appended after #686'sguest_backing_generation: Apple host API version 26 (#686 took 25), offset 400, size 408 (5c37ec10).gpu/vk/vk_main.cpp:load_globalno longer asks;mkwvk_initcallshost->metal_cache_check()wheremetal_cache::checkwas, aftercreate_deviceand beforemetal_archive::begin.metal_archive.h's comment names the newheader.
docs/development.md, theRX_VK_METAL_CACHE_DIRrow says where the game asks and why.tests/test_metal_cache_dir.pycompiles the host's real ask and check (cut fromdarwin_vulkan.c) and the header(also as C11 with
-Werror) against made-up Metal and CoreFoundation calls, and checksmain()'s order, the renderer's checkafter
create_device, the macOS-only slot (version 26) and its wiring;tests/display_startup_failure_test.cppruns thereal
main()and requires exactly one ask, afterlongshot_env.txtand the driver's choice, before any SDL load or init,window, thread or offscreen start; the ABI fixtures (
test_vk_darwin_admission.py,darwin_renderer_startup_test.cpp) takethe API 26 layout and call the wired slot;
test_darwin_renderer.pyandtest_metal_archive.pyfollow the moved check.Measurements
Verdict: fixed where measured. Probe WC-01 replayed a windowed start's own steps (SDL's video, MoltenVK and SDL's Vulkan
library, the window, its events, then the device and two pipelines) in a sandbox shaped like the runner's, with this PR's
real host ask and check: asked first, as
main()now asks, Metal took the folder and wrote its cache there; asked where therenderer asked in #35, after the window, it did not. Asked after SDL's video start or after the Vulkan library it still took
it: SDL's window creation is the step that fixes Metal's folder. Run #35's os_log agrees (the renderer's thread was the first
to open Metal's cache files, after the main thread's window). For whoever sets the switch, a windowed start should then be as
warm as a windowless one: the start's time is in the table.
Baseline: main does not start on macOS. Both "before" columns are the integration build 3f872b0c (I38), arm64, without this
change: #35 windowed, #32 windowless (the switch applied there), each with the runner's sandbox and the switch set.
Hardware: macOS 26.7.1 (25G241), Apple M4 Max (40-core GPU), 16-core CPU, 64 GB
Scene: a start from the field-dawn save (runs #32, #35): the preparation, the precompile, the title
Method: the Metal cache line of the game log, and the run's Metal and libCoreFSCache os_log lines counted by text (
log showover #35's pid, 01:06:43-01:36:00; #32's as the RosettaCache lane counted them); one run each. WC-01: six routes, one process
each, the real
hos_darwin_metal_cache_ask()/_check()anddarwin_metal_cache.h(blob-identical at this head) made at eachroute's point,
MTLGetShaderCachePathread after the device and the route's folder listed; released once on arm64 (05:02 CST)How to reproduce: a windowed start with
RX_VK_METAL_CACHE_DIRset, readingvk: Metal shader cache/NOT applied; WC-01'sbuildandruncommands (reports/2026-11-15-windowed-metal-cache.md§4)sdl: SDL3at line 3)windowless(first; no SDL: #32's order)fixed(first, then SDL and the window: this PR's order)after-init(after SDL's video start)after-vklib(after MoltenVK's and SDL's Vulkan library load)after-window(afterSDL_CreateWindow)/var/folders/<…>//com.apple.metaltoday(after the window and 1 s of events: #35's order)/var/folders/<…>//com.apple.metalReports:
reports/2026-11-15-windowed-metal-cache.md(the order of a windowed start, what a player's start caches, the fix,WC-01),
reports/2026-11-15-mac-startup-35-windowed.md,reports/2026-11-14-mac-startup-32.md.Flags and settings
None new. One switch is asked earlier:
RX_VK_METAL_CACHE_DIR, off by default. The diff against main also shows the switchesof the PRs it stacks on (#667's, #686's and #654's stacks), theirs to list:
HOS_KEEP_FREED_PAGES,HOS_ZERO_ALL_PAGES,LONGSHOT_DARWIN_ARCHIVE_RECEIPT,LONGSHOT_DARWIN_ARCHIVE_RECEIPT_SHA256,LONGSHOT_FFMPEG,LONGSHOT_FSR,LONGSHOT_KOSMICKRISP,LONGSHOT_MOLTENVK,LONGSHOT_NGX_SDK,LONGSHOT_SDL3,LONGSHOT_VULKAN_LOADER,RX_ARCH,RX_NVN_WATCH_AFTER,RX_NVN_WATCH_ALL,RX_NVN_WATCH_TEXELS_SRC,RX_VK_ALONE_TRIES,RX_VK_COMPILER_SERVICE,RX_VK_COMPILE_QOS,RX_VK_DLSS_ACTORS,RX_VK_DRIVER,RX_VK_GIANT_COMPILES,RX_VK_GIANT_HOLD_PRESENTS,RX_VK_GIANT_HOLD_S,RX_VK_GIANT_WORDS,RX_VK_GPU_DUMP,RX_VK_LIBRARY,RX_VK_MESH_IND_COUNT_WORD,RX_VK_METAL_ARCHIVE_DIR,RX_VK_NO_UPSCALE_STUB,RX_VK_ONLY,RX_VK_SKIP_LOG,RX_VK_SKIP_PROG,RX_VK_SLOW_COMPILE_S,RX_VK_WORKER_CACHES.RX_VK_METAL_CACHE_DIR=<folder>load_global: lost in a windowed startmain()before SDL: applied in windowed and windowless startsTesting
Tested: on macOS 26.7.1, Apple M4 Max:
e090cc48, #667 with #686 and #654 merged, this PR), every run under the shared check slot with a freshHOME and TMPDIR (
reports/PubWave-files/PubLate5/StartTrio/proof/wmc-*):test_metal_cache_dir(ok: source: asked in main() after longshot_env.txt and the driver's choice, before SDL's display and any thread, macOS only; …),test_display_startup_failure(11 cases, 0 failed),test_vk_darwin_admission(Apple version 26, size 408, typed callbacks at 360/368/376/384/392/400),test_darwin_renderer(Apple26 typed host callback wiring, both arches in one run),test_metal_archive.425fba24with this PR's test files, exit 1 on each:host/posix/darwin_metal_cache.h is missing;11 cases, 11 failed(Metal's shader cache folder not asked once at startup);FAIL: Apple host API version is 26;no member named 'metal_cache_check' in 'MkwVkHost';test_metal_archive's source check not findinghost->metal_cache_check();.test_crash_recordsource_contract()alone (hos_main.cchanges): passed. The bases' own checks on the mergedtree:
test_darwin_guest_memory(both),test_vk_darwin_sdl,test_vendor_fixes(arm64, x86_64),test_thread_stack(both),test_loading_stall,test_import_hold(arm64): exit 0.gpu/vk/vk_main.cppand the renderer files the base's merges combine (vk_draw.cpp,vk_memory.cpp,vk_replay.cpp,vk_texture.cpp) with the tree's own renderer flags, 10 of 10;host/hos_main.c,host/posix/darwin_vulkan.candhost/rx_backend.cwith the macOS host flags (#490, #479, #476, #492 merged in a scratchtree only), 6 of 6; and
host/posix/win32_posix.c(no change of this PR's, but new to all three bases: the merges combine#686's and #654's), 2 of 2 with #490's side of its include hunk, as for #491 and #658 before (a scratch merge that keeps the
branch's side drops #490's Mach includes) (
…/StartTrio/compile/windowed-metal-cache-own/,…-host/).macos/windowed-metal-cachec77008a8, API 25, on the integration branch79e2e006): the two orderchecks failed on
79e2e006(darwin_metal_cache.h is missing; 11 of 11 startup casesnot asked once at startup) and on 4mutations (the ask after
hos_display_init, the check beforecreate_device, the string released beforeMTLSetShaderCachePath, the slot unwired); 8 of 8 whole-file compiles (hos_main.c,rx_backend.c,darwin_vulkan.c,vk_main.cpp, arm64 and x86_64) with 0 warnings; the object link check 0 failures; full suite 585 passed, 1 failed, 16skipped, by name the base's (
test_menu_i18n) (reports/WindowedCache-files/).unifdef -U__APPLE__, base against head): every changed file's text is the base's (metal_archive.hin a comment);metal_cache.h(Apple-only) is gone anddarwin_metal_cache.his new, neither included off Apple(
reports/PubWave-files/PubLate5/StartTrio/offapple-windowed-metal-cache.txt).eda365d7…, build4f53e8b5…; released once on arm64 by the parent, 05:02 CST):reports/WindowedCache-files/probe/runs-01/result.json(sha25652524f95b04516267adf66ac07be6006b0a0da284a4cd8c7f3741a7d34748975):fix_proventrue (the windowless control and this PR'sorder applied, with Metal's cache files in their folders; #35's order not applied, its folder empty),
first_touch_afterafter-window(SDL's window creation fixes the folder), no new crash report. Its host code is this head's:darwin_metal_cache.hand
darwin_vulkan.care blob-identical atc77008a8, which it was built from, and here.Not tested: Rosetta in the game (WC-01 and run #36 are arm64); Windows and Linux: not built there (their text is the base's);
its test request covers them. A windowed game start on the integration carrying this PR, run #36, found Metal's cache warm, as
in the table. WC-01 has passed, so no probe holds this PR in WIP any more; the Testing box waits for the test requests.
Must be tested:
start of the same tag length), expecting the ask line right after
vk: Vulkan driver:, thenvk: Metal shader cache: …/cache/metal (MTLGetShaderCachePath, as RX_VK_METAL_CACHE_DIR asked)after the device, and a precompile like #32's (by hand: the parent'sgame run)
5c37ec10, it carries #667, #686, #654 and what they carry) and #686's head thesame way and start each to the title, expecting every line to match #686's (by hand: test request T-XXXX)
Risks
What could break: on macOS only, and only with the switch set: Metal's shader cache path is set from
main()before SDL(the same calls and checks as before, earlier), and the renderer's check goes through a host slot. Unset or empty, nothing
runs. Players, who do not set it, see no change.
Turning it off: leave
RX_VK_METAL_CACHE_DIRunset (the default), or revert the PR.Compatibility: the Apple host ABI moves to version 26 (size 408): the macOS executable and its renderer library must be
built together, as with every ABI step (on a mismatch
rx_backend.cdoes not take the Vulkan renderer). Windows and Linux keepversion 22, size 360. No shader-stamp file changes; nothing changes in saves,
longshot_options.inior the caches.Reviewer notes
425fba24..5c37ec10, two commits: the topic picked with-xfrommacos/windowed-metal-cache(c77008a8→9ca31cd8)and the API end commit (
5c37ec10). Readmain()'s macOS block andhos_darwin_metal_cache_askfirst, thendarwin_metal_cache.hagainst themetal_cache.hit replaces (the same logic in C).this stack #686 took 25 for the backing generation (offset 392, size 400). The pick keeps #686's numbers where they met
(
vk_api.h's define,test_vk_darwin_admission.py's offsets and line, the startup fixture's layout check);5c37ec10thenmakes the slot version 26 at offset 400, size 408, in
vk_api.h, both ABI fixtures andtest_metal_cache_dir.py's check (thepick alone fails the two ABI checks on the layout, as that commit says). The tree's other users of
MKWVK_API_VERSIONcheckat least a version (
test_fps_stats,test_menu_text_field) and pass at 26; the docs name no Apple version. Any other PR thatappends to the Apple host table renumbers after whichever lands first.
metal_cache.hand the archives' begin it moves around, and through it #653 and #666's stack), #686 (the Applehost layout this appends to, and #658 below it) and #654 (
main()'s macOS block with the Vulkan driver's choice, which the askfollows and the startup test orders it against,
darwin_vulkan.{c,h}; and #479, #453, #437, #438 below it). The branch is#667's head
bf5be681with #686's heada0027de2merged (1c35980c:vk_draw.cpp'sdispatch(), #658's stand-in admissionwith #667's stack's named skips, as the integration branch
79e2e006has it), then #654's head7872c6e7(425fba24:docs/development.md, theRX_VK_DRIVERrow before the Metal rows, as79e2e006).test_vk_darwin_admission.pyonly (#686's layout, above); every other file is the topic's change line forline.
git merge-tree): none of this PR's own commits beyond its bases'. Inherited:gpu/vk/vk_draw.cpp'sdispatch()with #784 and #669 (the base merges'),docs/development.mdwith #785 (rows: keepboth),
display_startup_failure_test.cpp,win32_posix.c,test_collect_wide.pywith #490 (#654's and #686's).35465d12): inherited from the bases (docs/development.md,vk_draw.cpp,test_pipeline_play_cache.py);none of this PR's own.
reports/2026-11-1*.md(the port's design notes), which are not in this repository.Issues
See #420
Checklist
Everyone
type(scope): description) of the status line's type.main. (On maine090cc48through #667bf5be681, #686a0027de2and #6547872c6e7; main has moved since (35465d12): a delta sweep follows.)python -I -B tests/run_all.pypasses (any failure that also fails onmainis named). (On wave Sc08ae52d= #784694edb10with this head5c37ec10and #785a4d31a59merged: 586 passed, 31 failed, 18 skipped against main e090cc48's 544/32/17 on this Mac; the 31 failures are main's own and nothing that passes on main fails;test_collect_widenow passes; 41 new tests pass;reports/PubWave-files/PubLate5/wave/full-S-c08ae52d/.)third_party/untouched (a new folder with aPROVENANCE.mdinstead).When it applies
Assisted-by:/Co-Authored-By:trailers.mainand passing here. (They fail on this PR's base with this PR's test files; main has no Metal cache switch.)tests/that reads changed source updated in this PR, not weakened.MKWVK_API_VERSIONbumped (gpu/vk/vk_api.h'sMkwVkHostorMkwVkRendererchanged). (Apple 25 → 26 for the new host slot; non-Apple unchanged at 22.)7707c634) under the precompile hand-off c19fe5a59dEvery scan down vk_memory.cpp's import table by start (pool_lookup's covering import and merge plan, bg_drain, prefetch_chunks, import_in_background, memory_note_gpu_write and memory_guest_imported) stopped once an import ended more than 1 GiB below its address, on the belief that no import is larger than that. pool_lookup's own merge escape leaves an import above 1 GiB over the smaller ones inside it ("left overlapping"), and the device allows imports up to max_import_size: a scan stopped at a small import inside a large one, missed the large one, and the same bytes were imported again, two memories over the same bytes. import_reach.h counts the live imports by span class; its bound is above every live span, within twice the largest. imported_add and imported_take, now the table's only ways in and out, keep the count. A scan stops only where no import further down can reach its address, so it also stops sooner than before when the imports are small. The covering-import checks in pool_lookup and import_in_background look past the last import at or below the range for the same reason. Tests: test_import_reach.py runs the actual scans with a 4 GiB import over a 16 MiB chunk, left by the merge escape (imports are recorded ranges: no payload, no Vulkan, no game). Its lookup, query, note, drain, background and prefetch cases fail on main5e072fc8and pass here, and a source check keeps every change to the table in the two helpers. test_import_windows runs pool_lookup with the helpers; test_mirror_gpu_only names the new publication point. Assisted-by: Claude Opus 5.5 Co-Authored-By: Claude Opus 5.5 <[email protected]>3cd25d0e) under the Metal archives 8abb99e0b7044635ea added vma_startup_rollback (vk_vma.cpp, run after a failed replay_start), which calls vmaDestroyAllocator. vk_vma.cpp includes vk_work.h last so that RX_VK_RECORDER=defer/thread drains before its destructions, and every such boundary call in a vk_work.h file must be in RX_REC_BOUNDARIES with a drain-first macro. This one was not, and test_recorder_sync caught it ("vk_vma.cpp calls ['vmaDestroyAllocator'], which vk_work.h does not drain before"): it passes at 58c5c5aa (044635ea's parent) and fails at 044635ea. Add it to the list and to vk_work.h, next to vmaDestroyBuffer and vmaDestroyImage. With the recorder off (the default) boundary() is a relaxed load and a branch. Rollback runs before any image or buffer exists, so nothing should be held there today, but the allocator is no longer destroyed while a recorder could still hold packets. The test is unchanged (sources: 42 boundaries drained, was 41). Assisted-by: Claude Opus 5.5 Co-Authored-By: Claude Opus 5.5 <[email protected]>Carries the vk-admission pull request's commits, which the packaged driver choice needs (host/posix/darwin_vulkan.{c,h} and test_vk_darwin_admission). #479 (mac/build-complete, this branch's start) brings darwin_build.py and darwin_package.py; the two merge without a conflict. Assisted-by: Claude Opus 5.5 Co-Authored-By: Claude Opus 5.5 <[email protected]>a0027de2, #686) into Metal's binary archives (mac/metal-cache2bf5be681, #667) 1c35980c577872c6e7, #654) beside the Metal cache and the Apple host API 425fba241f5c37ec101d976bb0ac6dFolded into #849 (fix(macos): MoltenVK's precompile and pipelines on Metal)
Pull request closed