TL;DR: I got OpenGL 4.6 apps (Minecraft) running on macOS by translating OpenGL → Vulkan → Metal using Mesa's Zink driver and the KosmicKrisp Vulkan implementation.
Minecraft (OpenGL 4.6) → Zink → Vulkan → KosmicKrisp → Metal → GPU
# Core build tools
brew install meson ninja python3
# Mesa dependencies
brew install bison flex llvm glslang spirv-tools molten-vk
# Add LLVM and bison to PATH (Apple's versions are outdated)
export PATH="/opt/homebrew/opt/llvm/bin:/opt/homebrew/opt/bison/bin:$PATH"git clone https://github.com/lucamignatti/mesa.git
cd mesa
# Create native file for Homebrew bison
cat > native.ini << 'EOF'
[binaries]
bison = '/opt/homebrew/opt/bison/bin/bison'
EOF
# Configure
meson setup build --native-file native.ini \
-Dprefix=$HOME/mesa-native \
-Dbuildtype=release \
-Dplatforms=macos \
-Degl-native-platform=surfaceless \
-Degl=enabled \
-Dgallium-drivers=zink \
-Dvulkan-drivers=kosmickrisp \
-Dgles1=enabled \
-Dgles2=enabled \
-Dglx=disabled \
-Dgbm=disabled \
-Dmoltenvk-dir=/opt/homebrew/opt/molten-vk
# Build and install
ninja -C build
ninja -C build install
# Configure DRI
mkdir -p $HOME/mesa-native/lib/dri
cd $HOME/mesa-native/lib/dri
ln -sf ../libgallium-26.0.0-devel.dylib zink_dri.so
ln -sf ../libgallium-26.0.0-devel.dylib swrast_dri.so
# Copy vulkan loader
cp /opt/homebrew/lib/libvulkan.1.dylib $HOME/mesa-native/lib/git clone https://github.com/lucamignatti/glfw.git
cd glfw
mkdir build && cd build
cmake .. -DGLFW_BUILD_EXAMPLES=OFF -DGLFW_BUILD_TESTS=OFF -DBUILD_SHARED_LIBS=ON
make -j8The library is at build/src/libglfw.3.dylib.
Environment Variables (set in your launcher):
DYLD_INSERT_LIBRARIES=$HOME/mesa-native/lib/libgl_interpose.dylib
DYLD_LIBRARY_PATH=$HOME/mesa-native/lib
LIBGL_DRIVERS_PATH=$HOME/mesa-native/lib/dri
VK_DRIVER_FILES=$HOME/mesa-native/share/vulkan/icd.d/kosmickrisp_mesa_icd.aarch64.json
EGL_PLATFORM=surfaceless
MESA_LOADER_DRIVER_OVERRIDE=zink
MESA_GL_VERSION_OVERRIDE=4.6
MESA_GLSL_VERSION_OVERRIDE=460JVM Arguments:
-Dorg.lwjgl.egl.libname=$HOME/mesa-native/lib/libEGL.dylib
-Dorg.lwjgl.opengl.libname=$HOME/mesa-native/lib/libGL.dylib
-Dorg.lwjgl.glfw.libname=/path/to/glfw/build/src/libglfw.3.dylib
Known Issues
- The game initially only renders in the bottom left quarter of the window. Resize the window to fix.
Apple deprecated OpenGL in 2018 and capped macOS at OpenGL 4.1. But shader packs, mods, and modern Minecraft features increasingly expect OpenGL 4.6. Apple's answer is Metal, but that doesnt help us.
Recently, the KosmicKrisp driver was created as a part of mesa. This is the first compliant vulkan driver on macOS, and according to various MRs it is capable of running zink. So, we translate OpenGL to Vulkan using Mesa's Zink driver, then translate Vulkan to Metal using KosmicKrisp. Sounds simple right? It wasn't.
Zink requires many different vulkan features to be supported to impliment correct and performant openGL, and until recently, kosmickrisp didnt support all of them. currently, it's lacking one non-critical feature: NullDescriptor. Fortunately a current MR enables zink to work around this. So, we apply this MR and it's off to the races. Except that it's not.
Mesa's EGL implementation needs a "platform" to talk to the display server. On Linux, this is X11, Wayland, or DRM/KMS. macOS has none of these. I tried creating a platform_macos.c from scratch. Dead end. it would require weeks of work to implement the full EGL platform interface. Then I noticed something: the surfaceless platform. It's designed for headless rendering such as GPU compute, offscreen rendering, or CI testing. It explicitly has no window support. But it does talk to Vulkan via Kopper, Zink's WSI layer. What if we just... added window support to surfaceless? On macOS only?
This is cursed. A platform called "surfaceless" that creates window surfaces. But it works. I added a macOS-specific code path in platform_surfaceless.c that activates when Kopper detects Metal support. The "Metal display vtbl" includes create_window_surface, swap_buffers, and swap_interval. Functions that surfaceless normally doesn't have. We also force the software driver probe path, because macOS has no DRM devices to enumerate.
GLFW on macOS defaults to NSGL, which is Apple's native OpenGL implementation. That's the capped 4.1 version we're trying to escape. I patched cocoa_window.m to route all context requests to EGL instead. This way Mesa handles the context, not Apple's deprecated driver.
GLFW was now using EGL, but Minecraft still crashed. LWJGL (Minecraft's native library layer) loads OpenGL function pointers by calling dlsym on libGL.dylib. But our OpenGL implementation lives in EGL. there is no system libGL.dylib that knows about our context.
I created a small interposition library (libgl_interpose.dylib) that uses macOS's DYLD_INTERPOSE mechanism to intercept all dlsym calls. When code asks for a function starting with gl, we redirect to eglGetProcAddress instead. Then, we inject this via DYLD_INSERT_LIBRARIES and a launch argument. Now when LWJGL asks for glGenBuffers, it gets Mesa's implementation instead of nothing.
First launch. Immediate crash: vkCreateMetalSurfaceEXT failed. Debugging revealed the issue: GLFW passes us an NSView's backing layer. By default that's NSViewBackingLayer, which is a generic 2D compositing layer. Metal needs a CAMetalLayer.
We can't ask GLFW to change this without major patches. So we swap the layer ourselves. When our surface creation callback is invoked, we check the layer class using Objective-C runtime introspection. If it's NSViewBackingLayer, we create a fresh CAMetalLayer, configure it (frame, scale factor, opaque), and attach it to the view. All of this must happen on the main thread, CoreAnimation's requirement. We use dispatch_sync_f() to safely dispatch from render threads.
Minecraft launched! Into a 1×1 pixel window.
The Kopper interface has a GetDrawableInfo() callback to query window dimensions. Our implementation queried [layer drawableSize]. But CAMetalLayer.drawableSize isn't populated until the next CoreAnimation layout pass, which hasn't happened yet because we just created the layer. So, we explicitly set drawableSize when creating the layer, matching the view bounds times the scale factor. Now the first query returns sensible values.
Minecraft would sometimes crash with SIGBUS in CoreAnimation internals when querying drawable size. Completely non-deterministic.
After adding backtraces, I identified the pattern: crashes happened when GetDrawableInfo() was called from a render thread while CoreAnimation was doing layout on the main thread. CoreAnimation is not thread-safe. We wrapped all size queries in dispatch_sync_f() to the main thread.
The first run works! the second run has crash in Metal shader compilation with invalid pointers though.
I discovered that KosmicKrisp caches compiled shaders to disk. The cache included Metal Pipeline State Object pointers, but those are process-specific. On the next launch, they're garbage pointing to deallocated GPU resources. So, I disable shader disk caching in KosmicKrisp. Performance loss is negligible for Minecraft anyway.
Minecraft loaded. The main menu background though, was gray. The world was also gray. Only UI elements where visible. oddly though, the 3d world was visible from behind f3 text and the chat box.
I added logging throughout the blit path. The RGB channels had correct values, but the gray areas all had alpha = 0. Minecraft renders the 3D world with alpha=0, it doesn't need transparency for terrain. On typical OpenGL, this is fine. But Metal's compositor respects alpha. Alpha=0 means "fully transparent". To fix this, I force alpha = 1.0 in the blit shaders.
Main menu rendered correctly! Loading a world... black frames. Every few frames where pure black.
Zink has two blit paths:
- vk_meta_blit (native): Uses Vulkan blit commands. Fast.
- util_blitter (Gallium fallback): Software-assisted, and more compatible.
For format conversions (RGBA↔BGRA when blitting to the swapchain), Zink was using vk_meta_blit. On KosmicKrisp, this exhibited intermittent black frames. The root cause is still unknown. possibly a synchronization issue in KosmicKrisp's blit. Regardless, I force format-conversion blits to use util_blitter instead. The performance penalty is about ~2-3%, which is acceptable for stable frames. Both paths now force alpha=1.
The game finally launched, looked right, and ran smoothly. But there was one final annoyance. Resizing the window didnt resize the view, and broke everything. The internal rendering resolution remained stuck at whatever size the window was when the game first appeared, leading to either black borders or extreme stretching.
Zink's Kopper layer has an update mechanism that is supposed to handle window resizing. However, this path was hardcoded to only query surface capabilities for X11 platform surfaces. For every other surface type, including our new Metal surfaces, it just returned the current resource dimensions, effectively telling the driver that the size had never changed.
I extended zink_kopper_update and the DRI frontend to handle KOPPER_METAL surfaces. Now, every frame, Zink queries the current actual size of the CAMetalLayer via Vulkan surface capabilities. If there's a mismatch, it triggers a swapchain recreation. After this change Minecraft responds correctly to window resizes, maintaining the correct aspect ratio and resolution as you resize.
70fps, shader packs load, modern mods work, and f3 reports OpenGL 4.6 on macOS, running entirely through Mesa, Vulkan, and Metal.
The surfaceless platform has surfaces, the blit is emulated and lies, we swap layer types at runtime, and theres no shader cache. It's held together with the most hackiest of hacks.
But it works. mostly. There are still bugs in rendering deep in kosmickrisp somewhere (youll notice the hotbar, for example, looks strange). not all shaders work as kosmickrisp doesnt expose all needed features for zink to run them, and voxy doesnt have the indirect GL features it needs as kosmickrisp doesnt support them yet, and performance is lacking. But maybe one day not to far away, everything will work. This at least proves that it can. On another note, it's not impossible this is the first time ever openGL 4.6 has run on macOS, which is cool.



Impressive! could you maybe leave a repo with your individually edited files so anyone could reproduce it? Also wanna play on Mac :). The interpose.dylib is missing