WIP: feat(macos): Vulkan on Metal: pinned host imports, the unified-memory budget, Metal's cache folder and KosmicKrisp #846
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
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!846
Loading…
Reference in a new issue
No description provided.
Delete branch "mooki/Longshot:mac/topic-vulkan-on-metal"
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: medium · Tested on: macOS 26.7.1, Apple M4 Max (arm64; x86_64 under Rosetta) · Needs: Windows and Linux, any GPU
Why
On a Mac the game's Vulkan renderer runs on Metal through a driver (MoltenVK, or KosmicKrisp through the Vulkan loader), and
four things it needs there were missing. Guest memory imported into Vulkan could be replaced or discarded while the GPU still
used it, and a renderer whose startup failed part-way left what it had made behind (on every platform). The GPU has no memory
of its own on a Mac, yet the renderer took Metal's recommended working set (53 GB on a 64 GB Mac) as its allowance, so nothing
gave memory back while the Mac ran out (run #22 lost the device). Metal keeps its compiled shader cache under
/var/folders,which a sandboxed start cannot open, so every start compiled every pipeline again. And the macOS package carried MoltenVK
only, so KosmicKrisp, LunarG's conformant Vulkan 1.4 driver on Metal, could not be measured. This PR is that layer of the macOS
port, on top of the macOS host (the topic before it). See #420.
What changed
Every macOS host import is pinned in the executable until its last Vulkan owner is gone, and a failed startup gives back what
it made (that rollback, the recorder's drain before it and the move-only import owners apply on every platform); on a Mac's
unified memory the renderer's GPU allowance follows what the Mac has free;
RX_VK_METAL_CACHE_DIRpoints Metal's cache at achosen folder; and the macOS package can carry the Vulkan loader with MoltenVK (the default) and KosmicKrisp (opt-in,
RX_VK_DRIVER=kosmickrisp). The four original pull requests, in commit order:c1fa25a4; Was: #491):macOS:
DarwinImportBackingand the move-onlyDarwinImportViewingpu/vk/vk_memory.cpp(range arithmetic in the newgpu/vk/darwin_import.h) pin each import's whole backing through two newMkwVkHostcallbacks (gpu/vk/vk_api.h, AppleMKWVK_API_VERSION25; Windows and Linux keep main's 23), filled byhost/rx_backend.c;host/posix/win32_posix.c'swaveOutOpenresolves the executable's audio service (host/hos_sdl.c) instead of a weak import. Every platform:replay_start_rollback()ingpu/vk/vk_replay.cppandvma_startup_rollback()ingpu/vk/vk_vma.cppunwind a failedstartup;
vmaDestroyAllocatoris a recorder drain boundary (gpu/vk/vk_recorder.h,vk_work.h); import pools are moved,not copied, with one
destroy_import(); on LinuxMessageBoxAcloses its extradlopenreference andwaveOutOpenchecksits allocations. Tests:
tests/test_darwin_renderer.py,tests/test_darwin_audio_service.py(new); eight existing checksthat build
vk_memory.cppor the audio route follow the movedPooland the new callbacks.45e1d666; Was: #642): on aMac's unified memory
vram_usage()ingpu/vk/vk_vma.cppanswers the least of the driver's allowance, RAM less thisprocess's CPU memory less a reserve, and what the GPU holds plus what the system has available less a margin
(
gpu/vk/system_memory.handgpu/vk/unified_budget.h, new); spare capture buffers (vk_memory.cpp) and idle staging(
unified_memory_watch()invk_texture.cpp) are not kept past 90% of it; avk: unified memoryline splits the GPU'smemory into the renderer's and Metal's own. All inside
__APPLE__;RX_VK_UNIFIED_BUDGET=0turns it off. Test:tests/test_unified_budget.py(new).01630d4b; Was: #653):RX_VK_METAL_CACHE_DIR=<folder>(macOS only, off by default) hands the folder to Metal before the Vulkan library loads andlogs after the device whether Metal took it (
gpu/vk/metal_cache.h, new;gpu/vk/vk_main.cpp). Test:tests/test_metal_cache_dir.py(new).7df74b12; Was:#654): with the optional build inputs
LONGSHOT_VULKAN_LOADER(and
LONGSHOT_KOSMICKRISP, arm64) the macOS package carries LunarG's loader and each driver with a manifest beside it, andat startup
host/posix/darwin_vulkan.candhost/hos_main.cpoint the loader at MoltenVK unlessRX_VK_DRIVER=kosmickrisp;without the loader input the package is as before (
nx2/darwin_build.py,nx2/darwin_package.py). Tests:tests/test_vk_darwin_admission.py(the driver choice, 27 cases),tests/test_macos_package.py,tests/display_startup_failure_test.cpp.docs/development.md(the switches' rows and the macOS build inputs),gpu/vk/VULKAN.md.Measurements
Verdict: works. The members' tests fail on main (or are new) and pass here on arm64 and on x86_64 under Rosetta; in the
macOS port's game runs (integration builds) the allowance read the system (10.2-20.8 GB against the driver's 53,084 MB),
Metal took the chosen cache folder in every start that set it (denied cache opens from about 1.5 per compile to 0), and
MoltenVK through the packaged loader prepared all 38,809 programs in 58.0 s while KosmicKrisp prepared 1,607 in 1,770 s and
never reached the title (so MoltenVK stays the default). The full suite at this head: 576 passed, 15 failed, 17 skipped against
main's 543/33/17 on this Mac (and topic 1's 572/15/17), with no check failing that passes on main.
Baseline: the game rows are the macOS port's integration builds, arm64, MoltenVK 1.4.2 (main does not run on macOS): the
allowance, before integration
552cc3d4(run #23, whose read gate never opened: the driver's allowance, as main), aftere7730869and6962f8fb(runs #24, #26); the cache, beforeaefe6b0a(run #4), afterf32adfa2anda7711f3c(runs#5-#8); the drivers, one build
09f9a171with both drivers packaged (run #20 KosmicKrisp, run #21 MoltenVK). The game-freerows compare with main (the tests do not exist there, or fail there) and run at this head.
Hardware: macOS 26.7.1, Apple M4 Max (40-core GPU), MoltenVK 1.4.2, KosmicKrisp from LunarG's Vulkan SDK 1.4.363.1, 16-core
CPU, 64 GB
Scene: game-free fixtures; the game rows: Hyrule Field from the field-dawn save (the allowance), the start-up precompile and
the title (the cache and the drivers), windowless, defaults
Method: each member's test before and after (two interleaved runs in the members' own runs); at this head each once per
architecture; the game rows: the
vk: unified memory, cache and precompile lines of each run's longshot_log.txt, one run eachHow to reproduce: the tests under Testing;
RX_VK_UNIFIED_BUDGET=0gives the old allowance on the same build;RX_VK_METAL_CACHE_DIR=<folder>in a sandbox that denies/var/folders;RX_VK_DRIVER=kosmickrispon a loader packageaa95d35f7df74b12test_darwin_renderer.py,test_darwin_audio_service.pyDarwinImportBacking, noWaveAudioOpenFnroute)Flags and settings
Three new macOS switches and two optional macOS build inputs; none is read on Windows or Linux.
RX_VK_UNIFIED_BUDGET=0: the GPU's allowance is the driver's againvk: unified memoryline=0only to compareRX_VK_METAL_CACHE_DIR=<folder>/var/folders)NOT appliedline and unchangedRX_VK_DRIVERkosmickrispormoltenvk, the packaged driver the loader is givenkosmickrisp: measurement only (run #20 did not reach the title)LONGSHOT_VULKAN_LOADERlibvulkan.1.dylibLONGSHOT_KOSMICKRISPlibvulkan_kosmickrisp.dylibTesting
Tested: on macOS 26.7.1, Apple M4 Max, at this head
7df74b12(a clean worktree):--arch x86_64or its fixture built with
clang++ -arch x86_64):test_vk_darwin_admission(each arch),test_darwin_audio_serviceandtest_darwin_renderer(both arches in one run),test_unified_budget,test_metal_cache_dir,test_display_startup_failure,test_vk_darwin_sdl,test_vendor_fixes(each arch),test_macos_build_flagsandtest_macos_package(both arches in onerun, and the
VulkanDriverPackagecases), and #491's 12 named checks throughtests/run_all.py(arm64): 18 runs, 18 exit 0(admission: Apple version 25, size 400, the non-Apple ABI main's version 23, size 368; package 29 tests OK; build flags 16
tests OK; 12 passed, 0 failed).
python -I -B tests/run_all.pythroughtools/ls-suite.pyat this head: 576 passed, 15 failed, 17 skipped, against mainaa95d35f's 543/33/17 on this Mac, compared by test name: no check that passes on main fails here. Against topic 1's head(572/15/17) the only differences are this PR's four new tests, passing (
test_darwin_audio_service,test_darwin_renderer,test_metal_cache_dir,test_unified_budget); against main, topic 1's differences as its description lists them (11 newtests, 17 checks fixed,
test_appimage_package_retryskipped on macOS,test_dav1d_av1running). The 15 failures fail on maintoo, on this Mac:
test_readback_late,test_appimage_player_build,test_options_budget_once,test_vk_pgo,test_crash_dialog,test_walk_key,test_lift_cleanup_error,test_game_layout,test_options_path,test_make_game_cache,test_cpm_discovery,test_pipeline_tour,test_linux_preflight,test_env_on,test_split_giants.mkwvk::name onegpu/vkfile declares and another defines; each defining.cppcompiled with the tree's ownrenderer_flags(), arm64 and x86_64): 0 of 597 declared names lack a definition, 66 of66 objects compiled, 544 cross-file symbols per architecture all defined.
unifdef -U__APPLE__of every changed non-test C/C++ file against topic 1's head, compared as tokens): thedifferences are #491's stated every-platform parts:
vk_replay.cpp(replay_start_rollback),vk_vma.cpp(
vma_startup_rollback, the allocator published after it is made),vk_internal.h(their declarations),vk_recorder.hand
vk_work.h(thevmaDestroyAllocatorboundary),vk_memory.cpp(move-only pools,destroy_import()) andhost/posix/win32_posix.c(Linux:MessageBoxA'sdlclose,waveOutOpen's checks); and #642'svk_texture.cpp, whoseonly difference is
const uint64_t keep_now = keep;used wherekeepwas, the same value. The four new headers(
darwin_import.h,system_memory.h,unified_budget.h,metal_cache.h) are included only inside__APPLE__blocks;vk_api.h,vk_main.cpp,hos_main.c,hos_sdl.c,hos_sdl.h,darwin_vulkan.c,darwin_vulkan.handrx_backend.care the base's text there.
E9a on I41
78ec1c47: a 1,197 s hold in play, 12 of 12 must-shows, 0 skipped draws, a clean end(
reports/2026-11-15-mac-perf-E9.md); x86_64 under Rosetta: run #34 on I383f872b0c: 16 m 52 s of play, every drawdrawn, 19 of 19 checks, a clean exit (
reports/2026-11-14-mac-startup-34-x86_64.md).Not tested: Windows and Linux: nothing was built or run there (the test request below), so neither a startup that fails
part-way there nor the recorder modes (
RX_VK_RECORDER=defer/thread) through such a failure; a real Vulkan device on thishead (the tests use recording stand-ins); a Mac with 8-32 GB of RAM, where the allowance gives back early; a start that makes
its window before the renderer loads Vulkan (
RX_VK_METAL_CACHE_DIRwould logNOT applied); KosmicKrisp beyond run #20;macOS 14.4 and 15 (the deployment target is 14.4; every run was on 26.7.1); an Intel Mac.
Must be tested:
aa95d35fthe same way, runpython -I -B tests/run_all.pyon each andstart each to the title, expecting the new
test_darwin_renderer,test_darwin_audio_service,test_unified_budgetandtest_metal_cache_dirto pass or skip with their macOS reason, no other check to change status, and thevk:lines to matchmain's (Test Farm: @Beanstalk windows linux, build, run_all and start to the title)
Risks
What could break: every platform: the startup rollback runs only after a failed startup, and the import moves and
queue-slot order keep the same owners and release calls; the recorder boundary costs a relaxed load and a branch with the
recorder off. These touch the renderer's import path, which nobody has run in a game on Windows or Linux. macOS: a Mac short
of memory gives textures, spare capture buffers and idle staging back sooner (a re-upload hitch; floor 1 GB); a package built
with the loader now loads MoltenVK through it (system-wide implicit layers load, as for any Vulkan app).
Turning it off:
RX_VK_UNIFIED_BUDGET=0; leaveRX_VK_METAL_CACHE_DIRandRX_VK_DRIVERunset; build withoutLONGSHOT_VULKAN_LOADER; otherwise revert the PR.Compatibility: saves,
longshot_options.iniand the shader and pipeline caches are unchanged (none of the changed filesis in the translator's stamp;
vk_api.his not hashed since #370). The vk ABI:MKWVK_API_VERSIONis 25 on macOS only (twocallbacks appended to
MkwVkHost): the macOS executable and renderer are built together; Windows and Linux keep main's 23 withthe same structure. On macOS new log lines appear (
vk: unified memory, the cache check, the driver choice); switching toKosmicKrisp starts a new pipeline cache.
Issues
See #420. The macOS topic PRs, merged in this order; "topic N" in each description means these: 1 #845 (macos-host), 2 #846 (vulkan-on-metal, on 1), 3 #847 (mvk-descriptor-limits), 4 #848 (shader-backend, on 2, 3 and #431), 5 #849 (precompile-metal, on 4), 6 #850 (draw-path, on 4), 7 #851 (draw-path-quads-mesh, on 6, #705 and #707), 8 #852 (mac-perf, on 7).
Checklist
Everyone
type(scope): description) of the status line's type.main: onaa95d35f(through topic 1).python -I -B tests/run_all.pypasses (any failure that also fails onmainis named). (macOS: 576/15/17, the 15 failures also fail on main, named under Testing; Windows and Linux: the test request.)third_party/untouched (a new folder with aPROVENANCE.mdinstead).When it applies
Assisted-by:/Co-Authored-By:trailers.MKWVK_API_VERSIONbumped (gpu/vk/vk_api.h'sMkwVkHostorMkwVkRendererchanged).mainand passing here.tests/that reads changed source updated in this PR, not weakened.This conflicts with
mainand is built on top of the macOS series (it carries the commits of #845 and others below it).nx2/build.py,nx2/make_game.pyanddocs/development.mdconflict with #577 (the WebGPU renderer removed from the Windows build). Please rebase onto currentmainonce its base PR #845 lands or is rebased; I didn't touch it because a rebase here would have to be redone after that.View command line instructions
Manual merge helper
Use this merge commit message when completing the merge manually.
Checkout
From your project repository, check out a new branch and test the changes.