core: darwin: More input callbacks, correct framebuffer/window sizes, core has responsibility of swapchain

This commit is contained in:
foxnne 2024-12-11 23:02:05 -06:00 committed by Emi Gutekanst
parent a10cbc3419
commit 98c303aefc
2 changed files with 178 additions and 209 deletions

View file

@ -209,8 +209,8 @@ pub fn initWindow(core: *Core, window_id: mach.ObjectID) !void {
.label = "main swap chain", .label = "main swap chain",
.usage = core_window.swap_chain_usage, .usage = core_window.swap_chain_usage,
.format = .bgra8_unorm, .format = .bgra8_unorm,
.width = core_window.width, .width = core_window.framebuffer_width,
.height = core_window.height, .height = core_window.framebuffer_height,
.present_mode = switch (core_window.vsync_mode) { .present_mode = switch (core_window.vsync_mode) {
.none => .immediate, .none => .immediate,
.double => .fifo, .double => .fifo,
@ -218,10 +218,6 @@ pub fn initWindow(core: *Core, window_id: mach.ObjectID) !void {
}, },
}; };
core_window.swap_chain = core_window.device.createSwapChain(core_window.surface, &core_window.swap_chain_descriptor); core_window.swap_chain = core_window.device.createSwapChain(core_window.surface, &core_window.swap_chain_descriptor);
core_window.framebuffer_format = core_window.swap_chain_descriptor.format;
core_window.framebuffer_width = core_window.swap_chain_descriptor.width;
core_window.framebuffer_height = core_window.swap_chain_descriptor.height;
core.pushEvent(.{ .window_open = .{ .window_id = window_id } }); core.pushEvent(.{ .window_open = .{ .window_id = window_id } });
} }
@ -236,6 +232,32 @@ pub fn tick(core: *Core, core_mod: mach.Mod(Core)) !void {
core_mod.call(.presentFrame); core_mod.call(.presentFrame);
} }
pub fn presentFrame(core: *Core, core_mod: mach.Mod(Core)) !void {
var windows = core.windows.slice();
while (windows.next()) |window_id| {
var core_window = core.windows.getValue(window_id);
defer core.windows.setValueRaw(window_id, core_window);
mach.sysgpu.Impl.deviceTick(core_window.device);
core_window.swap_chain.present();
}
// Record to frame rate frequency monitor that a frame was finished.
core.frame.tick();
switch (core.state) {
.running => {},
.exiting => {
core.state = .deinitializing;
core_mod.run(core.on_exit.?);
core_mod.call(.deinit);
},
.deinitializing => {},
.exited => @panic("application not running"),
}
}
pub fn main(core: *Core, core_mod: mach.Mod(Core)) !void { pub fn main(core: *Core, core_mod: mach.Mod(Core)) !void {
if (core.on_tick == null) @panic("core.on_tick callback must be set"); if (core.on_tick == null) @panic("core.on_tick callback must be set");
if (core.on_exit == null) @panic("core.on_exit callback must be set"); if (core.on_exit == null) @panic("core.on_exit callback must be set");
@ -286,11 +308,14 @@ fn platform_update_callback(core: *Core, core_mod: mach.Mod(Core)) !bool {
core_mod.run(core.on_tick.?); core_mod.run(core.on_tick.?);
core_mod.call(.presentFrame); core_mod.call(.presentFrame);
//core_mod.call(.processWindowUpdates);
return core.state != .exited; return core.state != .exited;
} }
pub fn exit(core: *Core) void {
core.state = .exiting;
}
pub fn deinit(core: *Core) !void { pub fn deinit(core: *Core) !void {
core.state = .exited; core.state = .exited;
@ -369,206 +394,6 @@ pub fn mousePosition(core: *@This()) Position {
return core.input_state.mouse_position; return core.input_state.mouse_position;
} }
// TODO(object)
// /// Set refresh rate synchronization mode. Default `.triple`
// ///
// /// Calling this function also implicitly calls setFrameRateLimit for you:
// /// ```
// /// .none => setFrameRateLimit(0) // unlimited
// /// .double => setFrameRateLimit(0) // unlimited
// /// .triple => setFrameRateLimit(2 * max_monitor_refresh_rate)
// /// ```
// pub inline fn setVSync(core: *@This(), mode: VSyncMode) void {
// return core.platform.setVSync(mode);
// }
// TODO(object)
// /// Returns refresh rate synchronization mode.
// pub inline fn vsync(core: *@This()) VSyncMode {
// return core.platform.vsync_mode;
// }
// TODO(object)
// /// Sets the frame rate limit. Default 0 (unlimited)
// ///
// /// This is applied *in addition* to the vsync mode.
// pub inline fn setFrameRateLimit(core: *@This(), limit: u32) void {
// core.frame.target = limit;
// }
// TODO(object)
// /// Returns the frame rate limit, or zero if unlimited.
// pub inline fn frameRateLimit(core: *@This()) u32 {
// return core.frame.target;
// }
// TODO(object)
// /// Set the window size, in subpixel units.
// pub inline fn setSize(core: *@This(), value: Size) void {
// return core.platform.setSize(value);
// }
// TODO(object)
// /// Returns the window size, in subpixel units.
// pub inline fn size(core: *@This()) Size {
// return core.platform.size;
// }
// TODO(object)
// pub inline fn setCursorMode(core: *@This(), mode: CursorMode) void {
// return core.platform.setCursorMode(mode);
// }
// TODO(object)
// pub inline fn cursorMode(core: *@This()) CursorMode {
// return core.platform.cursorMode();
// }
// TODO(object)
// pub inline fn setCursorShape(core: *@This(), cursor: CursorShape) void {
// return core.platform.setCursorShape(cursor);
// }
// TODO(object)
// pub inline fn cursorShape(core: *@This()) CursorShape {
// return core.platform.cursorShape();
// }
// TODO(object)
// /// Sets the minimum target frequency of the input handling thread.
// ///
// /// Input handling (the main thread) runs at a variable frequency. The thread blocks until there are
// /// input events available, or until it needs to unblock in order to achieve the minimum target
// /// frequency which is your collaboration point of opportunity with the main thread.
// ///
// /// For example, by default (`setInputFrequency(1)`) mach-core will aim to invoke `updateMainThread`
// /// at least once per second (but potentially much more, e.g. once per every mouse movement or
// /// keyboard button press.) If you were to increase the input frequency to say 60hz e.g.
// /// `setInputFrequency(60)` then mach-core will aim to invoke your `updateMainThread` 60 times per
// /// second.
// ///
// /// An input frequency of zero implies unlimited, in which case the main thread will busy-wait.
// ///
// /// # Multithreaded mach-core behavior
// ///
// /// On some platforms, mach-core is able to handle input and rendering independently for
// /// improved performance and responsiveness.
// ///
// /// | Platform | Threading |
// /// |----------|-----------------|
// /// | Desktop | Multi threaded |
// /// | Browser | Single threaded |
// /// | Mobile | TBD |
// ///
// /// On single-threaded platforms, `update` and the (optional) `updateMainThread` callback are
// /// invoked in sequence, one after the other, on the same thread.
// ///
// /// On multi-threaded platforms, `init` and `deinit` are called on the main thread, while `update`
// /// is called on a separate rendering thread. The (optional) `updateMainThread` callback can be
// /// used in cases where you must run a function on the main OS thread (such as to open a native
// /// file dialog on macOS, since many system GUI APIs must be run on the main OS thread.) It is
// /// advised you do not use this callback to run any code except when absolutely neccessary, as
// /// it is in direct contention with input handling.
// ///
// /// APIs which are not accessible from a specific thread are declared as such, otherwise can be
// /// called from any thread as they are internally synchronized.
// pub inline fn setInputFrequency(core: *@This(), input_frequency: u32) void {
// core.input.target = input_frequency;
// }
// TODO(object)
// /// Returns the input frequency, or zero if unlimited (busy-waiting mode)
// pub inline fn inputFrequency(core: *@This()) u32 {
// return core.input.target;
// }
// TODO(object)
// /// Returns the actual number of frames rendered (`update` calls that returned) in the last second.
// ///
// /// This is updated once per second.
// pub inline fn frameRate(core: *@This()) u32 {
// return core.frame.rate;
// }
// TODO(object)
// /// Returns the actual number of input thread iterations in the last second. See setInputFrequency
// /// for what this means.
// ///
// /// This is updated once per second.
// pub inline fn inputRate(core: *@This()) u32 {
// return core.input.rate;
// }
// TODO(object)
// /// Returns the underlying native NSWindow pointer
// ///
// /// May only be called on macOS.
// pub fn nativeWindowCocoa(core: *@This()) *anyopaque {
// return core.platform.nativeWindowCocoa();
// }
// TODO(object)
// /// Returns the underlying native Windows' HWND pointer
// ///
// /// May only be called on Windows.
// pub fn nativeWindowWin32(core: *@This()) std.os.windows.HWND {
// return core.platform.nativeWindowWin32();
// }
pub fn presentFrame(core: *Core, core_mod: mach.Mod(Core)) !void {
var windows = core.windows.slice();
while (windows.next()) |window_id| {
var core_window = core.windows.getValue(window_id);
defer core.windows.setValueRaw(window_id, core_window);
mach.sysgpu.Impl.deviceTick(core_window.device);
core_window.swap_chain.present();
// Update swapchain for the next frame
if (core_window.swap_chain_update.isSet()) blk: {
core_window.swap_chain_update.reset();
switch (core_window.vsync_mode) {
.triple => core.frame.target = 2 * core_window.refresh_rate,
else => core.frame.target = 0,
}
if (core_window.width == 0 or core_window.height == 0) break :blk;
core_window.swap_chain_descriptor.present_mode = switch (core_window.vsync_mode) {
.none => .immediate,
.double => .fifo,
.triple => .mailbox,
};
core_window.swap_chain_descriptor.width = core_window.width;
core_window.swap_chain_descriptor.height = core_window.height;
core_window.swap_chain.release();
core_window.swap_chain = core_window.device.createSwapChain(core_window.surface, &core_window.swap_chain_descriptor);
}
}
// Record to frame rate frequency monitor that a frame was finished.
core.frame.tick();
switch (core.state) {
.running => {},
.exiting => {
core.state = .deinitializing;
core_mod.run(core.on_exit.?);
core_mod.call(.deinit);
},
.deinitializing => {},
.exited => @panic("application not running"),
}
}
pub fn exit(core: *Core) void {
core.state = .exiting;
}
inline fn requestAdapterCallback( inline fn requestAdapterCallback(
context: *RequestAdapterResponse, context: *RequestAdapterResponse,
status: gpu.RequestAdapterStatus, status: gpu.RequestAdapterStatus,

View file

@ -165,9 +165,19 @@ fn initWindow(
screen, screen,
); );
if (native_window_opt) |native_window| { if (native_window_opt) |native_window| {
const framebuffer_scale: f32 = @floatCast(native_window.backingScaleFactor());
const window_width: f32 = @floatFromInt(core_window.width);
const window_height: f32 = @floatFromInt(core_window.height);
core_window.framebuffer_width = @intFromFloat(window_width * framebuffer_scale);
core_window.framebuffer_height = @intFromFloat(window_height * framebuffer_scale);
native_window.setReleasedWhenClosed(false); native_window.setReleasedWhenClosed(false);
var view = objc.mach.View.allocInit(); var view = objc.mach.View.allocInit();
// initWithFrame is overridden in our MACHView, which creates a tracking area for mouse tracking
view = view.initWithFrame(rect);
view.setLayer(@ptrCast(layer)); view.setLayer(@ptrCast(layer));
const context = try core.allocator.create(Context); const context = try core.allocator.create(Context);
@ -190,6 +200,46 @@ fn initWindow(
null, null,
); );
view.setBlock_keyUp(keyUp.asBlock().copy()); view.setBlock_keyUp(keyUp.asBlock().copy());
var flagsChanged = objc.foundation.stackBlockLiteral(
ViewCallbacks.flagsChanged,
context,
null,
null,
);
view.setBlock_flagsChanged(flagsChanged.asBlock().copy());
var mouseMoved = objc.foundation.stackBlockLiteral(
ViewCallbacks.mouseMoved,
context,
null,
null,
);
view.setBlock_mouseMoved(mouseMoved.asBlock().copy());
var mouseDown = objc.foundation.stackBlockLiteral(
ViewCallbacks.mouseDown,
context,
null,
null,
);
view.setBlock_mouseDown(mouseDown.asBlock().copy());
var mouseUp = objc.foundation.stackBlockLiteral(
ViewCallbacks.mouseUp,
context,
null,
null,
);
view.setBlock_mouseUp(mouseUp.asBlock().copy());
var scrollWheel = objc.foundation.stackBlockLiteral(
ViewCallbacks.scrollWheel,
context,
null,
null,
);
view.setBlock_scrollWheel(scrollWheel.asBlock().copy());
} }
native_window.setContentView(@ptrCast(view)); native_window.setContentView(@ptrCast(view));
native_window.center(); native_window.center();
@ -261,12 +311,23 @@ const WindowDelegateCallbacks = struct {
const native_window: *objc.app_kit.Window = native.window; const native_window: *objc.app_kit.Window = native.window;
const frame = native_window.frame(); const frame = native_window.frame();
const content_rect = native_window.contentRectForFrameRect(frame); const content_rect = native_window.contentRectForFrameRect(frame);
core_window.width = @intFromFloat(content_rect.size.width); core_window.width = @intFromFloat(content_rect.size.width);
core_window.height = @intFromFloat(content_rect.size.height); core_window.height = @intFromFloat(content_rect.size.height);
core_window.swap_chain_update.set();
const framebuffer_scale: f32 = @floatCast(native_window.backingScaleFactor());
const window_width: f32 = @floatFromInt(core_window.width);
const window_height: f32 = @floatFromInt(core_window.height);
core_window.framebuffer_width = @intFromFloat(window_width * framebuffer_scale);
core_window.framebuffer_height = @intFromFloat(window_height * framebuffer_scale);
core_window.swap_chain_descriptor.width = core_window.framebuffer_width;
core_window.swap_chain_descriptor.height = core_window.framebuffer_height;
core_window.swap_chain.release();
core_window.swap_chain = core_window.device.createSwapChain(core_window.surface, &core_window.swap_chain_descriptor);
} }
core.windows.setValueRaw(block.context.window_id, core_window); core.windows.setValueRaw(block.context.window_id, core_window);
@ -288,6 +349,62 @@ const WindowDelegateCallbacks = struct {
}; };
const ViewCallbacks = struct { const ViewCallbacks = struct {
pub fn mouseMoved(block: *objc.foundation.BlockLiteral(*Context), event: *objc.app_kit.Event) callconv(.C) void {
const core: *Core = block.context.core;
const window_id = block.context.window_id;
const mouse_location = event.locationInWindow();
const window_height: f32 = @floatFromInt(core.windows.get(window_id, .height));
core.pushEvent(.{ .mouse_motion = .{
.window_id = window_id,
.pos = .{ .x = mouse_location.x, .y = window_height - mouse_location.y },
} });
}
pub fn mouseDown(block: *objc.foundation.BlockLiteral(*Context), event: *objc.app_kit.Event) callconv(.C) void {
const core: *Core = block.context.core;
const window_id = block.context.window_id;
core.pushEvent(.{ .mouse_press = .{
.window_id = window_id,
.button = @enumFromInt(event.buttonNumber()),
.pos = .{ .x = event.locationInWindow().x, .y = event.locationInWindow().y },
.mods = machModifierFromModifierFlag(event.modifierFlags()),
} });
}
pub fn mouseUp(block: *objc.foundation.BlockLiteral(*Context), event: *objc.app_kit.Event) callconv(.C) void {
const core: *Core = block.context.core;
const window_id = block.context.window_id;
core.pushEvent(.{ .mouse_release = .{
.window_id = window_id,
.button = @enumFromInt(event.buttonNumber()),
.pos = .{ .x = event.locationInWindow().x, .y = event.locationInWindow().y },
.mods = machModifierFromModifierFlag(event.modifierFlags()),
} });
}
pub fn scrollWheel(block: *objc.foundation.BlockLiteral(*Context), event: *objc.app_kit.Event) callconv(.C) void {
const core: *Core = block.context.core;
const window_id = block.context.window_id;
var scroll_delta_x = event.scrollingDeltaX();
var scroll_delta_y = event.scrollingDeltaY();
if (event.hasPreciseScrollingDeltas()) {
scroll_delta_x *= 0.1;
scroll_delta_y *= 0.1;
}
core.pushEvent(.{ .mouse_scroll = .{
.window_id = window_id,
.xoffset = @floatCast(scroll_delta_x),
.yoffset = @floatCast(scroll_delta_y),
} });
}
pub fn keyDown(block: *objc.foundation.BlockLiteral(*Context), event: *objc.app_kit.Event) callconv(.C) void { pub fn keyDown(block: *objc.foundation.BlockLiteral(*Context), event: *objc.app_kit.Event) callconv(.C) void {
const core: *Core = block.context.core; const core: *Core = block.context.core;
const window_id = block.context.window_id; const window_id = block.context.window_id;
@ -316,6 +433,33 @@ const ViewCallbacks = struct {
.mods = machModifierFromModifierFlag(event.modifierFlags()), .mods = machModifierFromModifierFlag(event.modifierFlags()),
} }); } });
} }
pub fn flagsChanged(block: *objc.foundation.BlockLiteral(*Context), event: *objc.app_kit.Event) callconv(.C) void {
const core: *Core = block.context.core;
const window_id = block.context.window_id;
const key = machKeyFromKeycode(event.keyCode());
const mods = machModifierFromModifierFlag(event.modifierFlags());
const key_flag = switch (key) {
.left_shift, .right_shift => objc.app_kit.EventModifierFlagShift,
.left_control, .right_control => objc.app_kit.EventModifierFlagControl,
.left_alt, .right_alt => objc.app_kit.EventModifierFlagOption,
.left_super, .right_super => objc.app_kit.EventModifierFlagCommand,
.caps_lock => objc.app_kit.EventModifierFlagCapsLock,
else => 0,
};
if (event.modifierFlags() & key_flag != 0) {
if (core.input_state.isKeyPressed(key)) {
core.pushEvent(.{ .key_release = .{ .window_id = window_id, .key = key, .mods = mods } });
} else {
core.pushEvent(.{ .key_press = .{ .window_id = window_id, .key = key, .mods = mods } });
}
} else {
core.pushEvent(.{ .key_release = .{ .window_id = window_id, .key = key, .mods = mods } });
}
}
}; };
fn machModifierFromModifierFlag(modifier_flag: usize) Core.KeyMods { fn machModifierFromModifierFlag(modifier_flag: usize) Core.KeyMods {