WgpuViewportWindow: stop using QSurface::MetalSurface on macOS

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 <noreply@anthropic.com>
This commit is contained in:
Dion Moult
2026-06-02 18:52:19 +10:00
parent aba8ac727d
commit 5ffae41bbd
+19 -9
View File
@@ -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() {