Skip to content

Commit

Permalink
Initialize macOS app delegate and set up a Metal view (#1247)
Browse files Browse the repository at this point in the history
  • Loading branch information
mjbshaw authored Aug 17, 2024
1 parent 3d2f731 commit 0a12758
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 27 deletions.
26 changes: 17 additions & 9 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,17 @@ pub fn build(b: *std.Build) !void {
});
module.addImport("build-options", build_options.createModule());

if (target.result.isDarwin()) {
if (b.lazyDependency("mach_objc", .{
.target = target,
.optimize = optimize,
})) |dep| {
if (want_core or want_sysaudio or want_sysgpu) {
module.addImport("objc", dep.module("mach-objc"));
}
}
}

if (want_mach) {
// Linux gamemode requires libc.
if (target.result.os.tag == .linux) module.link_libc = true;
Expand Down Expand Up @@ -167,10 +178,6 @@ pub fn build(b: *std.Build) !void {
example_run_step.dependOn(&example_run_cmd.step);
}
}
if (b.lazyDependency("mach_objc", .{
.target = target,
.optimize = optimize,
})) |dep| module.addImport("objc", dep.module("mach-objc"));
}

if (target.result.isDarwin()) {
Expand Down Expand Up @@ -222,10 +229,7 @@ pub fn build(b: *std.Build) !void {
}
if (want_sysgpu) {
if (b.lazyDependency("vulkan_zig_generated", .{})) |dep| module.addImport("vulkan", dep.module("vulkan-zig-generated"));
if (b.lazyDependency("mach_objc", .{
.target = target,
.optimize = optimize,
})) |dep| module.addImport("objc", dep.module("mach-objc"));

linkSysgpu(b, module);

if (want_libs) {
Expand Down Expand Up @@ -294,7 +298,11 @@ fn linkSysgpu(b: *std.Build, module: *std.Build.Module) void {
if (target.cpu.arch != .wasm32) module.link_libc = true;
if (target.isDarwin()) {
module.linkSystemLibrary("objc", .{});
module.linkFramework("AppKit", .{});
if (target.os.tag == .macos) {
module.linkFramework("AppKit", .{});
} else {
module.linkFramework("UIKit", .{});
}
module.linkFramework("CoreGraphics", .{});
module.linkFramework("Foundation", .{});
module.linkFramework("Metal", .{});
Expand Down
2 changes: 1 addition & 1 deletion src/Core.zig
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub const sysjs = @import("mach-sysjs");
pub const Timer = @import("core/Timer.zig");
const Frequency = @import("core/Frequency.zig");

const Platform = switch (build_options.core_platform) {
pub const Platform = switch (build_options.core_platform) {
.x11 => @import("core/X11.zig"),
.wayland => @import("core/Wayland.zig"),
.web => @panic("TODO: revive wasm backend"),
Expand Down
106 changes: 99 additions & 7 deletions src/core/darwin.zig
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ core: *Core,

events: EventQueue,
input_state: InputState,
modifiers: KeyMods,
// modifiers: KeyMods,

title: [:0]u8,
title: [:0]const u8,
display_mode: DisplayMode,
vsync_mode: VSyncMode,
cursor_mode: CursorMode,
Expand All @@ -52,15 +52,107 @@ headless: bool,
refresh_rate: u32,
size: Size,
surface_descriptor: gpu.Surface.Descriptor,
window: ?*objc.appkit.ns.Window,

pub fn run(comptime on_each_update: anytype, args_tuple: std.meta.ArgsTuple(@TypeOf(on_each_update))) noreturn {
objc.avf_audio.avaudio.init();
objc.foundation.ns.init();
objc.metal.mtl.init();
objc.quartz_core.ca.init();
objc.appkit.ns.init();

const Args = @TypeOf(args_tuple);
const args_bytes = std.mem.toBytes(args_tuple);
const Literal = objc.foundation.ns.BlockLiteral(@TypeOf(args_bytes));
const Helper = struct {
extern const _NSConcreteStackBlock: *anyopaque;
extern "System" fn dispatch_async(queue: *anyopaque, block: *Literal) void;
extern "System" var _dispatch_main_q: anyopaque;
extern fn _Block_copy(*const Literal) *Literal;
extern fn _Block_release(*const Literal) void;

pub fn cCallback(literal: *Literal) callconv(.C) void {
const args: *Args = @ptrCast(&literal.context);
if (@call(.auto, on_each_update, args.*) catch false) {
dispatch_async(&_dispatch_main_q, literal);
} else {
_Block_release(literal);
}
}
};
const descriptor = objc.foundation.ns.BlockDescriptor{ .reserved = 0, .size = @sizeOf(Literal) };
const block = Literal{ .isa = Helper._NSConcreteStackBlock, .flags = 0, .reserved = 0, .invoke = @ptrCast(&Helper.cCallback), .descriptor = &descriptor, .context = args_bytes };

// `NSApplicationMain()` and `UIApplicationMain()` never return, so there's no point in trying to add any kind of cleanup work here.
const ns_app = objc.appkit.ns.Application.sharedApplication();
const delegate = objc.mach.AppDelegate.allocInit();
delegate.setRunBlock(Helper._Block_copy(&block));
ns_app.setDelegate(@ptrCast(delegate));
_ = objc.appkit.ns.applicationMain(0, undefined);

unreachable;
// TODO: support UIKit.
}

// Called on the main thread
pub fn init(_: *Darwin, _: InitOptions) !void {
const app = objc.appkit.ns.Application.sharedApplication();
_ = app; // autofix
return;
pub fn init(darwin: *Darwin, options: InitOptions) !void {
var surface_descriptor = gpu.Surface.Descriptor{};

// TODO: support UIKit.
var window: ?*objc.appkit.ns.Window = null;
if (!options.headless) {
const metal_descriptor = try options.allocator.create(gpu.Surface.DescriptorFromMetalLayer);
const layer = objc.quartz_core.ca.MetalLayer.new();
defer layer.release();
metal_descriptor.* = .{
.layer = layer,
};
surface_descriptor.next_in_chain = .{ .from_metal_layer = metal_descriptor };

const screen = objc.appkit.ns.Screen.mainScreen();
const rect = objc.core_graphics.cg.Rect{ // TODO: use a meaningful rect
.origin = .{ .x = 100, .y = 100 },
.size = .{ .width = 480, .height = 270 },
};
const window_style =
(if (options.display_mode == .fullscreen) objc.appkit.ns.WindowStyleMaskFullScreen else 0) |
(if (options.display_mode == .windowed) objc.appkit.ns.WindowStyleMaskTitled else 0) |
(if (options.display_mode == .windowed) objc.appkit.ns.WindowStyleMaskClosable else 0) |
(if (options.display_mode == .windowed) objc.appkit.ns.WindowStyleMaskMiniaturizable else 0) |
(if (options.display_mode == .windowed) objc.appkit.ns.WindowStyleMaskResizable else 0);
window = objc.appkit.ns.Window.alloc().initWithContentRect_styleMask_backing_defer_screen(rect, window_style, objc.appkit.ns.BackingStoreBuffered, true, screen);
window.?.setReleasedWhenClosed(false);
if (window.?.contentView()) |view| {
view.setLayer(@ptrCast(layer));
}
window.?.setIsVisible(true);
window.?.makeKeyAndOrderFront(null);
}

var events = EventQueue.init(options.allocator);
try events.ensureTotalCapacity(2048);

darwin.* = .{
.allocator = options.allocator,
.core = @fieldParentPtr("platform", darwin),
.events = events,
.input_state = .{},
.title = options.title,
.display_mode = options.display_mode,
.vsync_mode = .none,
.cursor_mode = .normal,
.cursor_shape = .arrow,
.border = options.border,
.headless = options.headless,
.refresh_rate = 60, // TODO: set to something meaningful
.size = options.size,
.surface_descriptor = surface_descriptor,
.window = window,
};
}

pub fn deinit(_: *Darwin) void {
pub fn deinit(darwin: *Darwin) void {
if (darwin.window) |w| @as(*objc.foundation.ns.ObjectProtocol, @ptrCast(w)).release();
return;
}

Expand Down
30 changes: 20 additions & 10 deletions src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -72,18 +72,28 @@ pub const App = struct {
app.mods.schedule(app.main_mod, .init);

// Main loop
while (!app.mods.mod.mach_core.state().should_close) {
// Dispatch events until queue is empty
try app.mods.dispatch(&stack_space, .{});
// Run `update` when `init` and all other systems are exectued
app.mods.schedule(app.main_mod, .update);
if (comptime builtin.target.isDarwin()) {
Core.Platform.run(on_each_update, .{app, &stack_space});
} else {
while (try app.on_each_update(&stack_space)) {}
}
}

fn on_each_update(app: *App, stack_space: []u8) !bool {
if (app.mods.mod.mach_core.state().should_close) {
// Final Dispatch to deinitalize resources
app.mods.schedule(app.main_mod, .deinit);
try app.mods.dispatch(stack_space, .{});
app.mods.schedule(.mach_core, .deinit);
try app.mods.dispatch(stack_space, .{});
return false;
}

// Final Dispatch to deinitalize resources
app.mods.schedule(app.main_mod, .deinit);
try app.mods.dispatch(&stack_space, .{});
app.mods.schedule(.mach_core, .deinit);
try app.mods.dispatch(&stack_space, .{});
// Dispatch events until queue is empty
try app.mods.dispatch(stack_space, .{});
// Run `update` when `init` and all other systems are exectued
app.mods.schedule(app.main_mod, .update);
return true;
}
};

Expand Down

0 comments on commit 0a12758

Please sign in to comment.