@@ -273,20 +273,75 @@ jobs:
273273 fi
274274 done
275275
276- # MoltenVK / KosmicKrisp ICD + dylib.
276+ # ---- Vulkan stack for the Zink hardware-rendering path ----
277+ # On a real macOS 26 + Apple Silicon machine the engine can
278+ # render through Mesa's Zink driver onto the Metal GPU via
279+ # KosmicKrisp instead of the llvmpipe software rasteriser. The
280+ # launcher (below) activates this at runtime by setting
281+ # GALLIUM_DRIVER=zink when it detects macOS 26+. Three things
282+ # must be in the bundle for that to work; the previous version
283+ # of this step shipped none of them correctly.
277284 MOLTEN_PREFIX="$(brew --prefix molten-vk)"
278- mkdir -p "$APP/Contents/Resources/share/vulkan/icd.d"
279- if [ -f "$MOLTEN_PREFIX/share/vulkan/icd.d/MoltenVK_icd.json" ]; then
280- cp -f "$MOLTEN_PREFIX/share/vulkan/icd.d/MoltenVK_icd.json" \
281- "$APP/Contents/Resources/share/vulkan/icd.d/"
285+ LOADER_PREFIX="$(brew --prefix vulkan-loader)"
286+
287+ # (1) The Vulkan loader. Zink dlopen()s "libvulkan.1.dylib" by
288+ # name, so it MUST be bundled under that exact name — without it
289+ # Zink dies with "ZINK: failed to load libvulkan.1.dylib". brew
290+ # ships it as a symlink to a versioned file; cp -L copies the
291+ # real target.
292+ if [ -f "$LOADER_PREFIX/lib/libvulkan.1.dylib" ]; then
293+ cp -L -f "$LOADER_PREFIX/lib/libvulkan.1.dylib" "$LIBDIR/libvulkan.1.dylib"
294+ install_name_tool -id "$PREFIX/libvulkan.1.dylib" "$LIBDIR/libvulkan.1.dylib"
295+ ln -sf libvulkan.1.dylib "$LIBDIR/libvulkan.dylib"
296+ for dep in $(otool -L "$LIBDIR/libvulkan.1.dylib" | grep -oE '/opt/homebrew/[^ ]+\.dylib' | sort -u); do
297+ if [ -f "$dep" ]; then
298+ process_dylib "$dep"
299+ dep_base="$(basename "$dep")"
300+ install_name_tool -change "$dep" "$PREFIX/$dep_base" "$LIBDIR/libvulkan.1.dylib"
301+ fi
302+ done
303+ else
304+ echo "::warning::Vulkan loader libvulkan.1.dylib not found; Zink hardware path will be unavailable"
282305 fi
306+
307+ # (2) The Vulkan-on-Metal drivers (ICDs). KosmicKrisp is the
308+ # macOS-26 hardware path; MoltenVK is bundled as a secondary.
283309 if [ -f "$MESA_PREFIX/lib/libvulkan_kosmickrisp.dylib" ]; then
284310 process_dylib "$MESA_PREFIX/lib/libvulkan_kosmickrisp.dylib"
285311 fi
286312 if [ -f "$MOLTEN_PREFIX/lib/libMoltenVK.dylib" ]; then
287313 process_dylib "$MOLTEN_PREFIX/lib/libMoltenVK.dylib"
288314 fi
289315
316+ # (3) ICD manifests so the loader can find a driver. library_path
317+ # is written RELATIVE to the manifest so it resolves wherever the
318+ # user installs the .app: the manifest lives at
319+ # Resources/share/vulkan/icd.d/, the dylibs at Resources/lib/, so
320+ # the relative path is ../../../lib/<dylib>. The previous version
321+ # copied a brew ICD path that does not exist, leaving icd.d empty.
322+ ICD_DIR="$APP/Contents/Resources/share/vulkan/icd.d"
323+ mkdir -p "$ICD_DIR"
324+ if [ -f "$LIBDIR/libvulkan_kosmickrisp.dylib" ]; then
325+ printf '%s\n' \
326+ '{' \
327+ ' "file_format_version": "1.0.0",' \
328+ ' "ICD": {' \
329+ ' "library_path": "../../../lib/libvulkan_kosmickrisp.dylib",' \
330+ ' "api_version": "1.3.0"' \
331+ ' }' \
332+ '}' > "$ICD_DIR/kosmickrisp.json"
333+ fi
334+ if [ -f "$LIBDIR/libMoltenVK.dylib" ]; then
335+ printf '%s\n' \
336+ '{' \
337+ ' "file_format_version": "1.0.0",' \
338+ ' "ICD": {' \
339+ ' "library_path": "../../../lib/libMoltenVK.dylib",' \
340+ ' "api_version": "1.2.0"' \
341+ ' }' \
342+ '}' > "$ICD_DIR/MoltenVK.json"
343+ fi
344+
290345 # Copy docs but exclude dotfiles (like .hugo_build.lock,
291346 # .gitignore) that get dropped during artifact upload and
292347 # break the code-signed bundle seal.
@@ -414,10 +469,32 @@ jobs:
414469 # there is no X11 display server. Without this, eglInitialize
415470 # tries to connect to xcb and fails with EGL_NOT_INITIALIZED.
416471 export EGL_PLATFORM=surfaceless
417- # Vulkan loader looks here for the KosmicKrisp / MoltenVK ICD.
418- export VK_DRIVER_FILES="\$RES/share/vulkan/icd.d/MoltenVK_icd.json"
419- export VK_ICD_FILENAMES="\$RES/share/vulkan/icd.d/MoltenVK_icd.json"
420472 export VULKAN_SDK="\$RES"
473+ # ---- Rendering backend selection ----
474+ # Default: Mesa llvmpipe software rendering. It is the only path
475+ # that can be verified in CI (GitHub's hosted macOS runners have
476+ # no GPU: KosmicKrisp reports "Failed to detect any valid GPUs")
477+ # and it works on every macOS version.
478+ #
479+ # On a real macOS 26+ Apple Silicon machine the GPU is reachable
480+ # through Mesa Zink -> KosmicKrisp -> Metal, which is far faster
481+ # than llvmpipe. KosmicKrisp only exposes the Vulkan features
482+ # Zink requires (VK_EXT_robustness2 nullDescriptor) on macOS 26,
483+ # so gate on the OS major version. Set BAR_FORCE_LLVMPIPE=1 to
484+ # force software rendering (e.g. inside a GPU-less VM).
485+ OS_MAJOR="\$(sw_vers -productVersion | cut -d. -f1)"
486+ KOSMIC_ICD="\$RES/share/vulkan/icd.d/kosmickrisp.json"
487+ if [ "\${BAR_FORCE_LLVMPIPE:-0}" != "1" ] && [ "\$OS_MAJOR" -ge 26 ] && [ -f "\$KOSMIC_ICD" ] && [ -f "\$RES/lib/libvulkan.1.dylib" ]; then
488+ export GALLIUM_DRIVER=zink
489+ export MESA_LOADER_DRIVER_OVERRIDE=zink
490+ export VK_ICD_FILENAMES="\$KOSMIC_ICD"
491+ export VK_DRIVER_FILES="\$KOSMIC_ICD"
492+ # Zink dlopen()s libvulkan.1.dylib by name. DYLD_FALLBACK is the
493+ # SAFE search path: unlike DYLD_LIBRARY_PATH it does not shadow
494+ # the system OpenGL framework, so it avoids the NSOpenGLContext
495+ # SIGBUS that a full DYLD_LIBRARY_PATH caused.
496+ export DYLD_FALLBACK_LIBRARY_PATH="\$RES/lib"
497+ fi
421498 cd "\$RES"
422499 exec "\$RES/bin/spring" \\
423500 --isolation-dir "\$GAME_DIR" \\
0 commit comments