Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core: platform: Input callbacks, corrected framebuffer/window sizes, swapchain handling #1321

Merged
merged 12 commits into from
Dec 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
.lazy = true,
},
.mach_objc = .{
.url = "https://pkg.machengine.org/mach-objc/eb1e1eee9c02039d582f5fd9814d32e48b736ba6.tar.gz",
.hash = "12209742f139402c34a8901bfb012a748c7101bef971f0a541338d659baa345b237d",
.url = "https://pkg.machengine.org/mach-objc/61206f68d907111ce3c1f068ef7d0b926ead5d62.tar.gz",
.hash = "12205e56037f3c3112c073bc833eea5f95b6ec3fb03a53cc7264595d2266304a5f7f",
.lazy = true,
},
.xcode_frameworks = .{
Expand Down
11 changes: 8 additions & 3 deletions examples/core-transparent-window/App.zig
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pub fn init(
const window = try core.windows.new(.{
.title = "core-transparent-window",
.vsync_mode = .double,
.transparent = true,
});

// Store our render pipeline in our module's state, so we can access it later on.
Expand Down Expand Up @@ -124,10 +125,10 @@ pub fn tick(app: *App, core: *mach.Core) void {
defer encoder.release();

// Begin render pass
const sky_blue_background = gpu.Color{ .r = 0.0, .g = 0.0, .b = 0.0, .a = 0.0 };
const transparent_background = gpu.Color{ .r = 0.0, .g = 0.0, .b = 0.0, .a = 0.0 };
const color_attachments = [_]gpu.RenderPassColorAttachment{.{
.view = back_buffer_view,
.clear_value = sky_blue_background,
.clear_value = transparent_background,
.load_op = .clear,
.store_op = .store,
}};
Expand Down Expand Up @@ -172,7 +173,11 @@ pub fn tick(app: *App, core: *mach.Core) void {
const green = mach.math.lerp(0.2, 0.6, mach.math.clamp(app.color_time - 2.0, 0.0, 1.0));
const alpha = mach.math.lerp(0.3, 1.0, app.color_time / 4.0);

core.windows.set(app.window, .color, .{ .transparent = .{ .color = .{ .r = red, .g = green, .b = blue, .a = alpha }, .titlebar = true } });
core.windows.set(
app.window,
.decoration_color,
.{ .r = red, .g = green, .b = blue, .a = alpha },
);
}

pub fn deinit(app: *App) void {
Expand Down
267 changes: 57 additions & 210 deletions src/Core.zig
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ windows: mach.Objects(
/// Vertical sync mode, prevents screen tearing.
vsync_mode: VSyncMode = .none,

/// Window display mode: fullscreen, windowed or borderless fullscreen
display_mode: DisplayMode = .windowed,

/// Cursor
Expand All @@ -74,8 +75,17 @@ windows: mach.Objects(
/// Target frames per second
refresh_rate: u32 = 0,

/// Color of the window background/titlebar
color: WindowColor = .system,
/// Titlebar/window decorations
decorated: bool = true,

/// Color of the window decorations, i.e. titlebar
/// if null, decoration is the system-determined color
decoration_color: ?gpu.Color = null,

/// Whether the window should be completely transparent
/// or not. On macOS, to achieve a fully transparent window
/// decoration_color must also be set fully transparent.
transparent: bool = false,

// GPU
// When `native` is not null, the rest of the fields have been
Expand All @@ -86,7 +96,6 @@ windows: mach.Objects(
queue: *gpu.Queue = undefined,
swap_chain: *gpu.SwapChain = undefined,
swap_chain_descriptor: gpu.SwapChain.Descriptor = undefined,
swap_chain_update: std.Thread.ResetEvent = .{},
surface: *gpu.Surface = undefined,
surface_descriptor: gpu.Surface.Descriptor = undefined,

Expand All @@ -99,6 +108,7 @@ windows: mach.Objects(
.render_attachment = true,
},

/// Container for native platform-specific information
native: ?Platform.Native = null,
},
),
Expand Down Expand Up @@ -209,19 +219,15 @@ pub fn initWindow(core: *Core, window_id: mach.ObjectID) !void {
.label = "main swap chain",
.usage = core_window.swap_chain_usage,
.format = .bgra8_unorm,
.width = core_window.width,
.height = core_window.height,
.width = core_window.framebuffer_width,
.height = core_window.framebuffer_height,
.present_mode = switch (core_window.vsync_mode) {
.none => .immediate,
.double => .fifo,
.triple => .mailbox,
},
};
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 } });
}

Expand All @@ -236,6 +242,32 @@ pub fn tick(core: *Core, core_mod: mach.Mod(Core)) !void {
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 {
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");
Expand Down Expand Up @@ -286,11 +318,14 @@ fn platform_update_callback(core: *Core, core_mod: mach.Mod(Core)) !bool {

core_mod.run(core.on_tick.?);
core_mod.call(.presentFrame);
//core_mod.call(.processWindowUpdates);

return core.state != .exited;
}

pub fn exit(core: *Core) void {
core.state = .exiting;
}

pub fn deinit(core: *Core) !void {
core.state = .exited;

Expand Down Expand Up @@ -369,206 +404,6 @@ pub fn mousePosition(core: *@This()) 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(
context: *RequestAdapterResponse,
status: gpu.RequestAdapterStatus,
Expand Down Expand Up @@ -697,6 +532,7 @@ pub const Event = union(enum) {
window_open: struct {
window_id: mach.ObjectID,
},
zoom_gesture: ZoomGestureEvent,
focus_gained: struct {
window_id: mach.ObjectID,
},
Expand Down Expand Up @@ -726,6 +562,17 @@ pub const ResizeEvent = struct {
size: Size,
};

pub const ZoomGestureEvent = struct {
window_id: mach.ObjectID,
phase: GesturePhase,
zoom: f32,
};

pub const GesturePhase = enum {
began,
ended,
};

pub const MouseButton = enum {
left,
right,
Expand Down
Loading
Loading