ifcviewer-wgpu: bring up macOS Metal surface (task #32)

Without this, BonsaiViewer.app launched on macOS would show a white
viewport for the same reason Windows did before d4d0c934b — the
`createSurface()` else branch fell through to "wgpu surface creation
not yet wired for this platform", `init()` returned false, and the
Qt window background was all the user saw.

Pieces:

- `WgpuMetalSurface_mac.{h,mm}`: tiny Objective-C++ bridge. Takes the
  NSView pointer that Qt's `winId()` returns on macOS, attaches a
  CAMetalLayer (using Qt's existing one when surfaceType is
  MetalSurface, attaching one ourselves as a defensive fallback),
  sets `contentsScale` from the window's backing scale factor so
  retina drawables come out at native resolution, and returns the
  layer as `void*`. The .mm keeps the Objective-C namespace pollution
  out of WgpuViewportWindow.cpp.

- `WgpuViewportWindow` ctor: `setSurfaceType(QSurface::MetalSurface)`
  on macOS so Qt backs the NSView with a CAMetalLayer at window
  creation; OpenGLSurface elsewhere as before.

- `WgpuViewportWindow::createSurface()`: new `#elif defined(Q_OS_MAC)`
  branch that fills a `WGPUSurfaceSourceMetalLayer` with the layer
  pointer from the bridge and hands it to `wgpuInstanceCreateSurface`.

- `CMakeLists.txt`: `enable_language(OBJCXX)` + the .mm file added to
  the source list on Apple, and links `-framework Cocoa` (NSView) +
  `-framework QuartzCore` (CAMetalLayer).

`winId()` on macOS returns the backing NSView*, not the NSWindow* —
that's the layer-bearing host wgpu-native expects.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Dion Moult
2026-06-02 13:45:42 +10:00
parent d4d0c934b1
commit 21d394501f
4 changed files with 124 additions and 5 deletions
+32 -5
View File
@@ -686,11 +686,19 @@ static WGPUStringView svFromCStr(const char* s) {
WgpuViewportWindow::WgpuViewportWindow(QWindow* parent)
: QWindow(parent) {
// wgpu doesn't need a GL context; we just need a real native window that
// the platform window manager has actually created. OpenGLSurface is the
// most portable way to ask Qt for a hardware-rendering-ready native
// window — we never bind a GL context on top of it.
// 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
setSurfaceType(QSurface::OpenGLSurface);
#endif
}
WgpuViewportWindow::~WgpuViewportWindow() {
@@ -1818,6 +1826,10 @@ bool WgpuViewportWindow::probeAndCreatePool() {
# define WIN32_LEAN_AND_MEAN
# endif
# include <windows.h>
#elif defined(Q_OS_MAC)
// Cocoa bridge declared in WgpuMetalSurface_mac.h, implemented in the
// adjacent .mm file. Keeps Objective-C out of this pure-C++ TU.
# include "WgpuMetalSurface_mac.h"
#endif
bool WgpuViewportWindow::createSurface() {
@@ -1873,8 +1885,23 @@ bool WgpuViewportWindow::createSurface() {
hwndsrc.hwnd = reinterpret_cast<void*>(static_cast<uintptr_t>(winId()));
surface_desc.nextInChain = &hwndsrc.chain;
surface_ = wgpuInstanceCreateSurface(instance_, &surface_desc);
#elif defined(Q_OS_MAC)
// QWindow::winId() returns the backing NSView* on macOS (as WId,
// which is quintptr — same width as void* on all macOS arches we
// care about). Hand it to the Cocoa bridge to attach a
// CAMetalLayer, then wrap that layer in WGPUSurfaceSourceMetalLayer.
void* nsview = reinterpret_cast<void*>(static_cast<uintptr_t>(winId()));
void* layer = wgpu_macos_attach_metal_layer(nsview);
if (!layer) {
qWarning() << "Could not attach CAMetalLayer to the Qt NSView";
return false;
}
WGPUSurfaceSourceMetalLayer metalsrc = {};
metalsrc.chain.sType = WGPUSType_SurfaceSourceMetalLayer;
metalsrc.layer = layer;
surface_desc.nextInChain = &metalsrc.chain;
surface_ = wgpuInstanceCreateSurface(instance_, &surface_desc);
#else
// macOS Metal surface wiring lands with task #32.
qWarning() << "wgpu surface creation not yet wired for this platform";
return false;
#endif