WIP: feat(vk): add macOS Vulkan loader and device admission #438

Draft
mooki wants to merge 3 commits from mooki/Longshot:mac/vk-admission into main
Member

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 the
renderer 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 the
shader 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.c and darwin_vulkan.c) and the SDL2/SDL3 bridge against stand-ins.
They fail on main and pass here, on arm64 and on x86_64 under Rosetta, two interleaved runs each; their Windows and
Linux 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 enables
the portability extensions that MoltenVK requires.

Problem

  • load_global (gpu/vk/vk_main.cpp) calls LoadLibraryA("vulkan-1.dll"); the POSIX layer's LoadLibraryA
    (host/posix/win32_posix.c) turns that into libvulkan.so.1. macOS ships no Vulkan loader, and a Mac has
    libvulkan.1.dylib or libMoltenVK.dylib, if anything.
  • SDL loads its own Vulkan library for the window. Nothing makes SDL and the renderer use the same one; two different
    loaders in one process would hand out unrelated instances.
  • create_instance asks for VK_KHR_surface plus whichever of the xlib, xcb and Wayland surfaces are advertised. On a
    Mac the window needs VK_EXT_metal_surface, which only SDL knows to ask for.
  • hos_display_init (host/posix/display_sdl.c) asks SDL for wayland,x11.
  • Through the loader, MoltenVK is listed only to an instance created with VK_KHR_portability_enumeration and its flag.
    And a device that advertises VK_KHR_portability_subset must have it enabled
    (VUID-VkDeviceCreateInfo-pProperties-04451).
    create_device never enables it.
  • A refusal says only vk: vkCreateInstance failed or vk: vkCreateDevice failed, with no VkResult and no name for
    the missing extension, queue or feature.

What changed

  • host/posix/darwin_vulkan.c and .h (new), Apple only:

    • hos_darwin_vulkan_library picks one library for the process: RX_VK_LIBRARY if set, then libvulkan.1.dylib and
      libMoltenVK.dylib beside the game's executable, then libvulkan.1.dylib through dyld. Each failed candidate is
      logged with dlerror(), and the chosen one by its real path (vk: Vulkan library: …). It stays loaded for the
      process's life.
    • hos_darwin_vulkan_sdl, _prepare and _capture: SDL loads that same library before the window, and its
      vkGetInstanceProcAddr must equal the renderer's. After the window is made, the main thread reads SDL2's or SDL3's
      instance extensions (their APIs differ) and caches them before the renderer thread starts.
      hos_darwin_vulkan_extensions hands out the cache; headless, it is empty and needs no SDL.
    • hos_darwin_library_path (inline, because both images build win32_posix.c): SDL3.dll, SDL2.dll and
      vulkan-1.dll become libSDL3.0.dylib, libSDL2-2.0.0.dylib and libvulkan.1.dylib; a renderer path's .dll
      becomes .dylib; a bare Windows system DLL is refused, as on Linux.
  • host/posix/win32_posix.c: LoadLibraryA uses that name map on Apple, then main's dlopen and its dlerror()
    text for hos_load_error. The only include it adds is darwin_vulkan.h.

  • host/hos_sdl.c, host/posix/display_sdl.c: on Apple, register the SDL module and preload the library before the
    window; ask SDL for cocoa unless a video driver is set; capture the extensions after the window, or stop with
    display: no Vulkan instance extensions for the SDL window.

  • gpu/vk/vk_api.h: two callbacks appended at the end of MkwVkHost, vulkan_library and vulkan_extensions,
    on Apple only, where MKWVK_API_VERSION is 22. Elsewhere it stays 21 and the structure is unchanged, so Windows and
    Linux hosts and renderers built before this PR still pair. host/rx_backend.c fills the two callbacks on Apple.

  • gpu/vk/vk_loader.h: on Apple, VK_ENABLE_BETA_EXTENSIONS before vulkan.h, for the portability-subset types.

  • gpu/vk/vk_main.cpp, Apple branches only:

    • load_global takes the host's library; create_instance asks for VK_KHR_surface plus SDL's list (none
      headless), and for VK_KHR_portability_enumeration with its flag only when advertised;
    • create_device enables an advertised VK_KHR_portability_subset with the subset features the device reports;
    • the log names the loader version, the selected driver, every extension asked for, a missing required extension,
      queue or core feature, and each creation VkResult by name and number.

    Required features, optional extension choices and every other platform's path are unchanged.

  • tests/test_vk_darwin_admission.py (new): compiles vk_main.cpp's loader, instance and device decisions,
    LoadLibraryA and the library candidates against stand-ins, for Windows, Linux and Darwin. Windows and Linux must
    choose exactly what main does (16 instance cases and 8 device cases each). Darwin is checked for SDL and headless
    extensions, 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 same
    library 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_LIBRARY in the switch table, and a paragraph on SDL extensions, headless start and
    portability.

  • Unchanged: nx2/build.py. The macOS build PR in this series (build(macos): classify Darwin helpers …) adds
    darwin_vulkan.c to the executable and names the renderer longshot_vk.dylib.

Flags and settings

Flag What it does Default now Recommendation Helps on Doesn't help on Untested on
RX_VK_LIBRARY=<path> macOS only: the first Vulkan loader or MoltenVK dylib to try. Then libvulkan.1.dylib and libMoltenVK.dylib beside the game, then libvulkan.1.dylib through dyld unset: no override leave unset when the game folder carries a loader or MoltenVK; set it to try another macOS Windows, Linux (not read) a loader that advertises portability enumeration; an Intel Mac

MKWVK_API_VERSION becomes 22 on macOS only; 21 elsewhere.

Measurements

main: 5e072fc8c2adadba5ea610148be1c47800ba0cca (2026-11-14)
this PR: mac/vk-admission @ 91d8cc3527ee64d13ad06da2d2e0f6d8ff42b4da
Hardware: 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 arm64 and with --arch x86_64, on main's source (--source, a clean checkout of
5e072fc8) and on this PR, interleaved: main, PR, main, PR (8 runs per side). Then tests/run_all.py --jobs 6 on this
PR, compared by test name with a main run 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 again
with --source <a checkout of main>.

main @ 5e072fc8 this PR @ 91d8cc35
tests/test_vk_darwin_admission.py, arm64 and x86_64, runs 1 and 2 exit 1 each: 23 Darwin failures (Linux surface names, no portability, no API 22 callbacks, no named refusals); the 4 Windows and Linux lines pass exit 0 each: 9 of 9 PASS lines
tests/test_vk_darwin_sdl.py, arm64 and x86_64, runs 1 and 2 exit 1 each: FAIL: main has no Darwin SDL Vulkan loader/extension bridge exit 0 each
full game-free suite 525 passed, 19 failed, 17 skipped 527 passed, 19 failed, 17 skipped
main @ 5e072fc8, test_vk_darwin_admission.py (each architecture, each run; a few of the 23 failures):
FAIL: Apple host API version is 22
FAIL: Apple host has no Vulkan library/extension callbacks
PASS: windows loader and 16 instance extension cases equal main
PASS: linux loader and 16 instance extension cases equal main
PASS: windows required/optional device extension decisions (8 cases)
PASS: linux required/optional device extension decisions (8 cases)
FAIL: Darwin: SDL extensions, no Linux names; portability only when advertised
  actual: VK_KHR_surface VK_KHR_xlib_surface
  expected: VK_KHR_surface VK_EXT_metal_surface
FAIL: Darwin library and feature admission absent on main

main @ 5e072fc8, test_vk_darwin_sdl.py (each architecture, each run):
FAIL: main has no Darwin SDL Vulkan loader/extension bridge

this PR @ 91d8cc35 (each architecture, each run):
PASS: fixed non-Apple ABI: version 21, size 352, all 44 named offsets; Apple version 22, size 368, typed callbacks at 352/360
PASS: windows loader and 16 instance extension cases equal main
PASS: linux loader and 16 instance extension cases equal main
PASS: windows required/optional device extension decisions (8 cases)
PASS: linux required/optional device extension decisions (8 cases)
PASS: Darwin SDL/headless/portability and named instance refusals
PASS: darwin required/optional device extension decisions (8 cases)
PASS: Darwin library candidates, UTF-8-safe name mapping and all 8 required-feature masks
PASS: Vulkan admission decisions; no library, SDL, device, window or surface opened
PASS: SDL2/SDL3 bridge 16 cases; loader identity, named failures, cached extensions; surface path compiled only

Before this rebase (history, at 818e33b1 on 514591a3; not rerun on this head): the same loader, instance and
device code, cut into a probe that stops right after vkCreateDevice, created a Vulkan 1.3 instance and a device with
LS's full default feature chain on MoltenVK 1.4.2, natively and under Rosetta. Both returned VK_SUCCESS: 11
structures and 121 enabled feature bits, none of them unsupported. main has since changed create_device around
these branches (#191's float-add choice), so that run speaks for the code as it was then.

vk: loader Vulkan 1.4.357
vk: portability enumeration not advertised; SDL window extensions 0
vk: vkCreateInstance: VK_SUCCESS (0)
vk: selected driver: MoltenVK 1.4.2
vk: device extension: VK_KHR_portability_subset
vk: vkCreateDevice: VK_SUCCESS (0)

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 are
    dropped (one blank line in vk_main.cpp aside). 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 macOS linux):

    main @ 5e072fc8:     525 passed, 19 failed, 17 skipped in 177 s (linux, 6 at once)
    this PR @ 91d8cc35:  527 passed, 19 failed, 17 skipped in 179 s (linux, 6 at once)
    
    • By test name, the only differences are the two new tests, tests/test_vk_darwin_admission.py and
      tests/test_vk_darwin_sdl.py (pass, native arm64, as run_all runs them). No other check changed status.
    • The same 19 checks fail on both. They are macOS portability failures of main, which other macOS pull requests
      address: 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.
    • No new crash report during the suite.

Not tested:

  • Windows and Linux builds and runs.
  • A window: SDL's Cocoa driver, the Metal surface, present support, the swapchain, a frame. The SDL tests use stand-ins.
  • A real device on this head (no GPU run after the rebase), and a loader that advertises portability enumeration
    (MoltenVK loaded directly does not).
  • What a created device does next: shaders, descriptor limits, formats and host-memory imports on Metal are other PRs.
  • macOS 14.4 itself (the deployment target is 14.4; it ran only on 26.7.1) and an Intel Mac.

Needs other testers:

  • Windows (llvm-mingw): build host and renderer and run the game-free suite; then start the game once and check that
    the shader caches rebuild once and nothing else changes (request to open).
  • Linux: the same, and a start with a renderer built before this PR next to a host built with it, which must still
    pair at API 21 (request to open).

Screenshots

Nothing on screen.

Risk and rollback

  • Every platform: vk_api.h is hashed into LONGSHOT_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.h is unchanged and the API stays 21 there.
  • Windows and Linux: otherwise no change in behaviour. Every new line is inside #if defined(__APPLE__) or in a
    new file.
  • macOS: a bad RX_VK_LIBRARY path is logged, and the next candidate is tried. The library must have the
    process's architecture; nothing installs one.
  • The API number differs by platform (22 on Apple, 21 elsewhere). A global 22 would make every existing Windows and
    Linux renderer refuse a new host. Question 1 below.
  • Rollback: revert the three commits. RX_VK_LIBRARY unset changes only the search, not the rest.

Reviewer notes

  • Review 5e072fc8..91d8cc35: three commits and no dependency. e294efe9 is the change; 81053e98 replaces the tests'
    source-text checks with compiled ones and the header hash with the fixed ABI list; 91d8cc35 follows main's
    LoadLibraryA (#34, load_error) and native_fadd_wanted (#191, four arguments) in the admission test's stand-ins.
  • Start with darwin_vulkan.c, then the Apple branches in vk_main.cpp's create_instance and create_device.
  • On macOS, rx_backend.c's "did not load" line (from #34) turns .dll into .so for the file name; the dlerror()
    text after it names the real .dylib. Not changed here.
  • Written from the SDL2 and SDL3 Vulkan pages, dlopen(3), dyld(3) and the Vulkan specification. Nothing was copied
    from another project.

Questions for you

  1. MKWVK_API_VERSION is 22 on Apple and 21 elsewhere, so existing Windows and Linux renderer builds keep pairing with
    new hosts. Would you rather bump it to 22 everywhere?

Issues

See #420.

Checklist

For everyone

  • One topic.
  • The title is a Conventional Commit (type(scope): description).
  • Rebased on current main. Based on 5e072fc8.
  • python -I -B tests/run_all.py run and passing (any failure that also fails on main is named above). On macOS:
    527 passed, 17 skipped, and the same 19 failures as main, named under Testing.
  • Tested in the game, or what wasn't is named under Testing. No game run; named under Testing.
  • Testing filled in: what was tested, what wasn't, and what needs other testers (with test requests). The test
    requests are still to open.
  • Measured: main and this PR at named commits, on the same PC, scene and settings, at least 2 interleaved runs each,
    the median reported. For a feat without a scene: the new tests fail on main and pass here, in two interleaved
    runs each.
  • No game files, dumps, build output or generated files.
  • third_party/ untouched (a new folder with a PROVENANCE.md instead).
  • The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled.

If applicable

  • AI assistance recorded with Assisted-by: / Co-Authored-By: trailers.
  • Flags noted, and the switch reference updated (docs/development.md, the README). RX_VK_LIBRARY is in
    docs/development.md's switch table; no menu row.
  • MKWVK_API_VERSION bumped (gpu/vk/vk_api.h's MkwVkHost or MkwVkRenderer changed). 22 on Apple, where
    MkwVkHost changed; 21 elsewhere, where it did not.
  • A game-free test added for the fix, failing on main and passing here.
  • Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths. Not built there; requests to open.
  • Docs updated.
**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 the renderer 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 the shader 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.c` and `darwin_vulkan.c`) and the SDL2/SDL3 bridge against stand-ins. They fail on `main` and pass here, on arm64 and on x86_64 under Rosetta, two interleaved runs each; their Windows and Linux 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 enables the portability extensions that MoltenVK requires. ## Problem - `load_global` (`gpu/vk/vk_main.cpp`) calls `LoadLibraryA("vulkan-1.dll")`; the POSIX layer's `LoadLibraryA` (`host/posix/win32_posix.c`) turns that into `libvulkan.so.1`. macOS ships no Vulkan loader, and a Mac has `libvulkan.1.dylib` or `libMoltenVK.dylib`, if anything. - SDL loads its own Vulkan library for the window. Nothing makes SDL and the renderer use the same one; two different loaders in one process would hand out unrelated instances. - `create_instance` asks for `VK_KHR_surface` plus whichever of the xlib, xcb and Wayland surfaces are advertised. On a Mac the window needs `VK_EXT_metal_surface`, which only SDL knows to ask for. - `hos_display_init` (`host/posix/display_sdl.c`) asks SDL for `wayland,x11`. - Through the loader, MoltenVK is listed only to an instance created with `VK_KHR_portability_enumeration` and its flag. And a device that advertises `VK_KHR_portability_subset` must have it enabled ([VUID-VkDeviceCreateInfo-pProperties-04451](https://docs.vulkan.org/refpages/latest/refpages/source/VkDeviceCreateInfo.html)). `create_device` never enables it. - A refusal says only `vk: vkCreateInstance failed` or `vk: vkCreateDevice failed`, with no `VkResult` and no name for the missing extension, queue or feature. ## What changed - **`host/posix/darwin_vulkan.c` and `.h` (new)**, Apple only: - `hos_darwin_vulkan_library` picks one library for the process: `RX_VK_LIBRARY` if set, then `libvulkan.1.dylib` and `libMoltenVK.dylib` beside the game's executable, then `libvulkan.1.dylib` through dyld. Each failed candidate is logged with `dlerror()`, and the chosen one by its real path (`vk: Vulkan library: …`). It stays loaded for the process's life. - `hos_darwin_vulkan_sdl`, `_prepare` and `_capture`: SDL loads that same library before the window, and its `vkGetInstanceProcAddr` must equal the renderer's. After the window is made, the main thread reads SDL2's or SDL3's instance extensions (their APIs differ) and caches them before the renderer thread starts. `hos_darwin_vulkan_extensions` hands out the cache; headless, it is empty and needs no SDL. - `hos_darwin_library_path` (inline, because both images build `win32_posix.c`): `SDL3.dll`, `SDL2.dll` and `vulkan-1.dll` become `libSDL3.0.dylib`, `libSDL2-2.0.0.dylib` and `libvulkan.1.dylib`; a renderer path's `.dll` becomes `.dylib`; a bare Windows system DLL is refused, as on Linux. - **`host/posix/win32_posix.c`:** `LoadLibraryA` uses that name map on Apple, then `main`'s `dlopen` and its `dlerror()` text for `hos_load_error`. The only include it adds is `darwin_vulkan.h`. - **`host/hos_sdl.c`, `host/posix/display_sdl.c`:** on Apple, register the SDL module and preload the library before the window; ask SDL for `cocoa` unless a video driver is set; capture the extensions after the window, or stop with `display: no Vulkan instance extensions for the SDL window`. - **`gpu/vk/vk_api.h`:** two callbacks appended at the end of `MkwVkHost`, `vulkan_library` and `vulkan_extensions`, on Apple only, where `MKWVK_API_VERSION` is 22. Elsewhere it stays 21 and the structure is unchanged, so Windows and Linux hosts and renderers built before this PR still pair. `host/rx_backend.c` fills the two callbacks on Apple. - **`gpu/vk/vk_loader.h`:** on Apple, `VK_ENABLE_BETA_EXTENSIONS` before `vulkan.h`, for the portability-subset types. - **`gpu/vk/vk_main.cpp`**, Apple branches only: - `load_global` takes the host's library; `create_instance` asks for `VK_KHR_surface` plus SDL's list (none headless), and for `VK_KHR_portability_enumeration` with its flag only when advertised; - `create_device` enables an advertised `VK_KHR_portability_subset` with the subset features the device reports; - the log names the loader version, the selected driver, every extension asked for, a missing required extension, queue or core feature, and each creation `VkResult` by name and number. Required features, optional extension choices and every other platform's path are unchanged. - **`tests/test_vk_darwin_admission.py` (new):** compiles `vk_main.cpp`'s loader, instance and device decisions, `LoadLibraryA` and the library candidates against stand-ins, for Windows, Linux and Darwin. Windows and Linux must choose exactly what `main` does (16 instance cases and 8 device cases each). Darwin is checked for SDL and headless extensions, 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 same library 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_LIBRARY` in the switch table, and a paragraph on SDL extensions, headless start and portability. - **Unchanged:** `nx2/build.py`. The macOS build PR in this series (`build(macos): classify Darwin helpers …`) adds `darwin_vulkan.c` to the executable and names the renderer `longshot_vk.dylib`. ## Flags and settings | Flag | What it does | Default now | Recommendation | Helps on | Doesn't help on | Untested on | |---|---|---|---|---|---|---| | `RX_VK_LIBRARY=<path>` | macOS only: the first Vulkan loader or MoltenVK dylib to try. Then `libvulkan.1.dylib` and `libMoltenVK.dylib` beside the game, then `libvulkan.1.dylib` through dyld | unset: no override | leave unset when the game folder carries a loader or MoltenVK; set it to try another | macOS | Windows, Linux (not read) | a loader that advertises portability enumeration; an Intel Mac | `MKWVK_API_VERSION` becomes 22 on macOS only; 21 elsewhere. ## Measurements main: `5e072fc8c2adadba5ea610148be1c47800ba0cca` (2026-11-14) this PR: `mac/vk-admission` @ `91d8cc3527ee64d13ad06da2d2e0f6d8ff42b4da` Hardware: 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 arm64` and with `--arch x86_64`, on `main`'s source (`--source`, a clean checkout of `5e072fc8`) and on this PR, interleaved: main, PR, main, PR (8 runs per side). Then `tests/run_all.py --jobs 6` on this PR, compared by test name with a `main` run 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 again with `--source <a checkout of main>`. | | main @ 5e072fc8 | this PR @ 91d8cc35 | |---|---|---| | `tests/test_vk_darwin_admission.py`, arm64 and x86_64, runs 1 and 2 | exit 1 each: 23 Darwin failures (Linux surface names, no portability, no API 22 callbacks, no named refusals); the 4 Windows and Linux lines pass | exit 0 each: 9 of 9 PASS lines | | `tests/test_vk_darwin_sdl.py`, arm64 and x86_64, runs 1 and 2 | exit 1 each: `FAIL: main has no Darwin SDL Vulkan loader/extension bridge` | exit 0 each | | full game-free suite | 525 passed, 19 failed, 17 skipped | 527 passed, 19 failed, 17 skipped | ``` main @ 5e072fc8, test_vk_darwin_admission.py (each architecture, each run; a few of the 23 failures): FAIL: Apple host API version is 22 FAIL: Apple host has no Vulkan library/extension callbacks PASS: windows loader and 16 instance extension cases equal main PASS: linux loader and 16 instance extension cases equal main PASS: windows required/optional device extension decisions (8 cases) PASS: linux required/optional device extension decisions (8 cases) FAIL: Darwin: SDL extensions, no Linux names; portability only when advertised actual: VK_KHR_surface VK_KHR_xlib_surface expected: VK_KHR_surface VK_EXT_metal_surface FAIL: Darwin library and feature admission absent on main main @ 5e072fc8, test_vk_darwin_sdl.py (each architecture, each run): FAIL: main has no Darwin SDL Vulkan loader/extension bridge this PR @ 91d8cc35 (each architecture, each run): PASS: fixed non-Apple ABI: version 21, size 352, all 44 named offsets; Apple version 22, size 368, typed callbacks at 352/360 PASS: windows loader and 16 instance extension cases equal main PASS: linux loader and 16 instance extension cases equal main PASS: windows required/optional device extension decisions (8 cases) PASS: linux required/optional device extension decisions (8 cases) PASS: Darwin SDL/headless/portability and named instance refusals PASS: darwin required/optional device extension decisions (8 cases) PASS: Darwin library candidates, UTF-8-safe name mapping and all 8 required-feature masks PASS: Vulkan admission decisions; no library, SDL, device, window or surface opened PASS: SDL2/SDL3 bridge 16 cases; loader identity, named failures, cached extensions; surface path compiled only ``` **Before this rebase** (history, at `818e33b1` on `514591a3`; not rerun on this head): the same loader, instance and device code, cut into a probe that stops right after `vkCreateDevice`, created a Vulkan 1.3 instance and a device with LS's full default feature chain on MoltenVK 1.4.2, natively and under Rosetta. Both returned `VK_SUCCESS`: 11 structures and 121 enabled feature bits, none of them unsupported. `main` has since changed `create_device` around these branches (#191's float-add choice), so that run speaks for the code as it was then. ``` vk: loader Vulkan 1.4.357 vk: portability enumeration not advertised; SDL window extensions 0 vk: vkCreateInstance: VK_SUCCESS (0) vk: selected driver: MoltenVK 1.4.2 vk: device extension: VK_KHR_portability_subset vk: vkCreateDevice: VK_SUCCESS (0) ``` ## 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 are dropped (one blank line in `vk_main.cpp` aside). 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 macOS `linux`): ``` main @ 5e072fc8: 525 passed, 19 failed, 17 skipped in 177 s (linux, 6 at once) this PR @ 91d8cc35: 527 passed, 19 failed, 17 skipped in 179 s (linux, 6 at once) ``` - By test name, the only differences are the two new tests, `tests/test_vk_darwin_admission.py` and `tests/test_vk_darwin_sdl.py` (pass, native arm64, as `run_all` runs them). No other check changed status. - The same 19 checks fail on both. They are macOS portability failures of `main`, which other macOS pull requests address: `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`. - No new crash report during the suite. **Not tested:** - Windows and Linux builds and runs. - A window: SDL's Cocoa driver, the Metal surface, present support, the swapchain, a frame. The SDL tests use stand-ins. - A real device on this head (no GPU run after the rebase), and a loader that advertises portability enumeration (MoltenVK loaded directly does not). - What a created device does next: shaders, descriptor limits, formats and host-memory imports on Metal are other PRs. - macOS 14.4 itself (the deployment target is 14.4; it ran only on 26.7.1) and an Intel Mac. **Needs other testers:** - Windows (llvm-mingw): build host and renderer and run the game-free suite; then start the game once and check that the shader caches rebuild once and nothing else changes (request to open). - Linux: the same, and a start with a renderer built before this PR next to a host built with it, which must still pair at API 21 (request to open). ## Screenshots Nothing on screen. ## Risk and rollback - **Every platform:** `vk_api.h` is hashed into `LONGSHOT_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.h` is unchanged and the API stays 21 there. - **Windows and Linux:** otherwise no change in behaviour. Every new line is inside `#if defined(__APPLE__)` or in a new file. - **macOS:** a bad `RX_VK_LIBRARY` path is logged, and the next candidate is tried. The library must have the process's architecture; nothing installs one. - **The API number differs by platform** (22 on Apple, 21 elsewhere). A global 22 would make every existing Windows and Linux renderer refuse a new host. Question 1 below. - **Rollback:** revert the three commits. `RX_VK_LIBRARY` unset changes only the search, not the rest. ## Reviewer notes - Review `5e072fc8..91d8cc35`: three commits and no dependency. `e294efe9` is the change; `81053e98` replaces the tests' source-text checks with compiled ones and the header hash with the fixed ABI list; `91d8cc35` follows `main`'s `LoadLibraryA` (#34, `load_error`) and `native_fadd_wanted` (#191, four arguments) in the admission test's stand-ins. - Start with `darwin_vulkan.c`, then the Apple branches in `vk_main.cpp`'s `create_instance` and `create_device`. - On macOS, `rx_backend.c`'s "did not load" line (from #34) turns `.dll` into `.so` for the file name; the `dlerror()` text after it names the real `.dylib`. Not changed here. - Written from the SDL2 and SDL3 Vulkan pages, `dlopen(3)`, `dyld(3)` and the Vulkan specification. Nothing was copied from another project. ## Questions for you 1. `MKWVK_API_VERSION` is 22 on Apple and 21 elsewhere, so existing Windows and Linux renderer builds keep pairing with new hosts. Would you rather bump it to 22 everywhere? ## Issues See #420. ## Checklist ### For everyone - [x] One topic. - [x] The title is a Conventional Commit (`type(scope): description`). - [x] Rebased on current `main`. Based on 5e072fc8. - [x] `python -I -B tests/run_all.py` run and passing (any failure that also fails on `main` is named above). On macOS: 527 passed, 17 skipped, and the same 19 failures as `main`, named under Testing. - [x] Tested in the game, or what wasn't is named under Testing. No game run; named under Testing. - [ ] Testing filled in: what was tested, what wasn't, and what needs other testers (with test requests). The test requests are still to open. - [x] Measured: main and this PR at named commits, on the same PC, scene and settings, at least 2 interleaved runs each, the median reported. For a `feat` without a scene: the new tests fail on `main` and pass here, in two interleaved runs each. - [x] No game files, dumps, build output or generated files. - [x] `third_party/` untouched (a new folder with a `PROVENANCE.md` instead). - [x] The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled. ### If applicable - [x] AI assistance recorded with `Assisted-by:` / `Co-Authored-By:` trailers. - [x] Flags noted, and the switch reference updated (docs/development.md, the README). `RX_VK_LIBRARY` is in `docs/development.md`'s switch table; no menu row. - [x] `MKWVK_API_VERSION` bumped (`gpu/vk/vk_api.h`'s `MkwVkHost` or `MkwVkRenderer` changed). 22 on Apple, where `MkwVkHost` changed; 21 elsewhere, where it did not. - [x] A game-free test added for the fix, failing on `main` and passing here. - [ ] Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths. Not built there; requests to open. - [x] Docs updated.
Select a local Vulkan loader or MoltenVK with RX_VK_LIBRARY and keep SDL and
renderer startup on that same library. Capture SDL's instance extensions on
the window thread, enable advertised portability extensions, and name missing
admission requirements and VkResults without changing Windows or Linux paths.

Keep the non-Apple host API at 21 for existing prebuilt renderers; append the
two macOS callbacks only in Apple's version 22. Cover pure decisions, legacy
platform expectations, ABI layout, and the SDL2/SDL3 bridge without a GPU.

Assisted-by: GPT-6 Astra
Co-Authored-By: GPT-6 Astra <[email protected]>
Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Remove the SDL source-string wiring and source-index ordering assertions while
keeping the sixteen compiled behavioral cases and surface adapter compile.
Replace the comment-sensitive API header hash with fixed baseline field names,
byte offsets, versions and sizes, retaining typed Apple callback checks and
both platforms' actual admission decisions and negative controls.

Production code is unchanged. Prior full-suite and device receipts remain
bound to 818e33b1; only the affected game-free tests were rerun.

Assisted-by: GPT-6 Astra
Co-Authored-By: GPT-6 Astra <[email protected]>
Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 <[email protected]>
test(vk): follow main's LoadLibraryA and float-add choice in the admission fixture
All checks were successful
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
PR description / Description follows the template (pull_request_target) Successful in 5s
CI / Conventional Commit title (pull_request) Successful in 3s
CI / Workflow lint (actionlint) (pull_request) Successful in 5s
Third-party parts / As their notes say (pull_request) Successful in 6s
Website / Website on Cloudflare (pull_request_target) Has been skipped
91d8cc3527
The fixture cuts LoadLibraryA and create_device out of main's source. Since
#34, LoadLibraryA keeps dlerror()'s text in load_error, and since #191
native_fadd_wanted also takes the device ID and driver version, so the cut
code no longer compiled against the fixture's stand-ins on this base. Give
it load_error and a dlerror stand-in, and the four-argument stand-in. No
expectation changed.

Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 <[email protected]>
All checks were successful
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
PR description / Description follows the template (pull_request_target) Successful in 5s
CI / Conventional Commit title (pull_request) Successful in 3s
CI / Workflow lint (actionlint) (pull_request) Successful in 5s
Third-party parts / As their notes say (pull_request) Successful in 6s
Website / Website on Cloudflare (pull_request_target) Has been skipped
This pull request has changes conflicting with the target branch.
  • gpu/vk/vk_api.h
  • host/rx_backend.c
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.
git fetch -u 'mac/vk-admission:mooki-mac/vk-admission'
git switch 'mooki-mac/vk-admission'
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
MagicBeanDev/Longshot!438
No description provided.