macOS: Add portable libGL wrapper and fix library path discovery

- Add glwrapper subdirectory to build system for libgl_interpose.dylib and libGL.dylib
- Make libgl_interpose.c and libgl_wrapper.c use dladdr() to find libEGL relative to themselves
- Add MESA_EGL_LIBRARY and MESA_VULKAN_LIBRARY env var support for bypassing SIP's stripping of DYLD_LIBRARY_PATH
- Zink now checks MESA_VULKAN_LIBRARY before default VK_LIBNAME

This enables Mesa to work on macOS when DYLD_LIBRARY_PATH is stripped by System Integrity Protection, as happens with Java processes.
This commit is contained in:
Luca Mignatti
2026-01-02 11:17:31 -06:00
parent f9624417ea
commit a325151787
5 changed files with 126 additions and 16 deletions

View File

@@ -3370,7 +3370,16 @@ zink_internal_create_screen(const struct pipe_screen_config *config, int64_t dev
u_trace_state_init();
screen->loader_lib = util_dl_open(VK_LIBNAME);
/* Check for MESA_VULKAN_LIBRARY environment variable first (useful on macOS where
* SIP strips DYLD_LIBRARY_PATH from hardened processes like Java) */
const char *vk_lib_env = getenv("MESA_VULKAN_LIBRARY");
if (vk_lib_env) {
screen->loader_lib = util_dl_open(vk_lib_env);
if (screen->loader_lib)
fprintf(stderr, "ZINK: loaded vulkan from MESA_VULKAN_LIBRARY=%s\n", vk_lib_env);
}
if (!screen->loader_lib)
screen->loader_lib = util_dl_open(VK_LIBNAME);
if (!screen->loader_lib) {
if (!screen->driver_name_is_inferred)
mesa_loge("ZINK: failed to load "VK_LIBNAME);

View File

@@ -1,9 +1,18 @@
/*
* Mesa libGL wrapper for macOS EGL using dlsym interposition
*
* This library intercepts dlsym calls to redirect GL function lookups
* to Mesa's EGL implementation. It finds libEGL.dylib relative to itself,
* making the install location-independent.
*
* It also sets MESA_EGL_LIBRARY and MESA_VULKAN_LIBRARY environment variables
* so that GLFW and Zink can find the libraries when DYLD_LIBRARY_PATH is
* stripped by SIP on macOS.
*/
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include <EGL/egl.h>
@@ -16,23 +25,75 @@
static void *egl_handle = NULL;
static PFNEGLGETPROCADDRESSPROC mesa_eglGetProcAddress = NULL;
static int initialized = 0;
static int env_vars_set = 0;
static char lib_dir[1024] = {0};
static char egl_lib_path[1024] = {0};
/* Determine the library directory and EGL path */
static void determine_paths(void) {
if (lib_dir[0] != '\0') return;
Dl_info info;
if (dladdr((void*)determine_paths, &info) && info.dli_fname) {
const char *last_slash = strrchr(info.dli_fname, '/');
if (last_slash) {
size_t dir_len = last_slash - info.dli_fname;
snprintf(lib_dir, sizeof(lib_dir), "%.*s", (int)dir_len, info.dli_fname);
snprintf(egl_lib_path, sizeof(egl_lib_path), "%s/libEGL.dylib", lib_dir);
} else {
snprintf(egl_lib_path, sizeof(egl_lib_path), "libEGL.dylib");
}
} else {
snprintf(egl_lib_path, sizeof(egl_lib_path), "libEGL.dylib");
}
}
/* Set environment variables for library paths (called lazily) */
static void set_env_vars(void) {
if (env_vars_set) return;
env_vars_set = 1;
determine_paths();
/* Set EGL library path */
setenv("MESA_EGL_LIBRARY", egl_lib_path, 1);
fprintf(stderr, "libGL interpose: Set MESA_EGL_LIBRARY=%s\n", egl_lib_path);
/* Set Vulkan library path */
if (lib_dir[0] != '\0') {
char vk_lib_path[1024];
snprintf(vk_lib_path, sizeof(vk_lib_path), "%s/libvulkan.1.dylib", lib_dir);
setenv("MESA_VULKAN_LIBRARY", vk_lib_path, 1);
fprintf(stderr, "libGL interpose: Set MESA_VULKAN_LIBRARY=%s\n", vk_lib_path);
}
}
static void ensure_initialized(void) {
if (initialized) return;
initialized = 1;
egl_handle = dlopen("/Users/lucamignatti/mesa-native/lib/libEGL.dylib", RTLD_NOW | RTLD_LOCAL);
determine_paths();
set_env_vars();
egl_handle = dlopen(egl_lib_path, RTLD_NOW | RTLD_LOCAL);
if (egl_handle) {
fprintf(stderr, "libGL interpose: Loaded Mesa EGL\n");
fprintf(stderr, "libGL interpose: Loaded Mesa EGL from %s\n", egl_lib_path);
mesa_eglGetProcAddress = (PFNEGLGETPROCADDRESSPROC)dlsym(egl_handle, "eglGetProcAddress");
if (mesa_eglGetProcAddress) {
fprintf(stderr, "libGL interpose: Ready to forward GL calls\n");
}
} else {
fprintf(stderr, "libGL interpose: Failed to load %s: %s\n", egl_lib_path, dlerror());
}
}
/* Interpose dlsym to catch GL function lookups */
void *my_dlsym(void *handle, const char *symbol) {
static void *my_dlsym(void *handle, const char *symbol) {
/* Set env vars early so other libraries can use them */
if (!env_vars_set) {
set_env_vars();
}
ensure_initialized();
/* If looking for a GL function, use eglGetProcAddress */

View File

@@ -34,22 +34,29 @@ static PFNEGLGETPROCADDRESSPROC mesa_eglGetProcAddress = NULL;
__attribute__((constructor))
static void init_egl_loader(void) {
const char *egl_paths[] = {
"/Users/lucamignatti/mesa-native/lib/libEGL.dylib",
"libEGL.dylib",
NULL
};
/* Find libEGL.dylib relative to this library (they're in the same directory) */
Dl_info info;
char egl_path[1024];
for (int i = 0; egl_paths[i] != NULL; i++) {
egl_handle = dlopen(egl_paths[i], RTLD_NOW | RTLD_LOCAL);
if (egl_handle) {
fprintf(stderr, "libGL wrapper: Loaded EGL from %s\n", egl_paths[i]);
break;
if (dladdr((void*)init_egl_loader, &info) && info.dli_fname) {
/* Get directory of this library */
const char *last_slash = strrchr(info.dli_fname, '/');
if (last_slash) {
size_t dir_len = last_slash - info.dli_fname;
snprintf(egl_path, sizeof(egl_path), "%.*s/libEGL.dylib", (int)dir_len, info.dli_fname);
} else {
snprintf(egl_path, sizeof(egl_path), "libEGL.dylib");
}
} else {
/* Fallback to just the filename, let dyld search */
snprintf(egl_path, sizeof(egl_path), "libEGL.dylib");
}
if (!egl_handle) {
fprintf(stderr, "libGL wrapper: Failed to load libEGL.dylib\n");
egl_handle = dlopen(egl_path, RTLD_NOW | RTLD_LOCAL);
if (egl_handle) {
fprintf(stderr, "libGL wrapper: Loaded EGL from %s\n", egl_path);
} else {
fprintf(stderr, "libGL wrapper: Failed to load %s: %s\n", egl_path, dlerror());
return;
}

30
src/glwrapper/meson.build Normal file
View File

@@ -0,0 +1,30 @@
# Copyright © 2024 Luca Mignatti
# SPDX-License-Identifier: MIT
# libgl_interpose.dylib - intercepts dlsym calls to redirect GL lookups to EGL
# This is macOS-specific and uses DYLD_INTERPOSE
libgl_interpose = shared_library(
'gl_interpose',
'libgl_interpose.c',
include_directories : [inc_include],
dependencies : [dep_dl],
install : true,
name_prefix : 'lib',
name_suffix : 'dylib',
)
# libGL.dylib - wrapper that provides GL function symbols forwarded to EGL
# This is for applications that load libGL and dlsym for GL functions
libgl_wrapper = shared_library(
'GL',
'libgl_wrapper.c',
include_directories : [inc_include],
dependencies : [dep_dl],
# These functions are intentionally exported without prototypes
c_args : ['-Wno-missing-prototypes'],
install : true,
name_prefix : 'lib',
name_suffix : 'dylib',
)

View File

@@ -153,6 +153,9 @@ endif
if with_egl
subdir('egl')
endif
if host_machine.system() == 'darwin' and with_egl
subdir('glwrapper')
endif
if with_gallium and with_gbm
if with_glx == 'dri' or with_platform_x11 or with_platform_xcb
subdir('gallium/targets/dril')