fix(wgpu): build + render the demo on macOS (Metal surface) - #290
Conversation
The example/build.zig was inherited from the old Dawn-based wiring and still linked a nonexistent 'zdawn' artifact plus a 'dawn' system lib and dawn_*_macos deps, so 'zig build' panicked with "unable to find artifact 'zdawn'" on macOS. The backend now rides wgpu-native (wgpu_native_zig), whose 'wgpu' module already embeds libwgpu_native.a via addObjectFile, so the runtime travels transitively through gfx/window. Drop the Dawn refs; the example only needs the glfw artifact plus the Metal/QuartzCore/ Foundation frameworks wgpu-native links at the Compile step. window.zig only created a surface on Windows (Win32 HWND). Wire the Apple path: pull the Cocoa NSWindow from GLFW, attach a CAMetalLayer to its content view via three objc_msgSend calls, and feed it to surfaceDescriptorFromMetalLayer. initGpu is refactored to a per-OS createSurface() so the adapter/device/pipeline spine is shared. Verified on macOS arm64: demo builds, opens a window, and runs the acquire->render->present loop with no warnings (GPU spine initializes; shape batch drains each frame).
There was a problem hiding this comment.
Code Review
This pull request adds macOS and iOS support to the WGPU backend. It updates the build configuration to link macOS system frameworks (Foundation, QuartzCore, and Metal) instead of Dawn, and implements macOS surface creation in window.zig by dynamically attaching a CAMetalLayer to the GLFW Cocoa window using the Objective-C runtime. A critical compilation issue was identified in window.zig where the lowercase .c calling convention is used instead of the uppercase .C.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| const msgId = @as(*const fn (ObjcId, ?*anyopaque) callconv(.c) ObjcId, @ptrCast(&objc_msgSend)); | ||
| const msgSetBool = @as(*const fn (ObjcId, ?*anyopaque, i8) callconv(.c) void, @ptrCast(&objc_msgSend)); | ||
| const msgSetId = @as(*const fn (ObjcId, ?*anyopaque, ObjcId) callconv(.c) void, @ptrCast(&objc_msgSend)); |
There was a problem hiding this comment.
In Zig, the calling convention for C is represented by the uppercase .C (i.e., callconv(.C)), not lowercase .c. Using .c will result in a compilation error because std.builtin.CallingConvention does not contain a lowercase c member.
const msgId = @as(*const fn (ObjcId, ?*anyopaque) callconv(.C) ObjcId, @ptrCast(&objc_msgSend));
const msgSetBool = @as(*const fn (ObjcId, ?*anyopaque, i8) callconv(.C) void, @ptrCast(&objc_msgSend));
const msgSetId = @as(*const fn (ObjcId, ?*anyopaque, ObjcId) callconv(.C) void, @ptrCast(&objc_msgSend));
The `link_wgpu` build section linked only the glfw artifact, so any game generated with `.backend = .wgpu` failed to link on macOS/iOS with undefined Metal/Foundation symbols pulled in by libwgpu_native.a (embedded in the wgpu module via addObjectFile). Link Foundation/QuartzCore/Metal on Apple targets, mirroring the example/build.zig fix. Verified end to end: generated the Ricochet game with `.backend = .wgpu` on macOS arm64 — `zig build` links, and the binary opens a window and runs the engine loop (gizmo rendering through the wgpu shape path) with no rendering-disabled warnings.
PR SummaryMedium Risk Overview The standalone wgpu demo Generated games using CI re-enables Reviewed by Cursor Bugbot for commit 38ae3b7. Bugbot is set up for automated code reviews on this repo. Configure here. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit c45f039. Configure here.
Cursor flagged that the initGpu refactor creates the wgpu instance before createSurface, so on platforms without a wired surface (Linux) the instance was created then leaked when createSurface returns null. Release it on that path. CI: the macOS build break shipped because nothing built wgpu on macOS. - Re-enable the wgpu backend WAV-parser unit test (the 0.16 gate is stale; it builds only the pure parser, no native deps). - Build the standalone backends/wgpu/example on the macos-latest runner — the real regression lock for the Apple render path (links the Metal/ QuartzCore/Foundation frameworks and compiles the CAMetalLayer surface). - Assert the generated wgpu build.zig links those frameworks in the Linux examples-integration step, locking the link_wgpu template against a silent framework-drop even without a macOS runner there. (Declined Gemini's callconv(.c) -> .C suggestion: Zig 0.16 uses lowercase .c — the file already uses callconv(.winapi), zglfw uses callconv(.c), and this builds + links on macOS.)
|
Bot triage:
Added CI so this class of break can't ship silently again: the standalone |
Backend work since v0.39.1: - wgpu: macOS Metal surface + textured sprite rendering (#290, #291) - bgfx: macOS bring-up to on-device Android (#296 epic, #304/#305/#307/#308/#309) - #310 AndroidBackendContext adapters: sokol (#312) + bgfx (#313) register the core seam; bgfx-Android gamepad via the shared android_gamepad sub-package - bgfx desktop gamepad: GLFW (#315) + SDL HIDAPI / Switch-pad support (#318) - cached bgfx CI job (#295) Android codegen now calls core.registerAndroidBackend → requires labelle-core >= v1.17.0 + labelle-engine >= v1.50.0.

What
Make the WebGPU backend build and render on macOS — both the standalone demo and engine-generated games.
The break
backends/wgpu/example/build.zigwas inherited from the old Dawn-based wiring (absorbed from labelle-cli) and still linked a nonexistentzdawnartifact plus adawnsystem library anddawn_aarch64_macos/dawn_x86_64_macosdeps. Since the backend migrated to wgpu-native (wgpu_native_zig, #228/#287),zig buildin the example panicked:The same missing-frameworks problem also lived in the assembler's
link_wgpubuild template, so any game generated with.backend = .wgpuwould fail to link on macOS too.The fix
wgpu_native_zig'swgpumodule already embedslibwgpu_native.aviaaddObjectFile, and that module is imported by the backend'sgfx/windowmodules, so the native runtime travels into the exe transitively. The example only needs theglfwartifact plus, on Apple targets, theMetal/QuartzCore/Foundationframeworks wgpu-native links at the Compile step (upstream links them per-Compile, not on the module).NSWindowfrom GLFW (getCocoaWindow), attach a freshCAMetalLayerto its content view via threeobjc_msgSendcalls, and feed it tosurfaceDescriptorFromMetalLayer.initGpuis refactored to a per-OScreateSurface()so the adapter → device → pipeline spine stays shared.link_wgpu) — linkFoundation/QuartzCore/Metalon Apple targets in generated engine builds, mirroring the example fix.Verification (macOS arm64, Zig 0.16.0)
backends/wgpu→zig build test: 14/14 pass.backends/wgpu/example→zig build: produceswgpu-demo(links the wgpu-native static lib); running it opens a window and runs the acquire → render → present loop with no warnings — previously it loggedsurface creation only wired for Windows; rendering disabledand spammedshape batch full(batch never drained). The clean run meansgpu_readyis set (full GPU spine initialized) and the shape batch drains each frame..backend = .wgpu.zig buildlinks cleanly; the binary opens a window and runs the full engine loop (waves, physics, gizmo rendering through the wgpu shape path) for the whole run with zero wgpu/render warnings.Assembler
zig build+zig build teststay green. Windows path is unchanged.