commit b39cfc8de9a09ca8fc01fb17e3276895d169f024
parent 92ff6a4eeb188eccf3a317360b71b2f65157405d
Author: Christian Ermann <christianermann@gmail.com>
Date: Sun, 8 Jun 2025 18:21:58 -0700
Add support for macOS
Diffstat:
4 files changed, 131 insertions(+), 18 deletions(-)
diff --git a/build.zig b/build.zig
@@ -20,6 +20,12 @@ pub fn build(b: *std.Build) void {
});
glfw.addIncludePath(b.path("include"));
glfw.addObjectFile(b.path("lib/libglfw3.a"));
+ if (target.result.os.tag == .macos) {
+ glfw.linkFramework("Cocoa", .{});
+ glfw.linkFramework("IOKit", .{});
+ glfw.linkFramework("Quartz", .{});
+ glfw.linkFramework("Metal", .{});
+ }
const wgpu = b.addModule("wgpu-bindings", .{
.root_source_file = b.path("src/wgpu-bindings/root.zig"),
diff --git a/src/glfw.zig b/src/glfw.zig
@@ -4,7 +4,10 @@ const c = @cImport({
@cInclude("glfw3.h");
if (builtin.target.os.tag == .linux) {
@cDefine("GLFW_EXPOSE_NATIVE_X11", "1");
+ } else if (builtin.target.os.tag == .macos) {
+ @cDefine("GLFW_EXPOSE_NATIVE_COCOA", "1");
}
+
@cInclude("glfw3native.h");
});
@@ -121,5 +124,15 @@ pub const native = switch (builtin.target.os.tag) {
return @intCast(c.glfwGetX11Window(@ptrCast(window)));
}
},
+ .macos => struct {
+ const NSWindow = opaque {};
+
+ pub fn getCocoaWindow(window: *Window) !*NSWindow {
+ return @ptrCast(c.glfwGetCocoaWindow(@ptrCast(window)));
+ }
+
+ pub const getMetalLayer = @import("metal.zig").getMetalLayer;
+
+ },
else => error.UnsupportedOS,
};
diff --git a/src/metal.zig b/src/metal.zig
@@ -0,0 +1,71 @@
+const glfw = @import("glfw.zig");
+
+pub fn getMetalLayer(window: *glfw.Window) *anyopaque {
+ const ns_window = try glfw.native.getCocoaWindow(window);
+ const ns_view = msgSend(ns_window, "contentView", .{}, *anyopaque);
+ // Create a CAMetalLayer that covers the whole window.
+ msgSend(ns_view, "setWantsLayer:", .{true}, void);
+ const layer = msgSend(
+ objc_getClass("CAMetalLayer"),
+ "layer",
+ .{},
+ ?*anyopaque,
+ ) orelse {
+ @panic("failed to create Metal layer");
+ };
+ msgSend(ns_view, "setLayer:", .{layer}, void);
+ // Use retina if the window was created with retina support.
+ //const scale_factor = msgSend(ns_window, "backingScaleFactor", .{}, f64);
+ //msgSend(layer, "setContentsScale:", .{scale_factor}, void);
+ return layer;
+}
+
+// objective c interop
+// Borrowed from https://github.com/hexops/mach-gpu
+// Borrowed from https://github.com/hazeycode/zig-objcrt
+const SEL = opaque {};
+const Class = opaque {};
+extern fn sel_getUid(str: [*c]const u8) ?*SEL;
+extern fn objc_getClass(name: [*c]const u8) ?*Class;
+extern fn objc_msgSend() void;
+fn msgSend(
+ obj: anytype,
+ sel_name: [:0]const u8,
+ args: anytype,
+ comptime ReturnType: type
+) ReturnType {
+ const args_meta = @typeInfo(@TypeOf(args)).@"struct".fields;
+ const FnType = switch (args_meta.len) {
+ 0 => *const fn (@TypeOf(obj), ?*SEL) callconv(.C) ReturnType,
+ 1 => *const fn (
+ @TypeOf(obj),
+ ?*SEL,
+ args_meta[0].type
+ ) callconv(.C) ReturnType,
+ 2 => *const fn (
+ @TypeOf(obj),
+ ?*SEL,
+ args_meta[0].type,
+ args_meta[1].type,
+ ) callconv(.C) ReturnType,
+ 3 => *const fn (
+ @TypeOf(obj),
+ ?*SEL,
+ args_meta[0].type,
+ args_meta[1].type,
+ args_meta[2].type,
+ ) callconv(.C) ReturnType,
+ 4 => *const fn (
+ @TypeOf(obj),
+ ?*SEL,
+ args_meta[0].type,
+ args_meta[1].type,
+ args_meta[2].type,
+ args_meta[3].type,
+ ) callconv(.C) ReturnType,
+ else => @compileError("Unsupported number of args"),
+ };
+ const func = @as(FnType, @ptrCast(&objc_msgSend));
+ const sel = sel_getUid(@as([*c]const u8, @ptrCast(sel_name)));
+ return @call(.auto, func, .{ obj, sel } ++ args);
+}
diff --git a/src/wgpu-bindings/main.zig b/src/wgpu-bindings/main.zig
@@ -1,3 +1,4 @@
+const builtin = @import("builtin");
const std = @import("std");
const glfw = @import("glfw");
@@ -49,8 +50,6 @@ pub fn main() !void {
glfw.windowHint(.client_api, glfw.ClientApi.no_api);
const window = try glfw.Window.create(640, 480, "Example", null, null);
defer window.destroy();
- const display = try glfw.native.getX11Display();
- const window_id = try glfw.native.getX11Window(window);
wgpu.setLogCallback(Callback1(zgpu_log_callback));
wgpu.setLogLevel(.warn);
@@ -58,7 +57,7 @@ pub fn main() !void {
const desc = Instance.Descriptor{
.next = .{
.extras = &.{
- .backends = .vulkan,
+ .backends = .primary,
.flags = .validation,
.dx12_compiler = .undefined,
.gles3_minor_version = .automatic,
@@ -78,27 +77,51 @@ pub fn main() !void {
};
defer instance.release();
- const surface = instance.createSurface(&.{
- .next = .{
- .from_xlib_window = &.{
- .window = window_id,
- .display = display,
+ const surface = switch (builtin.target.os.tag) {
+ .linux => instance.createSurface(&.{
+ .next = .{
+ .from_xlib_window = &.{
+ .window = try glfw.native.getX11Window(window)(),
+ .display = try glfw.native.getX11Display(),
+ },
},
- },
- .label = StringView.fromSlice("Example Surface"),
- }) orelse {
+ .label = StringView.fromSlice("Example Surface"),
+ }),
+ .macos => instance.createSurface(&.{
+ .next = .{
+ .from_metal_layer = &.{
+ .layer = glfw.native.getMetalLayer(window),
+ },
+ },
+ .label = StringView.fromSlice("Example Surface"),
+ }),
+ else => @compileError("Unsupported OS"),
+ } orelse {
std.log.err("failed to create GPU surface", .{});
std.process.exit(1);
};
defer surface.release();
- const adapter = try instance.requestAdapter(&.{
- .feature_level = .core,
- .power_preference = .high_performance,
- .backend_type = .vulkan,
- .force_fallback_adapter = false,
- .compatible_surface = surface,
- });
+
+ const adapter = switch (builtin.target.os.tag) {
+ .linux => try instance.requestAdapter(&.{
+ .feature_level = .core,
+ .power_preference = .high_performance,
+ .backend_type = .vulkan,
+ .force_fallback_adapter = false,
+ .compatible_surface = surface,
+ }),
+ .macos => try instance.requestAdapter(&.{
+ .feature_level = .core,
+ .power_preference = .high_performance,
+ .backend_type = .metal,
+ .force_fallback_adapter = false,
+ // the adapter seems to be compatible, but when I request that it
+ // is it can't find one
+ .compatible_surface = null,
+ }),
+ else => @compileError("Unsupported OS"),
+ };
defer adapter.release();
const device = try adapter.requestDevice(&.{