mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 17:31:45 +00:00
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:
@@ -108,6 +108,16 @@ file(GLOB IFCVIEWER_WGPU_CPP_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
|
|||||||
file(GLOB IFCVIEWER_WGPU_H_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.h)
|
file(GLOB IFCVIEWER_WGPU_H_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.h)
|
||||||
set(IFCVIEWER_WGPU_FILES ${IFCVIEWER_WGPU_CPP_FILES} ${IFCVIEWER_WGPU_H_FILES})
|
set(IFCVIEWER_WGPU_FILES ${IFCVIEWER_WGPU_CPP_FILES} ${IFCVIEWER_WGPU_H_FILES})
|
||||||
|
|
||||||
|
# Cocoa bridge for the CAMetalLayer surface attach — Objective-C++.
|
||||||
|
# Only compiled into the target on Apple platforms; CMake handles `.mm`
|
||||||
|
# natively once OBJCXX is enabled.
|
||||||
|
if(APPLE)
|
||||||
|
enable_language(OBJCXX)
|
||||||
|
list(APPEND IFCVIEWER_WGPU_FILES
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/WgpuMetalSurface_mac.mm
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Intentional source-level borrowing from the GL backend until ifcviewer-core
|
# Intentional source-level borrowing from the GL backend until ifcviewer-core
|
||||||
# is extracted (task #12). SidecarCache + InstancedGeometry have zero Qt /
|
# is extracted (task #12). SidecarCache + InstancedGeometry have zero Qt /
|
||||||
# OCCT / IFC-parse deps, so compiling them directly into IfcViewerWgpu is
|
# OCCT / IFC-parse deps, so compiling them directly into IfcViewerWgpu is
|
||||||
@@ -146,6 +156,14 @@ if(UNIX AND NOT APPLE)
|
|||||||
target_link_libraries(IfcViewerWgpu PUBLIC Threads::Threads)
|
target_link_libraries(IfcViewerWgpu PUBLIC Threads::Threads)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# Cocoa + QuartzCore for WgpuMetalSurface_mac.mm (NSView, CAMetalLayer).
|
||||||
|
if(APPLE)
|
||||||
|
target_link_libraries(IfcViewerWgpu PUBLIC
|
||||||
|
"-framework Cocoa"
|
||||||
|
"-framework QuartzCore"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
install(TARGETS IfcViewerWgpu EXPORT ${IFCOPENSHELL_EXPORT_TARGETS})
|
install(TARGETS IfcViewerWgpu EXPORT ${IFCOPENSHELL_EXPORT_TARGETS})
|
||||||
|
|
||||||
install(FILES ${IFCVIEWER_WGPU_H_FILES}
|
install(FILES ${IFCVIEWER_WGPU_H_FILES}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* Objective-C++ bridge between WgpuViewportWindow (pure C++) and Cocoa /
|
||||||
|
* QuartzCore (Objective-C). Compiled only on macOS — see CMakeLists.txt.
|
||||||
|
*
|
||||||
|
* Qt's QWindow::winId() returns the backing NSView* (as a WId) on macOS;
|
||||||
|
* we need a CAMetalLayer attached to that view to hand to wgpu-native
|
||||||
|
* via WGPUSurfaceSourceMetalLayer. Doing that requires Objective-C, so
|
||||||
|
* the actual layer attach lives in WgpuMetalSurface_mac.mm.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef WGPU_METAL_SURFACE_MAC_H
|
||||||
|
#define WGPU_METAL_SURFACE_MAC_H
|
||||||
|
|
||||||
|
#if defined(__APPLE__)
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// Ensures the given NSView has a CAMetalLayer as its backing layer.
|
||||||
|
/// Returns the CAMetalLayer pointer (`void*` so callers don't need to
|
||||||
|
/// pull QuartzCore into their TU); the layer is owned by the NSView.
|
||||||
|
/// Returns nullptr if `nsview_ptr` is null.
|
||||||
|
void* wgpu_macos_attach_metal_layer(void* nsview_ptr);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // __APPLE__
|
||||||
|
|
||||||
|
#endif // WGPU_METAL_SURFACE_MAC_H
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* Objective-C++ implementation of the Cocoa bridge declared in
|
||||||
|
* WgpuMetalSurface_mac.h. Compiled only on macOS.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "WgpuMetalSurface_mac.h"
|
||||||
|
|
||||||
|
#if defined(__APPLE__)
|
||||||
|
|
||||||
|
#import <AppKit/NSView.h>
|
||||||
|
#import <QuartzCore/CAMetalLayer.h>
|
||||||
|
|
||||||
|
void* wgpu_macos_attach_metal_layer(void* nsview_ptr) {
|
||||||
|
if (!nsview_ptr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
NSView* view = (__bridge NSView*)nsview_ptr;
|
||||||
|
|
||||||
|
// When QWindow::surfaceType is QSurface::MetalSurface, Qt already
|
||||||
|
// backs the NSView with a CAMetalLayer — just hand it back. Otherwise
|
||||||
|
// attach one ourselves (defensive: Qt's behaviour can change between
|
||||||
|
// major versions and 6.x has occasionally regressed this).
|
||||||
|
CAMetalLayer* layer = nil;
|
||||||
|
if ([view.layer isKindOfClass:[CAMetalLayer class]]) {
|
||||||
|
layer = (CAMetalLayer*)view.layer;
|
||||||
|
} else {
|
||||||
|
layer = [CAMetalLayer layer];
|
||||||
|
view.wantsLayer = YES;
|
||||||
|
view.layer = layer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track the screen's backing scale so we get retina-resolution
|
||||||
|
// drawables. wgpu's surface configure picks the drawable size up
|
||||||
|
// from layer.drawableSize at present-time.
|
||||||
|
if (view.window) {
|
||||||
|
layer.contentsScale = view.window.backingScaleFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (__bridge void*)layer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // __APPLE__
|
||||||
@@ -686,11 +686,19 @@ static WGPUStringView svFromCStr(const char* s) {
|
|||||||
|
|
||||||
WgpuViewportWindow::WgpuViewportWindow(QWindow* parent)
|
WgpuViewportWindow::WgpuViewportWindow(QWindow* parent)
|
||||||
: QWindow(parent) {
|
: QWindow(parent) {
|
||||||
// wgpu doesn't need a GL context; we just need a real native window that
|
// wgpu doesn't need a GL context; we just need a real native window
|
||||||
// the platform window manager has actually created. OpenGLSurface is the
|
// whose backing layer matches the GPU API wgpu will drive.
|
||||||
// most portable way to ask Qt for a hardware-rendering-ready native
|
//
|
||||||
// window — we never bind a GL context on top of it.
|
// - 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);
|
setSurfaceType(QSurface::OpenGLSurface);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
WgpuViewportWindow::~WgpuViewportWindow() {
|
WgpuViewportWindow::~WgpuViewportWindow() {
|
||||||
@@ -1818,6 +1826,10 @@ bool WgpuViewportWindow::probeAndCreatePool() {
|
|||||||
# define WIN32_LEAN_AND_MEAN
|
# define WIN32_LEAN_AND_MEAN
|
||||||
# endif
|
# endif
|
||||||
# include <windows.h>
|
# 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
|
#endif
|
||||||
|
|
||||||
bool WgpuViewportWindow::createSurface() {
|
bool WgpuViewportWindow::createSurface() {
|
||||||
@@ -1873,8 +1885,23 @@ bool WgpuViewportWindow::createSurface() {
|
|||||||
hwndsrc.hwnd = reinterpret_cast<void*>(static_cast<uintptr_t>(winId()));
|
hwndsrc.hwnd = reinterpret_cast<void*>(static_cast<uintptr_t>(winId()));
|
||||||
surface_desc.nextInChain = &hwndsrc.chain;
|
surface_desc.nextInChain = &hwndsrc.chain;
|
||||||
surface_ = wgpuInstanceCreateSurface(instance_, &surface_desc);
|
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
|
#else
|
||||||
// macOS Metal surface wiring lands with task #32.
|
|
||||||
qWarning() << "wgpu surface creation not yet wired for this platform";
|
qWarning() << "wgpu surface creation not yet wired for this platform";
|
||||||
return false;
|
return false;
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Reference in New Issue
Block a user