x86_64 Windows OpenGL 4.6 Compatibility Profile workstation viewport executing inside a single native arm64 macOS process on Apple M4 (Metal 4 HUD: 2557×1464, 8.66 ms GPU).
Introduction
Over the past few years, running Windows DirectX 11 and DirectX 12 applications on Apple Silicon has matured rapidly through translation layers such as Apple’s D3DMetal, DXVK, and DXMT. However, a massive category of x86_64 Windows software remains stranded on macOS: professional engineering and 3D workstation applications—most notably mechanical CAD suites like SolidWorks, Autodesk Inventor, Siemens NX, and CATIA—that rely on OpenGL 4.5–4.6 (Compatibility Profile) and typically force Mac users into heavy virtual machines like Parallels or VMware.
Running these applications natively on Apple Silicon without hardware virtualization presents two fundamental architectural obstacles:
- The macOS OpenGL 4.1 Ceiling: Apple deprecated native OpenGL on macOS over a decade ago, freezing support at OpenGL 4.1 (Core Profile) and OpenGL 2.1 (Legacy Compatibility). Modern workstation 3D viewports require OpenGL 4.5 or 4.6 Compatibility Profile,
GLSL 4.60,ARB_direct_state_access, persistent buffer mappings, and compute/geometry shader pipelines. - The Deprecation of Rosetta 2: Apple has outlined the sunsetting of general-purpose Rosetta 2 binary translation in macOS 28. Furthermore, because Rosetta 2 operates at the Mach-O process boundary, running an
x86_64Wine process under Rosetta 2 forces the entire graphics translation stack—from the OpenGL state tracker down to the Vulkan ICD andMetal.framework—to execute as translatedx86_64code.
This article details the design, compilation, and driver-level patching required to run unmodified x86_64 Windows OpenGL 4.6 workstation applications inside a single, 100% native arm64 macOS process on an Apple M4. By combining Wine 11.20’s ARM64EC runtime, FEX-Emu (libarm64ecfex.dll), a custom COFF-ARM64EC build of Mesa 27.0 Zink, and a patched native arm64 Mesa KosmicKrisp Vulkan-on-Metal driver, guest applications transition out of CPU emulation at the OpenGL API boundary and execute the entire graphics stack natively on Apple Silicon.
Background & Prior Work
When Mesa’s KosmicKrisp (a native Vulkan-on-Metal driver built on Mesa’s NIR-to-MSL compiler infrastructure) achieved Vulkan 1.4 conformance, an immediate question arose in the graphics community: Can Mesa’s Zink (OpenGL-on-Vulkan) be paired with KosmicKrisp (Vulkan-on-Metal) to provide full OpenGL 4.6 support on macOS?
Two previous community efforts explored adjacent configurations:
- Native macOS EGL (Surfaceless Contexts): A documented workaround demonstrated native
arm64macOS Java applications running over Zink and KosmicKrisp via an EGL surfaceless context patch. While effective for native Mach-O binaries using EGL, it did not address Windows PE binaries, Wine, WGL window system integration (WSI), orx86_64binary translation. - WGL over Wine (
WineMetalGLEvaluation): Developers investigating OpenGL-on-Metal solutions for Wine recently reported that pairing WGL Zink with KosmicKrisp failed during initialization:
“glcts.exe cannot create the context for KHR-GL30, KHR-GL31, KHR-GL32 or KHR-GL33: 'Failed to create WGL context'. WithMESA_GL_VERSION_OVERRIDE=3.3, context creation succeeds, but the first query fails withGL_OUT_OF_MEMORY. Zink also reports that the device lacks its base requirementslogicOpandEXT_custom_border_color... Neither driver exposes geometry shaders, so Zink cannot offer GL 3.2 - 4.6 on either.”
Investigation into Mesa 27.0.0-devel reveals that KosmicKrisp now implements compute-based lowering (libpoly) for geometry and tessellation shaders, making OpenGL 4.6 viable. However, getting WGL context creation, multi-viewport swapchains, and workstation rendering pipelines working required diagnosing and resolving five specific issues across the compiler toolchain, Zink’s Kopper WSI layer, and KosmicKrisp’s image view and feature advertisements.
System Architecture: Escaping Emulation via ARM64EC
On macOS, the kernel strictly prohibits mixing x86_64 and arm64 Mach-O libraries within the same process address space. Previously, avoiding Rosetta 2 inside the graphics driver would have required serializing Vulkan or Gallium commands across an inter-process communication (IPC) socket to a separate arm64 render server—incurring serialization and synchronization overhead.
Instead, we leverage Wine’s ARM64EC ("Emulation Compatible") architecture alongside FEX-Emu:
┌────────────────────────────────────────────────────────────────────────────┐
│ Single 100% Native arm64 macOS Process (Kind: Apple, No Rosetta) │
│ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ Guest Windows Application (x86_64 PE Binary) │ │
│ │ • Unmodified commercial OpenGL 4.6 3D / CAD workstation engine │ │
│ │ • Translated in-process by FEX JIT (libarm64ecfex.dll) │ │
│ │ • Tuned with FEX_TSOENABLED=0 & WINEMSYNC=1 │ │
│ └──────────────────────────────────┬───────────────────────────────────┘ │
│ │ │
│ ═══ ARM64EC Thunk (In-Process Register Mapping) ═══ │
│ [Execution escapes CPU emulation here on every gl* call] │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ opengl32.dll + libgallium_wgl.dll (COFF-ARM64EC PE, Native aarch64) │ │
│ │ • Mesa 27.0.0-devel Zink (OpenGL 4.6 Compat -> Vulkan 1.4) │ │
│ │ • Built with llvm-mingw Clang 23 (-O3 -ffixed-x28) │ │
│ │ • GLSL -> NIR -> SPIR-V compiler executes natively on Apple M4 │ │
│ └──────────────────────────────────┬───────────────────────────────────┘ │
│ │ │
│ ═══ Wine PE-to-Unix Call Boundary (ARM64X) ═══ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ winevulkan.dll (ARM64X) ──► winevulkan.so (Native arm64 Mach-O) │ │
│ └──────────────────────────────────┬───────────────────────────────────┘ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ libvulkan_kosmickrisp.dylib (Native arm64 Mach-O, -mcpu=apple-m4) │ │
│ │ • Mesa 27.0.0-devel KosmicKrisp (Vulkan 1.4 -> Metal) │ │
│ │ • SPIR-V -> NIR -> MSL compiler & Kopper WSI bridge │ │
│ └──────────────────────────────────┬───────────────────────────────────┘ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ Apple Metal.framework (Native arm64 on Apple M4 GPU) │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────────────────┘
In this configuration, the host macOS process is a pure arm64 executable. Only the guest application binary (x86_64 PE) executes through FEX’s JIT (libarm64ecfex.dll).
Because Mesa’s opengl32.dll and libgallium_wgl.dll are compiled as COFF-ARM64EC PE binaries, the moment the application invokes any OpenGL entry point (glDrawElements, glBindBuffer, glCompileShader), execution crosses an in-process ARM64EC thunk directly into native aarch64 instructions. Zink’s state tracking, GLSL-to-SPIR-V compilation, winevulkan, KosmicKrisp’s SPIR-V-to-MSL compilation, and Metal.framework all run natively on the M4 CPU without IPC or Rosetta 2.
Step 1: Cross-Compiling Mesa Zink to COFF-ARM64EC
Building Mesa’s WGL and Zink targets (opengl32.dll and libgallium_wgl.dll) as COFF-ARM64EC binaries using llvm-mingw (arm64ec-w64-mingw32-clang, Clang 23.1.5) requires addressing two critical ABI and build-system issues.
1. Resolving the __x86_64__ and __arm64ec__ Collision in detect_arch.h
Under the Windows ARM64EC ABI specification, the compiler defines both __x86_64__ / _M_X64 (to ensure C/C++ headers expose x86_64 struct layouts and data types) and __arm64ec__ / _M_ARM64EC (as the backend emits aarch64 machine code).
In unpatched Mesa, src/util/detect_arch.h checks for __x86_64__ first, sets DETECT_ARCH_X86_64 = 1, and attempts to include x86 <mmintrin.h> and <xmmintrin.h> SSE intrinsics during an AArch64 compilation. Excluding _M_ARM64EC and __arm64ec__ from DETECT_ARCH_X86_64 and routing them to DETECT_ARCH_AARCH64 resolves the build failure:
--- a/src/util/detect_arch.h
+++ b/src/util/detect_arch.h
@@ -42,7 +42,7 @@
#define DETECT_ARCH_X86 1
#endif
-#if defined(_M_X64) || defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64)
+#if (defined(_M_X64) || defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64)) && !defined(_M_ARM64EC) && !defined(__arm64ec__)
#define DETECT_ARCH_X86_64 1
#endif
@@ -82,7 +82,7 @@
#define DETECT_ARCH_ARM 1
#endif
-#if defined(__aarch64__) || defined(_M_ARM64)
+#if defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC) || defined(__arm64ec__)
#define DETECT_ARCH_AARCH64 1
#endif
2. Preserving Wine’s TEB Register on Darwin (-ffixed-x28)
On native Windows for ARM64, the x18 register points to the Thread Environment Block (TEB). On macOS, however, the Darwin kernel reserves x18 for platform use. Consequently, native arm64 Wine on macOS repurposes x28 to hold the TEB pointer.
Any custom ARM64EC or ARM64 PE DLL loaded into the process must be compiled with -ffixed-x28 so LLVM’s register allocator does not clobber Wine’s TEB pointer during register-heavy routines (such as NIR shader optimization passes):
[built-in options]
c_args = ['-O3', '-ffixed-x28']
cpp_args = ['-O3', '-ffixed-x28']
macOS Entitlement Note: On macOS 27+, the host Wine .app bundle must also be signed with com.apple.developer.cross-architecture-support and JIT entitlements; otherwise, early TEB allocation fails in virtual_alloc_first_teb with status c0000017.
Step 2: Resolving WGL, Swapchain, and Metal Validation Failures
With libgallium_wgl.dll (COFF-ARM64EC) and libvulkan_kosmickrisp.dylib (arm64 Mach-O, compiled with -mcpu=apple-m4 -O3) in place, tracing the initialization of multi-viewport workstation applications exposed four driver-level incompatibilities.
1. Advertising VK_EXT_custom_border_color in KosmicKrisp
During screen initialization, Zink checks for VK_EXT_custom_border_color and VK_EXT_border_color_swizzle. Upstream KosmicKrisp implements these extensions in src/kosmickrisp/vulkan/kk_physical_device.c, but gates them behind an experimental flag (KK_EXPERIMENTAL(CUSTOM_BORDER)) because Metal’s native MTLSamplerBorderColor enum only supports fixed border constants (TransparentBlack, OpaqueBlack, OpaqueWhite).
Enabling .EXT_custom_border_color = true and .EXT_border_color_swizzle = true unconditionally satisfies Zink’s OpenGL 4.6 capability checks and avoids expensive fragment-shader sampler border emulation for applications that rely on standard border color constants.
2. Handling 16-bit WGL Display Targets & Swapchain Fallbacks (zink_kopper.c)
Why did previous WGL tests on Zink + KosmicKrisp fail with Failed to create WGL context and VK_ERROR_FORMAT_NOT_SUPPORTED?
Win32 workstation applications routinely create multiple WGL contexts—including hidden dummy contexts, icon/thumbnail renderers, and offscreen picking buffers—that request legacy 16-bit pixel formats such as B5G5R5A1_UNORM. When Zink’s Kopper WSI layer attempts to create a VkSwapchainKHR with a 16-bit format over winevulkan, macOS’s Metal WSI (CAMetalLayer) rejects it, as CAMetalLayer only supports specific 32-bit and 64-bit formats (B8G8R8A8_UNORM, B8G8R8A8_SRGB, R16G16B16A16_SFLOAT, A2R10G10B10).
We address this in two layers:
- In
src/gallium/drivers/zink/zink_screen.c(zink_is_format_supported), reject formats with< 32bits whenPIPE_BIND_DISPLAY_TARGETis requested. - In
src/gallium/drivers/zink/zink_kopper.c(kopper_CreateSwapchain), implement a fallback toVK_FORMAT_B8G8R8A8_UNORMwhenCreateSwapchainKHRreturnsVK_ERROR_FORMAT_NOT_SUPPORTED:
VkResult error = VKSCR(CreateSwapchainKHR)(screen->dev, &cswap->scci, NULL, &cswap->swapchain);
if (error == VK_ERROR_FORMAT_NOT_SUPPORTED) {
cswap->scci.imageFormat = VK_FORMAT_B8G8R8A8_UNORM;
cswap->scci.imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
cswap->scci.flags &= ~VK_SWAPCHAIN_CREATE_MUTABLE_FORMAT_BIT_KHR;
cswap->scci.pNext = NULL;
if (cdt->formats)
cdt->formats[0] = VK_FORMAT_B8G8R8A8_UNORM;
error = VKSCR(CreateSwapchainKHR)(screen->dev, &cswap->scci, NULL, &cswap->swapchain);
}
3. Guarding Against Metal Texture View Bit-Depth Assertions (kk_image_view.c)
After falling back from 16-bit B5G5R5A1_UNORM (vk=8) to 32-bit B8G8R8A8_UNORM (vk=44) in kopper_CreateSwapchain, Metal’s validation layer triggered a fatal assertion during texture view creation:
_mtlValidateArgumentsForTextureViewOnDevice: source texture pixelFormat (MTLPixelFormatBGRA8Unorm) not compatible with texture view pixelFormat (MTLPixelFormatBGR5A1Unorm)
While the underlying swapchain VkImage was allocated as 32-bit BGRA8Unorm, Zink’s upper Gallium pipe_resource state tracker retained the originally requested 16-bit PIPE_FORMAT_B5G5R5A1_UNORM descriptor and requested a 16-bit VkImageView aliasing the 32-bit swapchain image.
To prevent a hard crash inside Metal.framework, we add a block-size validation guard in src/kosmickrisp/vulkan/kk_image_view.c: if both source and view formats are uncompressed and their util_format_get_blocksizebits() values differ, KosmicKrisp clamps the view format to plane->layout.format (BGRA8Unorm).
4. Unified Memory Heap & Host Transfer Flags (kk_physical_device.c & kk_image.c)
On Apple Silicon’s unified memory architecture, system memory is simultaneously DEVICE_LOCAL and HOST_VISIBLE. However, certain OpenGL/Vulkan memory allocators strictly distinguish between device-local and host-visible memory types or strip non-host-visible types when VK_IMAGE_USAGE_HOST_TRANSFER_BIT_EXT is set. Exposing an explicit VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT entry on KosmicKrisp’s unified heap and preserving all memory types on host-transfer images ensures optimal buffer and texture placement.
Step 3: Performance Engineering for 3D Workstation Viewports
Getting an OpenGL 4.6 Compatibility Profile context to render is only the first step; achieving interactive frame rates in complex engineering assemblies requires eliminating both GPU shader amplification and CPU memory-ordering bottlenecks.
1. Eliminating Geometry-Shader-to-Compute Amplification on Wireframe Edges
Unlike gaming workloads (which primarily rasterize textured triangle meshes), CAD and 3D modeling viewports draw tens of thousands of feature edges, construction lines, and wireframe overlays per frame, typically with GL_LINE_SMOOTH enabled.
Because Apple Silicon GPUs do not support hardware smooth lines (MTLPrimitiveTypeLine only rasterizes 1-pixel Bresenham lines), upstream KosmicKrisp reports smoothLines = false. When Zink observes smoothLines == false, it lowers every GL_LINE_SMOOTH draw call into an emulated Geometry Shader (nir_lower_line_smooth) that expands lines into triangle strips. Because Apple Metal also lacks native Geometry Shaders, KosmicKrisp compiles Zink’s generated Geometry Shader into multi-pass compute shader pre-passes (libpoly).
In edge-heavy 3D viewports, converting every wireframe pass into a Geometry-Shader-to-Compute pipeline creates a severe GPU bottleneck.
Engineering Trade-Off: In src/kosmickrisp/vulkan/kk_physical_device.c, we enable VK_EXT_line_rasterization with .bresenhamLines = true and advertise .smoothLines = true. Advertising smoothLines = true is a deliberate workstation optimization rather than a Vulkan CTS-compliant smooth-line implementation. It prevents Zink from emitting Geometry Shaders for GL_LINE_SMOOTH and allows KosmicKrisp to submit native hardware MTLPrimitiveTypeLine primitives directly to the M4 rasterizer. Paired with Wine’s native Retina (192 DPI) mode ("RetinaMode"="y") and full-scene hardware MSAA—which is nearly free on Apple Silicon’s tile-based deferred renderer—and with optional application-side geometry-shader silhouette passes disabled, viewport rendering remains crisp while GPU frame times drop substantially.
In modern OpenGL 4.5+ CAD viewports—such as the Enhanced Graphics Performance pipeline used when rendering multi-part .SLDASM and .SLDPRT assemblies with hardware-accelerated RealView shaders and Order-Independent Transparency (OIT)—combining this rasterization path with Wine’s native Retina (192 DPI) mode and full-scene MSAA eliminates compute-shader bottlenecks while keeping feature edges razor-sharp. Similarly, in single-threaded geometric modeling kernels like Parasolid, disabling software TSO barriers (FEX_TSOENABLED=0) restores near-native rebuild and tessellation speeds.
2. CPU Memory Ordering & Thread Synchronization (FEX_TSOENABLED=0 & WINEMSYNC=1)
Even with the graphics stack executing natively as arm64, initial interactive benchmarks exhibited high CPU frame intervals during scene-graph traversal and geometric kernel recalculations.
The primary bottleneck was x86 Total Store Ordering (TSO) emulation. Whereas x86_64 enforces strong memory ordering, ARM64 utilizes a weakly ordered memory model. By default, FEX-Emu inserts memory barriers (dmb ish / load-acquire / store-release instructions) around memory operations in JIT-translated x86_64 guest code to preserve strict TSO semantics. In single-threaded geometric modeling kernels and scene-graph traversal loops that perform millions of pointer dereferences per second, software TSO barriers severely degrade throughput.
Configuring two runtime environment variables resolved the CPU bottleneck:
export WINEMSYNC=1 # Uses Mach semaphores in shared memory, bypassing wineserver socket round-trips across ~40 application threads
export FEX_TSOENABLED=0 # Disables software TSO barriers on translated x86 loads and stores
Disabling software TSO barriers alongside Mach semaphore synchronization (WINEMSYNC=1) eliminated thread contention and restored fluid viewport interaction.
Verification & Results on Apple M4
arm64 process).
Testing the completed stack on an Apple M4 (10-core CPU, 16 GB Unified Memory, macOS 27.2) with Apple’s Metal HUD (MTL_HUD_ENABLED=1) confirms the runtime configuration:
- Process Execution: 100% Native
arm64(Kind: Applein Activity Monitor; zero Rosetta 2 dependencies). - OpenGL Context Capabilities:
GL_VENDOR: MesaGL_RENDERER: zink Vulkan 1.4 (Apple M4 (MESA_KOSMICKRISP))GL_VERSION: 4.6 (Compatibility Profile) Mesa 27.0.0-develGLSL: 4.60
- Validated Pipeline Features: Hardware-accelerated PBR viewport shaders, dynamic environment reflections, ambient occlusion, shadows, order-independent transparency, and multi-window WGL child swapchains.
Broader Applicability
Because every component of this stack operates at the standard WGL, OpenGL, Vulkan, and ARM64EC boundaries, the resulting runtime is completely application-agnostic. Any x86_64 Windows application constrained by macOS’s OpenGL 4.1 ceiling and the upcoming removal of Rosetta 2 can leverage this exact architecture, including:
- Mechanical CAD, CAM & CAE Suites: Parametric 3D modeling systems, finite element analysis (FEA) pre/post-processors, and computational fluid dynamics (CFD) visualizers relying on OpenGL 4.5–4.6 Compatibility Profile.
- Electronic Design Automation (EDA): 3D PCB layout and enclosure verification tools.
- Point-Cloud & Photogrammetry Software: High-density mesh and point-cloud inspection tools (such as CloudCompare and MeshLab).
- Legacy & Modern OpenGL 4.6 Engines: Windows-only OpenGL 4.5/4.6 titles, level editors, and simulation tools.
Acknowledgments
This integration builds directly upon the foundational work of several open-source projects and maintainers:
- The Mesa KosmicKrisp & NIR-to-MSL Contributors (LunarG, Collabora, Valve, and the Mesa Community): For developing a Vulkan 1.4-conformant driver on top of Apple Metal—including
libpolycompute lowering—capable of supporting Zink. - Mike Blumenkrantz & the Mesa Zink Team: For engineering a high-performance OpenGL 4.6 Compatibility Profile implementation over Vulkan.
- The Wine, CodeWeavers, & FEX-Emu Teams: For architecting the
ARM64EC+libarm64ecfex.dllin-process translation model that allows execution to transition seamlessly fromx86_64guest code into nativearm64system libraries. - Martin Storsjö (
llvm-mingw) & theMetalSharp-WineMaintainers: For providing thearm64ec-w64-mingw32Clang toolchain and the signed, entitlement-ready macOSarm64Wine 11.20 runtime environment.
Comments & Discussion