WIP: feat(vk): add macOS Vulkan loader and device admission #438
No reviewers
Labels
No labels
area/accessibility
area/audio
area/input
area/install
area/menu
area/renderer
area/saves
area/upscaling
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/steam-deck
platform/windows
preview
priority
critical
priority
high
priority
low
status
blocked
status
confirmed
status
duplicate
status
invalid
status
needs-info
status
wontfix
testing wanted
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!438
Loading…
Reference in a new issue
No description provided.
Delete branch "mooki/Longshot:mac/vk-admission"
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?
What this is for: the macOS port (#420). macOS has no system Vulkan loader, SDL on macOS needs the Cocoa driver
and Metal-surface extensions instead of X11 or Wayland, and MoltenVK requires the portability extensions. This PR lets
the Vulkan renderer find a loader or MoltenVK on macOS (
RX_VK_LIBRARY, then beside the game), keeps SDL and therenderer on that one library, takes the window's instance extensions from SDL, enables the portability extensions when
they are advertised, and names in the log whatever refuses an instance or a device. Windows and Linux behave as before.
Every platform rebuilds its shader caches once after this PR, because it edits
gpu/vk/vk_api.h, which theshader stamp hashes. What is measured: two new game-free tests compile the real loader, instance and device
decisions (cut from
vk_main.cpp,win32_posix.canddarwin_vulkan.c) and the SDL2/SDL3 bridge against stand-ins.They fail on
mainand pass here, on arm64 and on x86_64 under Rosetta, two interleaved runs each; their Windows andLinux cases pass on both. Also measured: the full game-free suite on an Apple M4 Max (macOS 26.7.1), compared by test
name with
main. Not measured: a window, a Metal surface, a swapchain or a frame; Windows and Linux; macOS 14.4;an Intel Mac. A real MoltenVK instance and device were created before this rebase (below), not on this head.
Why
On a Mac the renderer cannot start: it looks for
libvulkan.so.1, asks for X11 or Wayland surfaces, and never enablesthe portability extensions that MoltenVK requires.
Problem
load_global(gpu/vk/vk_main.cpp) callsLoadLibraryA("vulkan-1.dll"); the POSIX layer'sLoadLibraryA(
host/posix/win32_posix.c) turns that intolibvulkan.so.1. macOS ships no Vulkan loader, and a Mac haslibvulkan.1.dyliborlibMoltenVK.dylib, if anything.loaders in one process would hand out unrelated instances.
create_instanceasks forVK_KHR_surfaceplus whichever of the xlib, xcb and Wayland surfaces are advertised. On aMac the window needs
VK_EXT_metal_surface, which only SDL knows to ask for.hos_display_init(host/posix/display_sdl.c) asks SDL forwayland,x11.VK_KHR_portability_enumerationand its flag.And a device that advertises
VK_KHR_portability_subsetmust have it enabled(VUID-VkDeviceCreateInfo-pProperties-04451).
create_devicenever enables it.vk: vkCreateInstance failedorvk: vkCreateDevice failed, with noVkResultand no name forthe missing extension, queue or feature.
What changed
host/posix/darwin_vulkan.cand.h(new), Apple only:hos_darwin_vulkan_librarypicks one library for the process:RX_VK_LIBRARYif set, thenlibvulkan.1.dylibandlibMoltenVK.dylibbeside the game's executable, thenlibvulkan.1.dylibthrough dyld. Each failed candidate islogged with
dlerror(), and the chosen one by its real path (vk: Vulkan library: …). It stays loaded for theprocess's life.
hos_darwin_vulkan_sdl,_prepareand_capture: SDL loads that same library before the window, and itsvkGetInstanceProcAddrmust equal the renderer's. After the window is made, the main thread reads SDL2's or SDL3'sinstance extensions (their APIs differ) and caches them before the renderer thread starts.
hos_darwin_vulkan_extensionshands out the cache; headless, it is empty and needs no SDL.hos_darwin_library_path(inline, because both images buildwin32_posix.c):SDL3.dll,SDL2.dllandvulkan-1.dllbecomelibSDL3.0.dylib,libSDL2-2.0.0.dylibandlibvulkan.1.dylib; a renderer path's.dllbecomes
.dylib; a bare Windows system DLL is refused, as on Linux.host/posix/win32_posix.c:LoadLibraryAuses that name map on Apple, thenmain'sdlopenand itsdlerror()text for
hos_load_error. The only include it adds isdarwin_vulkan.h.host/hos_sdl.c,host/posix/display_sdl.c: on Apple, register the SDL module and preload the library before thewindow; ask SDL for
cocoaunless a video driver is set; capture the extensions after the window, or stop withdisplay: no Vulkan instance extensions for the SDL window.gpu/vk/vk_api.h: two callbacks appended at the end ofMkwVkHost,vulkan_libraryandvulkan_extensions,on Apple only, where
MKWVK_API_VERSIONis 22. Elsewhere it stays 21 and the structure is unchanged, so Windows andLinux hosts and renderers built before this PR still pair.
host/rx_backend.cfills the two callbacks on Apple.gpu/vk/vk_loader.h: on Apple,VK_ENABLE_BETA_EXTENSIONSbeforevulkan.h, for the portability-subset types.gpu/vk/vk_main.cpp, Apple branches only:load_globaltakes the host's library;create_instanceasks forVK_KHR_surfaceplus SDL's list (noneheadless), and for
VK_KHR_portability_enumerationwith its flag only when advertised;create_deviceenables an advertisedVK_KHR_portability_subsetwith the subset features the device reports;queue or core feature, and each creation
VkResultby name and number.Required features, optional extension choices and every other platform's path are unchanged.
tests/test_vk_darwin_admission.py(new): compilesvk_main.cpp's loader, instance and device decisions,LoadLibraryAand the library candidates against stand-ins, for Windows, Linux and Darwin. Windows and Linux mustchoose exactly what
maindoes (16 instance cases and 8 device cases each). Darwin is checked for SDL and headlessextensions, portability, every named refusal and the 8 required-feature masks. It also checks the host ABI against a
fixed list: version 21, 352 bytes and 44 named offsets off Apple; version 22, 368 bytes and the two callbacks at 352
and 360 on Apple. Nothing is loaded and no device is opened.
tests/test_vk_darwin_sdl.py(new): 16 compiled cases of the SDL2 and SDL3 bridge with stand-ins: the samelibrary for SDL and the renderer, named load and query failures, one preload, cached extensions. The surface adapter
is compiled, not called.
docs/development.md:RX_VK_LIBRARYin the switch table, and a paragraph on SDL extensions, headless start andportability.
Unchanged:
nx2/build.py. The macOS build PR in this series (build(macos): classify Darwin helpers …) addsdarwin_vulkan.cto the executable and names the rendererlongshot_vk.dylib.Flags and settings
RX_VK_LIBRARY=<path>libvulkan.1.dylibandlibMoltenVK.dylibbeside the game, thenlibvulkan.1.dylibthrough dyldMKWVK_API_VERSIONbecomes 22 on macOS only; 21 elsewhere.Measurements
main:
5e072fc8c2adadba5ea610148be1c47800ba0cca(2026-11-14)this PR:
mac/vk-admission@91d8cc3527ee64d13ad06da2d2e0f6d8ff42b4daHardware: Apple M4 Max, 16 cores, 64 GiB, macOS 26.7.1 (25G241); Apple clang 21.0.0; Vulkan-Headers 1.4.345; Python
3.14.10. No GPU used for these runs.
Scene: none. Game-free.
Method: both tests with
--arch arm64and with--arch x86_64, onmain's source (--source, a clean checkout of5e072fc8) and on this PR, interleaved: main, PR, main, PR (8 runs per side). Thentests/run_all.py --jobs 6on thisPR, compared by test name with a
mainrun on the same Mac.How to reproduce:
python -I -B tests/test_vk_darwin_admission.py --cxx clang++ --headers <Vulkan-Headers/include> --arch arm64(and--arch x86_64),python -I -B tests/test_vk_darwin_sdl.py --cxx clang++ --arch arm64, each againwith
--source <a checkout of main>.5e072fc891d8cc35tests/test_vk_darwin_admission.py, arm64 and x86_64, runs 1 and 2tests/test_vk_darwin_sdl.py, arm64 and x86_64, runs 1 and 2FAIL: main has no Darwin SDL Vulkan loader/extension bridgeBefore this rebase (history, at
818e33b1on514591a3; not rerun on this head): the same loader, instance anddevice code, cut into a probe that stops right after
vkCreateDevice, created a Vulkan 1.3 instance and a device withLS's full default feature chain on MoltenVK 1.4.2, natively and under Rosetta. Both returned
VK_SUCCESS: 11structures and 121 enabled feature bits, none of them unsupported.
mainhas since changedcreate_devicearoundthese branches (#191's float-add choice), so that run speaks for the code as it was then.
Testing
Tested:
Both tests, both ways, both routes, twice each, as above.
The non-Apple text of all seven shared C/C++ files equals
main's once the#if defined(__APPLE__)blocks aredropped (one blank line in
vk_main.cppaside). That is text, not a Windows or Linux build.The whole suite,
tests/run_all.py, 6 at once, on the same Mac (the runner labels macOSlinux):tests/test_vk_darwin_admission.pyandtests/test_vk_darwin_sdl.py(pass, native arm64, asrun_allruns them). No other check changed status.main, which other macOS pull requestsaddress:
test_appimage_package_retry,test_collect_wide,test_control,test_framegen_resources,test_game_layout,test_low_memory_notice,test_mem_gate,test_mem_gate_children,test_mirror_trim,test_module_ids,test_oot_state_tap,test_pad_layout,test_pcache_header_read,test_pipeline_cache_file,test_pipeline_cache_save,test_pipeline_play_cache,test_prof_symbols,test_program_cache_cleanup,test_program_cache_one_variant.Not tested:
(MoltenVK loaded directly does not).
Needs other testers:
the shader caches rebuild once and nothing else changes (request to open).
pair at API 21 (request to open).
Screenshots
Nothing on screen.
Risk and rollback
vk_api.his hashed intoLONGSHOT_SHADER_STAMP(nx2/build.py:1176-1182), so after updating,the first start translates the shader programs again and the host's objects rebuild, as with any edit to that file.
The non-Apple preprocessed
vk_api.his unchanged and the API stays 21 there.#if defined(__APPLE__)or in anew file.
RX_VK_LIBRARYpath is logged, and the next candidate is tried. The library must have theprocess's architecture; nothing installs one.
Linux renderer refuse a new host. Question 1 below.
RX_VK_LIBRARYunset changes only the search, not the rest.Reviewer notes
5e072fc8..91d8cc35: three commits and no dependency.e294efe9is the change;81053e98replaces the tests'source-text checks with compiled ones and the header hash with the fixed ABI list;
91d8cc35followsmain'sLoadLibraryA(#34,load_error) andnative_fadd_wanted(#191, four arguments) in the admission test's stand-ins.darwin_vulkan.c, then the Apple branches invk_main.cpp'screate_instanceandcreate_device.rx_backend.c's "did not load" line (from #34) turns.dllinto.sofor the file name; thedlerror()text after it names the real
.dylib. Not changed here.dlopen(3),dyld(3)and the Vulkan specification. Nothing was copiedfrom another project.
Questions for you
MKWVK_API_VERSIONis 22 on Apple and 21 elsewhere, so existing Windows and Linux renderer builds keep pairing withnew hosts. Would you rather bump it to 22 everywhere?
Issues
See #420.
Checklist
For everyone
type(scope): description).main. Based on5e072fc8.python -I -B tests/run_all.pyrun and passing (any failure that also fails onmainis named above). On macOS:527 passed, 17 skipped, and the same 19 failures as
main, named under Testing.requests are still to open.
the median reported. For a
featwithout a scene: the new tests fail onmainand pass here, in two interleavedruns each.
third_party/untouched (a new folder with aPROVENANCE.mdinstead).If applicable
Assisted-by:/Co-Authored-By:trailers.RX_VK_LIBRARYis indocs/development.md's switch table; no menu row.MKWVK_API_VERSIONbumped (gpu/vk/vk_api.h'sMkwVkHostorMkwVkRendererchanged). 22 on Apple, whereMkwVkHostchanged; 21 elsewhere, where it did not.mainand passing here.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.