From 5ffae41bbdb4ca8433fd92c15da0e974bdf7abd6 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 2 Jun 2026 18:52:19 +1000 Subject: [PATCH] WgpuViewportWindow: stop using QSurface::MetalSurface on macOS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NSZombieEnabled-lldb on macOS revealed the actual cause of the "OS_os_log displayLock" / segfault that has been chasing us: *** -[QMetalLayer displayLock]: message sent to deallocated instance 0xb5e234e40 Qt's QMetalLayer (its CAMetalLayer subclass installed when surfaceType == MetalSurface) is dealloc'd while Qt's QCocoaWindow still holds an internal reference to it. Once wgpu-native bridge-retains the layer in its Rust surface code and re-publishes the drawable pool from configureSurface, Qt's QMetalLayer life is implicitly handed to wgpu-native and Qt's separate ref winds up dangling. The next Qt expose event sends -displayLock to the dead pointer. Earlier guesses (Qt-6.11/macOS-26 incompatibility, multi-display) were both wrong — single-display still crashed, and the Tahoe os_log selector-cast theory was a red herring; the real error is "deallocated instance", revealed only by NSZombieEnabled. # Fix Set surfaceType to OpenGLSurface on macOS too. On macOS that still gives us a layer-backed NSView; Qt just doesn't install QMetalLayer. WgpuMetalSurface_mac.mm's else-branch (the one that always fires when the existing layer isn't already a CAMetalLayer) now consistently attaches a vanilla CAMetalLayer we fully own — wgpu-native can do whatever it wants to the layer's lifetime without stepping on any Qt-side bookkeeping. We never bind a real GL context on top of OpenGLSurface — it's just the most portable "hardware-rendering-ready surface" hint Qt has, and it's already what Linux and Windows use. Co-Authored-By: Claude Opus 4.7 --- src/ifcviewer-wgpu/WgpuViewportWindow.cpp | 28 +++++++++++++++-------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/ifcviewer-wgpu/WgpuViewportWindow.cpp b/src/ifcviewer-wgpu/WgpuViewportWindow.cpp index 621795982b..6c02c0fd0e 100644 --- a/src/ifcviewer-wgpu/WgpuViewportWindow.cpp +++ b/src/ifcviewer-wgpu/WgpuViewportWindow.cpp @@ -689,16 +689,26 @@ WgpuViewportWindow::WgpuViewportWindow(QWindow* parent) // wgpu doesn't need a GL context; we just need a real native window // whose backing layer matches the GPU API wgpu will drive. // - // - Linux/Windows: OpenGLSurface gives us a hardware-rendering-ready - // native window (XCB/HWND); we never bind a GL context on top. - // - macOS: MetalSurface tells Qt to back the NSView with a - // CAMetalLayer, which wgpu-native wraps via - // WGPUSurfaceSourceMetalLayer in createSurface(). -#if defined(Q_OS_MAC) - setSurfaceType(QSurface::MetalSurface); -#else + // - All platforms: OpenGLSurface gives us a hardware-rendering-ready + // native window (XCB/HWND/NSView). We never bind a GL context on + // top. + // + // - macOS specifically: we *don't* use QSurface::MetalSurface even + // though it'd be the "obvious" choice. Doing so makes Qt install + // its own CAMetalLayer subclass (QMetalLayer) on the NSView and + // keep an internal reference to it. Once wgpu-native (Rust) bridge- + // retains that layer and re-publishes its drawable pool in + // configureSurface, Qt's QMetalLayer winds up deallocated while + // Qt's internal reference still points at it, and the next Qt + // expose event aborts with: + // *** -[QMetalLayer displayLock]: + // message sent to deallocated instance ... + // With OpenGLSurface (which on macOS still gives us a layer-backed + // NSView), Qt doesn't install QMetalLayer; the + // WgpuMetalSurface_mac.mm bridge attaches a vanilla CAMetalLayer + // we fully own, and wgpu-native can do its lifetime gymnastics + // without stepping on Qt's bookkeeping. setSurfaceType(QSurface::OpenGLSurface); -#endif } WgpuViewportWindow::~WgpuViewportWindow() {