Skip to content

Core: Add ability to have separate render thread accessed through window.on_tick, which is called each display refresh. #1415

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

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
2 changes: 1 addition & 1 deletion build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ pub fn build(b: *std.Build) !void {
build_options.addOption(SysgpuBackend, "sysgpu_backend", sysgpu_backend);

var examples = [_]Example{
.{ .name = "core-custom-entrypoint", .deps = &.{} },
//.{ .name = "core-custom-entrypoint", .deps = &.{} },
.{ .name = "core-triangle", .deps = &.{} },
.{ .name = "core-transparent-window", .deps = &.{} },
.{ .name = "custom-renderer", .deps = &.{} },
Expand Down
6 changes: 4 additions & 2 deletions build.zig.zon
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
.{
.name = "mach",
.version = "0.4.0",
.mach_zig_version = "2024.11.0-mach",
.paths = .{
"src",
"build.zig",
Expand All @@ -22,8 +23,9 @@
.lazy = true,
},
.mach_objc = .{
.url = "https://pkg.machengine.org/mach-objc/79b6f80c32b14948554958afe72dace261b14afc.tar.gz",
.hash = "12203675829014e69be2ea7c126ecf25d403009d336b7ca5f6e7c4ccede826c8e597",
//.path = "../mach-objc-dev",
.url = "https://github.com/foxnne/mach-objc/archive/59b75ebc1253a2517a08940086e66e574e63f2e1.tar.gz",
.hash = "1220097d6c23dc1f4ec7c46a8f94171b4a09f954ee09156951db81edb20405a7ebf8",
.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 @@ -4,6 +4,8 @@ const gpu = mach.gpu;

const App = @This();

const title = "core-transparent-window [ {d}fps ] [ Input {d}hz ]";

// The set of Mach modules our application may use.
pub const Modules = mach.Modules(.{
mach.Core,
Expand Down Expand Up @@ -36,8 +38,7 @@ pub fn init(
core.on_exit = app_mod.id.deinit;

const window = try core.windows.new(.{
.title = "core-transparent-window",
.vsync_mode = .double,
.title = try std.fmt.allocPrintZ(core.allocator, title, .{ 0, 0 }),
.transparent = true,
});

Expand Down Expand Up @@ -156,11 +157,15 @@ pub fn tick(app: *App, core: *mach.Core) void {
defer command.release();
window.queue.submit(&[_]*gpu.CommandBuffer{command});

mach.sysgpu.Impl.deviceTick(window.device);

window.swap_chain.present();

if (app.title_timer.read() >= 1.0) {
app.title_timer.reset();
// TODO(object): window-title

core.windows.set(app.window, .title, std.fmt.allocPrintZ(core.allocator, "core-transparent-window [ {d}fps ] [ Input {d}hz ]", .{ core.frame.rate, core.input.rate }) catch unreachable);
core.windows.set(app.window, .title, std.fmt.allocPrintZ(core.allocator, title, .{ window.frame.rate, core.frame.rate }) catch unreachable);
}

if (app.color_time >= 4.0 or app.color_time <= 0.0) {
Expand Down
53 changes: 28 additions & 25 deletions examples/core-triangle/App.zig
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ pub const mach_module = .app;

pub const mach_systems = .{ .main, .init, .tick, .deinit };

pub const window_title = "core_triangle [ {d}fps ] [ Input {d}hz ]";

pub const main = mach.schedule(.{
.{ mach.Core, .init },
.{ App, .init },
Expand All @@ -23,6 +25,7 @@ pub const main = mach.schedule(.{
window: mach.ObjectID,
title_timer: mach.time.Timer,
pipeline: *gpu.RenderPipeline,
frames: usize = 0,

pub fn init(
core: *mach.Core,
Expand All @@ -33,7 +36,11 @@ pub fn init(
core.on_exit = app_mod.id.deinit;

const window = try core.windows.new(.{
.title = "core-triangle",
.title = try std.fmt.allocPrintZ(
core.allocator,
window_title,
.{ 0, 0 },
),
});

// Store our render pipeline in our module's state, so we can access it later on.
Expand Down Expand Up @@ -81,10 +88,7 @@ fn setupPipeline(core: *mach.Core, app: *App, window_id: mach.ObjectID) !void {
app.pipeline = window.device.createRenderPipeline(&pipeline_descriptor);
}

// TODO(object): window-title
// try updateWindowTitle(core);

pub fn tick(app: *App, core: *mach.Core) void {
pub fn tick(app: *App, core: *mach.Core) !void {
while (core.nextEvent()) |event| {
switch (event) {
.window_open => |ev| {
Expand Down Expand Up @@ -134,28 +138,27 @@ pub fn tick(app: *App, core: *mach.Core) void {
defer command.release();
window.queue.submit(&[_]*gpu.CommandBuffer{command});

// update the window title every second
// if (app.title_timer.read() >= 1.0) {
// app.title_timer.reset();
// // TODO(object): window-title
// // try updateWindowTitle(core);
// }
mach.sysgpu.Impl.deviceTick(window.device);

window.swap_chain.present();

app.frames += 1;

//update the window title every second
if (app.title_timer.read() >= 1.0) {
app.title_timer.reset();

core.allocator.free(window.title);
core.windows.set(app.window, .title, try std.fmt.allocPrintZ(
core.allocator,
window_title,
.{ app.frames, core.frame.rate },
));

app.frames = 0;
}
}

pub fn deinit(app: *App) void {
app.pipeline.release();
}

// TODO(object): window-title
// fn updateWindowTitle(core: *mach.Core) !void {
// try core.printTitle(
// core.main_window,
// "core-custom-entrypoint [ {d}fps ] [ Input {d}hz ]",
// .{
// // TODO(Core)
// core.frameRate(),
// core.inputRate(),
// },
// );
// core.schedule(.update);
// }
4 changes: 4 additions & 0 deletions examples/custom-renderer/Renderer.zig
Original file line number Diff line number Diff line change
Expand Up @@ -171,4 +171,8 @@ pub fn renderFrame(
var command = encoder.finish(&.{ .label = label });
defer command.release();
window.queue.submit(&[_]*gpu.CommandBuffer{command});

mach.sysgpu.Impl.deviceTick(window.device);

window.swap_chain.present();
}
4 changes: 4 additions & 0 deletions examples/glyphs/App.zig
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,10 @@ pub fn tick(
command.release();
render_pass.release();

mach.sysgpu.Impl.deviceTick(window.device);

window.swap_chain.present();

app.frame_count += 1;
app.time += delta_time;

Expand Down
4 changes: 4 additions & 0 deletions examples/hardware-check/App.zig
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,10 @@ pub fn tick(
command.release();
render_pass.release();

mach.sysgpu.Impl.deviceTick(window.device);

window.swap_chain.present();

app.frame_count += 1;
app.time += delta_time;

Expand Down
4 changes: 4 additions & 0 deletions examples/piano/App.zig
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,10 @@ pub fn tick(
var command = encoder.finish(&.{ .label = label });
defer command.release();
window.queue.submit(&[_]*gpu.CommandBuffer{command});

mach.sysgpu.Impl.deviceTick(window.device);

window.swap_chain.present();
}

fn fillTone(app: *App, audio: *mach.Audio, frequency: f32) ![]align(mach.Audio.alignment) const f32 {
Expand Down
4 changes: 4 additions & 0 deletions examples/play-opus/App.zig
Original file line number Diff line number Diff line change
Expand Up @@ -190,4 +190,8 @@ pub fn tick(
var command = encoder.finish(&.{ .label = label });
defer command.release();
window.queue.submit(&[_]*gpu.CommandBuffer{command});

mach.sysgpu.Impl.deviceTick(window.device);

window.swap_chain.present();
}
4 changes: 4 additions & 0 deletions examples/sprite/App.zig
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,10 @@ pub fn tick(
command.release();
render_pass.release();

mach.sysgpu.Impl.deviceTick(window.device);

window.swap_chain.present();

app.frame_count += 1;
app.time += delta_time;

Expand Down
4 changes: 4 additions & 0 deletions examples/text/App.zig
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,10 @@ pub fn tick(
command.release();
render_pass.release();

mach.sysgpu.Impl.deviceTick(window.device);

window.swap_chain.present();

app.frame_count += 1;
app.time += delta_time;

Expand Down
74 changes: 39 additions & 35 deletions src/Core.zig
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const EventQueue = std.fifo.LinearFifo(Event, .Dynamic);

pub const mach_module = .mach_core;

pub const mach_systems = .{ .main, .init, .tick, .presentFrame, .deinit };
pub const mach_systems = .{ .main, .init, .tick, .deinit };

// Set track_fields to true so that when these field values change, we know about it
// and can update the platform windows.
Expand All @@ -24,6 +24,13 @@ windows: mach.Objects(
// TODO: allocation/free strategy
title: [:0]const u8 = "Mach Window",

/// Callback called directly from backend when a frame is needed to be rendered
/// Will match the monitors refresh rate
on_tick: ?mach.FunctionID = null,

/// Frequency to get ticks per second
frame: mach.time.Frequency = .{ .target = 0 },

/// Texture format of the framebuffer (read-only)
framebuffer_format: gpu.Texture.Format = .bgra8_unorm,

Expand All @@ -35,9 +42,6 @@ windows: mach.Objects(
/// Will be updated to reflect the actual framebuffer dimensions after window creation.
framebuffer_height: u32 = 1080 / 2,

/// Vertical sync mode, prevents screen tearing.
vsync_mode: VSyncMode = .none,

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

Expand All @@ -51,9 +55,6 @@ windows: mach.Objects(
/// Height of the window in virtual pixels
height: u32 = 1080 / 2,

/// Target frames per second
refresh_rate: u32 = 0,

/// Whether window decorations (titlebar, borders, etc.) should be shown.
///
/// Has no effect on windows who DisplayMode is .fullscreen or .fullscreen_borderless
Expand Down Expand Up @@ -111,7 +112,6 @@ state: enum {
} = .running,

frame: mach.time.Frequency,
input: mach.time.Frequency,

// Internal module state
allocator: std.mem.Allocator,
Expand All @@ -136,12 +136,10 @@ pub fn init(core: *Core) !void {
.events = events,
.input_state = .{},

.input = .{ .target = 0 },
.frame = .{ .target = 1 },
};

try core.frame.start();
try core.input.start();
}

pub fn initWindow(core: *Core, window_id: mach.ObjectID) !void {
Expand Down Expand Up @@ -204,13 +202,10 @@ pub fn initWindow(core: *Core, window_id: mach.ObjectID) !void {
.format = .bgra8_unorm,
.width = core_window.framebuffer_width,
.height = core_window.framebuffer_height,
.present_mode = switch (core_window.vsync_mode) {
.none => .immediate,
.double => .fifo,
.triple => .mailbox,
},
.present_mode = .fifo,
};
core_window.swap_chain = core_window.device.createSwapChain(core_window.surface, &core_window.swap_chain_descriptor);
try core_window.frame.start();
core.pushEvent(.{ .window_open = .{ .window_id = window_id } });
}

Expand All @@ -219,25 +214,11 @@ pub fn tick(core: *Core, core_mod: mach.Mod(Core)) !void {
// during application execution, rendering to multiple windows, etc.) and how
// that relates to Platform.tick being responsible for both handling window updates
// (like title/size changes) and window creation, plus multi-threaded rendering.
try Platform.tick(core);
try Platform.tick(core, core_mod);

core_mod.run(core.on_tick.?);
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();
//core_mod.call(.presentFrame);

switch (core.state) {
.running => {},
Expand All @@ -255,9 +236,20 @@ 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");

try Platform.tick(core);
try Platform.tick(core, core_mod);
core_mod.run(core.on_tick.?);
core_mod.call(.presentFrame);
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"),
}

// Platform drives the main loop.
Platform.run(platform_update_callback, .{ core, core_mod });
Expand All @@ -272,10 +264,22 @@ fn platform_update_callback(core: *Core, core_mod: mach.Mod(Core)) !bool {
// during application execution, rendering to multiple windows, etc.) and how
// that relates to Platform.tick being responsible for both handling window updates
// (like title/size changes) and window creation, plus multi-threaded rendering.
try Platform.tick(core);

try Platform.tick(core, core_mod);

core_mod.run(core.on_tick.?);
core_mod.call(.presentFrame);
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"),
}

return core.state != .exited;
}
Expand Down
Loading
Loading