zgpu

git clone git://git.electrosoup.com/zgpu
Log | Files | Refs | Submodules | README

commit b39cfc8de9a09ca8fc01fb17e3276895d169f024
parent 92ff6a4eeb188eccf3a317360b71b2f65157405d
Author: Christian Ermann <christianermann@gmail.com>
Date:   Sun,  8 Jun 2025 18:21:58 -0700

Add support for macOS

Diffstat:
Mbuild.zig | 6++++++
Msrc/glfw.zig | 13+++++++++++++
Asrc/metal.zig | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/wgpu-bindings/main.zig | 59+++++++++++++++++++++++++++++++++++++++++------------------
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(&.{