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);
}