SDL: cocoa: only consider a bundled libMoltenVK.dylib if it exists

From ed562533ece3ab06f7b047130ff51b26bdd68d91 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Matthias=20D=C3=B6rfelt?= <[EMAIL REDACTED]>
Date: Mon, 21 Sep 2026 14:59:25 -0700
Subject: [PATCH] cocoa: only consider a bundled libMoltenVK.dylib if it exists

- otherwise take the default vulkan loader path
- Fixes #16366
---
 src/video/cocoa/SDL_cocoavulkan.m | 52 +++++++++++++++++++++++++++++++
 1 file changed, 52 insertions(+)

diff --git a/src/video/cocoa/SDL_cocoavulkan.m b/src/video/cocoa/SDL_cocoavulkan.m
index 74c64733abf41..a75fd36364419 100644
--- a/src/video/cocoa/SDL_cocoavulkan.m
+++ b/src/video/cocoa/SDL_cocoavulkan.m
@@ -34,6 +34,8 @@
 #include "SDL_cocoavulkan.h"
 
 #include <dlfcn.h>
+// for _NSGetExecutablePath
+#include <mach-o/dyld.h>
 
 const char *defaultPaths[] = {
     "@executable_path/../Frameworks/libMoltenVK.dylib",
@@ -48,6 +50,53 @@
 // Since libSDL is most likely a .dylib, need RTLD_DEFAULT not RTLD_SELF.
 #define DEFAULT_HANDLE RTLD_DEFAULT
 
+// This function filters the library search paths to only consider absolute paths if they exist.
+//
+// dlopen will look for a library by filename. It will only use the provided path if the filename is not found in any
+// DYLD_LIBRARY_PATH.
+//
+// With "@executable_path/../Frameworks/libMoltenVK.dylib" being first, this means that it will always circumvent the
+// vulkan loader whenever any libMoltenVK.dylib is found in any discoverable location (which it always will if the
+// vulkan sdk is installed via setup-env.sh for example).
+//
+// We don't want to change the priority of the default paths as loading a bundled MoltenVK is genuinely correct. Instead,
+// we expand the requirements for an absolute (and thus @executable_path) path to have to exist in order to be considered
+// for SDL_LoadObject (dlopen).
+//
+// This gives us best of both worlds:
+// - if MoltenVK is bundled, it will be used
+// - otherwise it will use the regular vulkan loader path
+static bool Cocoa_Vulkan_LibraryPathIsUsable(const char *path)
+{
+    static const char prefix[] = "@executable_path/";
+    char expanded[PATH_MAX];
+    SDL_PathInfo info;
+
+    if (path[0] == '/') {
+        SDL_strlcpy(expanded, path, sizeof(expanded));
+    } else if (SDL_strncmp(path, prefix, sizeof(prefix) - 1) == 0) {
+        uint32_t size = sizeof(expanded);
+        char *slash;
+
+        // if the executable path fails to resolve...
+        if (_NSGetExecutablePath(expanded, &size) != 0) {
+            return true;
+        }
+        slash = SDL_strrchr(expanded, '/');
+        // ... or is invalid, we still pass it to SDL_LoadObject for the old behavior
+        if (!slash) {
+            return true;
+        }
+        slash[1] = '\0';
+        SDL_strlcat(expanded, path + sizeof(prefix) - 1, sizeof(expanded));
+    } else {
+        // library or framework name, let SDL_LoadObject handle it
+        return true;
+    }
+
+    return SDL_GetPathInfo(expanded, &info) && info.type == SDL_PATHTYPE_FILE;
+}
+
 bool Cocoa_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path)
 {
     VkExtensionProperties *extensions = NULL;
@@ -93,6 +142,9 @@ bool Cocoa_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path)
 
         for (i = 0; i < numPaths && _this->vulkan_config.loader_handle == NULL; i++) {
             foundPath = paths[i];
+            if (!Cocoa_Vulkan_LibraryPathIsUsable(foundPath)) {
+                continue;
+            }
             _this->vulkan_config.loader_handle = SDL_LoadObject(foundPath);
         }