initial macOS backend (#1249)
Signed-off-by: Stephen Gutekanst <stephen@hexops.com> Co-authored-by: Michael Bradshaw <github@mjb.io>
This commit is contained in:
parent
af7ac96a0e
commit
66e56f037b
6 changed files with 308 additions and 25 deletions
17
build.zig
17
build.zig
|
|
@ -136,6 +136,12 @@ pub fn build(b: *std.Build) !void {
|
|||
module.linkLibrary(dep.artifact("wayland-headers"));
|
||||
lib.linkLibrary(dep.artifact("wayland-headers"));
|
||||
}
|
||||
if (target.result.isDarwin()) {
|
||||
if (b.lazyDependency("mach_objc", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
})) |dep| module.addImport("objc", dep.module("mach-objc"));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (want_sysaudio) {
|
||||
|
|
@ -167,11 +173,13 @@ pub fn build(b: *std.Build) !void {
|
|||
example_run_step.dependOn(&example_run_cmd.step);
|
||||
}
|
||||
}
|
||||
if (target.result.isDarwin()) {
|
||||
if (b.lazyDependency("mach_objc", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
})) |dep| module.addImport("objc", dep.module("mach-objc"));
|
||||
}
|
||||
}
|
||||
|
||||
if (target.result.isDarwin()) {
|
||||
// Transitive dependencies, explicit linkage of these works around
|
||||
|
|
@ -222,10 +230,13 @@ 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 (target.result.isDarwin()) {
|
||||
if (b.lazyDependency("mach_objc", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
})) |dep| module.addImport("objc", dep.module("mach-objc"));
|
||||
}
|
||||
|
||||
linkSysgpu(b, module);
|
||||
|
||||
if (want_libs) {
|
||||
|
|
@ -277,10 +288,12 @@ pub const Platform = enum {
|
|||
wayland,
|
||||
web,
|
||||
win32,
|
||||
darwin,
|
||||
null,
|
||||
|
||||
pub fn fromTarget(target: std.Target) Platform {
|
||||
if (target.cpu.arch == .wasm32) return .web;
|
||||
if (target.os.tag.isDarwin()) return .darwin;
|
||||
if (target.os.tag == .windows) return .win32;
|
||||
return .x11;
|
||||
}
|
||||
|
|
@ -292,7 +305,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", .{});
|
||||
if (target.os.tag == .macos) {
|
||||
module.linkFramework("AppKit", .{});
|
||||
} else {
|
||||
module.linkFramework("UIKit", .{});
|
||||
}
|
||||
module.linkFramework("CoreGraphics", .{});
|
||||
module.linkFramework("Foundation", .{});
|
||||
module.linkFramework("Metal", .{});
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@
|
|||
.lazy = true,
|
||||
},
|
||||
.mach_objc = .{
|
||||
.url = "https://pkg.machengine.org/mach-objc/055619a732ae50ba24eb04399613be87f8432f0f.tar.gz",
|
||||
.hash = "1220211475420584a552b461dd569f891d29cb8e9e485b97053be604680c8d6e2a3e",
|
||||
.url = "https://pkg.machengine.org/mach-objc/9f4635396dc8805247bab86c1decb7531b5f0309.tar.gz",
|
||||
.hash = "1220b2997b164f3a6c0be62264c79c7feae2ca005d7f5c405315def5a9af887be5ba",
|
||||
.lazy = true,
|
||||
},
|
||||
.xcode_frameworks = .{
|
||||
|
|
|
|||
|
|
@ -12,11 +12,12 @@ 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"),
|
||||
.win32 => @import("core/win32.zig"),
|
||||
.darwin => @import("core/Darwin.zig"),
|
||||
.null => @import("core/Null.zig"),
|
||||
};
|
||||
|
||||
|
|
|
|||
254
src/core/darwin.zig
Normal file
254
src/core/darwin.zig
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
const std = @import("std");
|
||||
const mach = @import("../main.zig");
|
||||
const Core = @import("../Core.zig");
|
||||
const InputState = @import("InputState.zig");
|
||||
const Frequency = @import("Frequency.zig");
|
||||
const unicode = @import("unicode.zig");
|
||||
const detectBackendType = @import("common.zig").detectBackendType;
|
||||
const gpu = mach.gpu;
|
||||
const InitOptions = Core.InitOptions;
|
||||
const Event = Core.Event;
|
||||
const KeyEvent = Core.KeyEvent;
|
||||
const MouseButtonEvent = Core.MouseButtonEvent;
|
||||
const MouseButton = Core.MouseButton;
|
||||
const Size = Core.Size;
|
||||
const DisplayMode = Core.DisplayMode;
|
||||
const CursorShape = Core.CursorShape;
|
||||
const VSyncMode = Core.VSyncMode;
|
||||
const CursorMode = Core.CursorMode;
|
||||
const Position = Core.Position;
|
||||
const Key = Core.Key;
|
||||
const KeyMods = Core.KeyMods;
|
||||
const Joystick = Core.Joystick;
|
||||
const objc = @import("objc");
|
||||
|
||||
const log = std.log.scoped(.mach);
|
||||
|
||||
const EventQueue = std.fifo.LinearFifo(Event, .Dynamic);
|
||||
pub const EventIterator = struct {
|
||||
queue: *EventQueue,
|
||||
|
||||
pub inline fn next(self: *EventIterator) ?Event {
|
||||
return self.queue.readItem();
|
||||
}
|
||||
};
|
||||
|
||||
pub const Darwin = @This();
|
||||
|
||||
allocator: std.mem.Allocator,
|
||||
core: *Core,
|
||||
|
||||
events: EventQueue,
|
||||
input_state: InputState,
|
||||
// modifiers: KeyMods,
|
||||
|
||||
title: [:0]const u8,
|
||||
display_mode: DisplayMode,
|
||||
vsync_mode: VSyncMode,
|
||||
cursor_mode: CursorMode,
|
||||
cursor_shape: CursorShape,
|
||||
border: bool,
|
||||
headless: bool,
|
||||
refresh_rate: u32,
|
||||
size: Size,
|
||||
surface_descriptor: gpu.Surface.Descriptor,
|
||||
window: ?*objc.app_kit.ns.Window,
|
||||
|
||||
pub fn run(comptime on_each_update_fn: anytype, args_tuple: std.meta.ArgsTuple(@TypeOf(on_each_update_fn))) noreturn {
|
||||
objc.avf_audio.avaudio.init();
|
||||
objc.foundation.ns.init();
|
||||
objc.metal.mtl.init();
|
||||
objc.quartz_core.ca.init();
|
||||
objc.app_kit.ns.init();
|
||||
|
||||
const Args = @TypeOf(args_tuple);
|
||||
const args_bytes = std.mem.asBytes(&args_tuple);
|
||||
const ArgsBytes = @TypeOf(args_bytes.*);
|
||||
const Helper = struct {
|
||||
// TODO: port libdispatch and use it instead of doing this directly.
|
||||
extern "System" fn dispatch_async(queue: *anyopaque, block: *objc.foundation.ns.Block(fn () void)) void;
|
||||
extern "System" var _dispatch_main_q: anyopaque;
|
||||
pub fn cCallback(block: *objc.foundation.ns.BlockLiteral(ArgsBytes)) callconv(.C) void {
|
||||
const args: *Args = @ptrCast(&block.context);
|
||||
if (@call(.auto, on_each_update_fn, args.*) catch false) {
|
||||
dispatch_async(&_dispatch_main_q, block.asBlockWithSignature(fn () void));
|
||||
} else {
|
||||
// We copied the block when we called `setRunBlock()`, so we release it here when the looping will end.
|
||||
block.release();
|
||||
}
|
||||
}
|
||||
};
|
||||
var block_literal = objc.foundation.ns.stackBlockLiteral(Helper.cCallback, args_bytes.*, null, null);
|
||||
|
||||
// `NSApplicationMain()` and `UIApplicationMain()` never return, so there's no point in trying to add any kind of cleanup work here.
|
||||
const ns_app = objc.app_kit.ns.Application.sharedApplication();
|
||||
const delegate = objc.mach.AppDelegate.allocInit();
|
||||
delegate.setRunBlock(block_literal.asBlock().copy());
|
||||
ns_app.setDelegate(@ptrCast(delegate));
|
||||
_ = objc.app_kit.ns.applicationMain(0, undefined);
|
||||
|
||||
unreachable;
|
||||
// TODO: support UIKit.
|
||||
}
|
||||
|
||||
// Called on the main thread
|
||||
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: *Darwin) void {
|
||||
if (darwin.window) |w| @as(*objc.foundation.ns.ObjectProtocol, @ptrCast(w)).release();
|
||||
return;
|
||||
}
|
||||
|
||||
// Called on the main thread
|
||||
pub fn update(_: *Darwin) !void {
|
||||
return;
|
||||
}
|
||||
|
||||
// May be called from any thread.
|
||||
pub inline fn pollEvents(n: *Darwin) EventIterator {
|
||||
return EventIterator{ .queue = &n.events };
|
||||
}
|
||||
|
||||
// May be called from any thread.
|
||||
pub fn setTitle(_: *Darwin, _: [:0]const u8) void {
|
||||
return;
|
||||
}
|
||||
|
||||
// May be called from any thread.
|
||||
pub fn setDisplayMode(_: *Darwin, _: DisplayMode) void {
|
||||
return;
|
||||
}
|
||||
|
||||
// May be called from any thread.
|
||||
pub fn setBorder(_: *Darwin, _: bool) void {
|
||||
return;
|
||||
}
|
||||
|
||||
// May be called from any thread.
|
||||
pub fn setHeadless(_: *Darwin, _: bool) void {
|
||||
return;
|
||||
}
|
||||
|
||||
// May be called from any thread.
|
||||
pub fn setVSync(_: *Darwin, _: VSyncMode) void {
|
||||
return;
|
||||
}
|
||||
|
||||
// May be called from any thread.
|
||||
pub fn setSize(_: *Darwin, _: Size) void {
|
||||
return;
|
||||
}
|
||||
|
||||
// May be called from any thread.
|
||||
pub fn size(_: *Darwin) Size {
|
||||
return Size{ .width = 100, .height = 100 };
|
||||
}
|
||||
|
||||
// May be called from any thread.
|
||||
pub fn setCursorMode(_: *Darwin, _: CursorMode) void {
|
||||
return;
|
||||
}
|
||||
|
||||
// May be called from any thread.
|
||||
pub fn setCursorShape(_: *Darwin, _: CursorShape) void {
|
||||
return;
|
||||
}
|
||||
|
||||
// May be called from any thread.
|
||||
pub fn joystickPresent(_: *Darwin, _: Joystick) bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
// May be called from any thread.
|
||||
pub fn joystickName(_: *Darwin, _: Joystick) ?[:0]const u8 {
|
||||
return null;
|
||||
}
|
||||
|
||||
// May be called from any thread.
|
||||
pub fn joystickButtons(_: *Darwin, _: Joystick) ?[]const bool {
|
||||
return null;
|
||||
}
|
||||
|
||||
// May be called from any thread.
|
||||
pub fn joystickAxes(_: *Darwin, _: Joystick) ?[]const f32 {
|
||||
return null;
|
||||
}
|
||||
|
||||
// May be called from any thread.
|
||||
pub fn keyPressed(_: *Darwin, _: Key) bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
// May be called from any thread.
|
||||
pub fn keyReleased(_: *Darwin, _: Key) bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
// May be called from any thread.
|
||||
pub fn mousePressed(_: *Darwin, _: MouseButton) bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
// May be called from any thread.
|
||||
pub fn mouseReleased(_: *Darwin, _: MouseButton) bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
// May be called from any thread.
|
||||
pub fn mousePosition(_: *Darwin) Position {
|
||||
return Position{ .x = 0, .y = 0 };
|
||||
}
|
||||
24
src/main.zig
24
src/main.zig
|
|
@ -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, .{});
|
||||
try app.mods.dispatch(stack_space, .{});
|
||||
app.mods.schedule(.mach_core, .deinit);
|
||||
try app.mods.dispatch(&stack_space, .{});
|
||||
try app.mods.dispatch(stack_space, .{});
|
||||
return false;
|
||||
}
|
||||
|
||||
// Dispatch events until queue is empty
|
||||
try app.mods.dispatch(stack_space, .{});
|
||||
// Run `update` when `init` and all other systems are executed
|
||||
app.mods.schedule(app.main_mod, .update);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -2214,13 +2214,14 @@ pub const Queue = struct {
|
|||
.queue = queue,
|
||||
.fence_value = queue.fence_value,
|
||||
};
|
||||
mtl_command_buffer.addCompletedHandler(ctx, completedHandler);
|
||||
var handler = ns.stackBlockLiteral(completedHandler, ctx, null, null);
|
||||
mtl_command_buffer.addCompletedHandler(handler.asBlock());
|
||||
mtl_command_buffer.commit();
|
||||
}
|
||||
|
||||
fn completedHandler(ctx: CompletedContext, mtl_command_buffer: *mtl.CommandBuffer) void {
|
||||
fn completedHandler(block: *ns.BlockLiteral(CompletedContext), mtl_command_buffer: *mtl.CommandBuffer) callconv(.C) void {
|
||||
_ = mtl_command_buffer;
|
||||
ctx.queue.completed_value.store(ctx.fence_value, .release);
|
||||
block.context.queue.completed_value.store(block.context.fence_value, .release);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue