core: add mach.Core module API
Signed-off-by: Stephen Gutekanst <stephen@hexops.com>
This commit is contained in:
parent
69b749879d
commit
013546b189
8 changed files with 309 additions and 41 deletions
25
build.zig
25
build.zig
|
|
@ -627,6 +627,7 @@ fn buildExamples(
|
||||||
deps: []const Dependency = &.{},
|
deps: []const Dependency = &.{},
|
||||||
std_platform_only: bool = false,
|
std_platform_only: bool = false,
|
||||||
has_assets: bool = false,
|
has_assets: bool = false,
|
||||||
|
use_module_api: bool = false,
|
||||||
}{
|
}{
|
||||||
.{ .name = "sysaudio", .deps = &.{} },
|
.{ .name = "sysaudio", .deps = &.{} },
|
||||||
.{
|
.{
|
||||||
|
|
@ -634,6 +635,7 @@ fn buildExamples(
|
||||||
.deps = &.{ .zigimg, .freetype, .assets },
|
.deps = &.{ .zigimg, .freetype, .assets },
|
||||||
.std_platform_only = true,
|
.std_platform_only = true,
|
||||||
},
|
},
|
||||||
|
.{ .name = "core-custom-entrypoint", .deps = &.{}, .use_module_api = true },
|
||||||
.{ .name = "custom-renderer", .deps = &.{} },
|
.{ .name = "custom-renderer", .deps = &.{} },
|
||||||
.{
|
.{
|
||||||
.name = "sprite",
|
.name = "sprite",
|
||||||
|
|
@ -658,6 +660,28 @@ fn buildExamples(
|
||||||
if (target.result.cpu.arch == .wasm32)
|
if (target.result.cpu.arch == .wasm32)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
if (example.use_module_api) {
|
||||||
|
const exe = b.addExecutable(.{
|
||||||
|
.name = example.name,
|
||||||
|
.root_source_file = .{ .path = "examples/" ++ example.name ++ "/main.zig" },
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
exe.root_module.addImport("mach", mach_mod);
|
||||||
|
addPaths(&exe.root_module);
|
||||||
|
link(b, exe, &exe.root_module);
|
||||||
|
b.installArtifact(exe);
|
||||||
|
|
||||||
|
const compile_step = b.step(example.name, "Compile " ++ example.name);
|
||||||
|
compile_step.dependOn(b.getInstallStep());
|
||||||
|
|
||||||
|
const run_cmd = b.addRunArtifact(exe);
|
||||||
|
run_cmd.step.dependOn(b.getInstallStep());
|
||||||
|
if (b.args) |args| run_cmd.addArgs(args);
|
||||||
|
|
||||||
|
const run_step = b.step("run-" ++ example.name, "Run " ++ example.name);
|
||||||
|
run_step.dependOn(&run_cmd.step);
|
||||||
|
} else {
|
||||||
var deps = std.ArrayList(std.Build.Module.Import).init(b.allocator);
|
var deps = std.ArrayList(std.Build.Module.Import).init(b.allocator);
|
||||||
for (example.deps) |d| try deps.append(d.dependency(b, target, optimize));
|
for (example.deps) |d| try deps.append(d.dependency(b, target, optimize));
|
||||||
const app = try App.init(
|
const app = try App.init(
|
||||||
|
|
@ -691,6 +715,7 @@ fn buildExamples(
|
||||||
const run_step = b.step("run-" ++ example.name, "Run " ++ example.name);
|
const run_step = b.step("run-" ++ example.name, "Run " ++ example.name);
|
||||||
run_step.dependOn(&app.run.step);
|
run_step.dependOn(&app.run.step);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn buildCoreExamples(
|
fn buildCoreExamples(
|
||||||
|
|
|
||||||
105
examples/core-custom-entrypoint/Game.zig
Normal file
105
examples/core-custom-entrypoint/Game.zig
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const mach = @import("mach");
|
||||||
|
const gpu = mach.gpu;
|
||||||
|
|
||||||
|
pub const name = .game;
|
||||||
|
pub const Mod = mach.Mod(@This());
|
||||||
|
|
||||||
|
pub const global_events = .{
|
||||||
|
.init = .{ .handler = init },
|
||||||
|
.tick = .{ .handler = tick },
|
||||||
|
};
|
||||||
|
|
||||||
|
title_timer: mach.Timer,
|
||||||
|
pipeline: *gpu.RenderPipeline,
|
||||||
|
|
||||||
|
fn init(game: *Mod) !void {
|
||||||
|
const shader_module = mach.core.device.createShaderModuleWGSL("shader.wgsl", @embedFile("shader.wgsl"));
|
||||||
|
defer shader_module.release();
|
||||||
|
|
||||||
|
// Fragment state
|
||||||
|
const blend = gpu.BlendState{};
|
||||||
|
const color_target = gpu.ColorTargetState{
|
||||||
|
.format = mach.core.descriptor.format,
|
||||||
|
.blend = &blend,
|
||||||
|
.write_mask = gpu.ColorWriteMaskFlags.all,
|
||||||
|
};
|
||||||
|
const fragment = gpu.FragmentState.init(.{
|
||||||
|
.module = shader_module,
|
||||||
|
.entry_point = "frag_main",
|
||||||
|
.targets = &.{color_target},
|
||||||
|
});
|
||||||
|
const pipeline_descriptor = gpu.RenderPipeline.Descriptor{
|
||||||
|
.fragment = &fragment,
|
||||||
|
.vertex = gpu.VertexState{
|
||||||
|
.module = shader_module,
|
||||||
|
.entry_point = "vertex_main",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const pipeline = mach.core.device.createRenderPipeline(&pipeline_descriptor);
|
||||||
|
|
||||||
|
game.init(.{
|
||||||
|
.title_timer = try mach.Timer.start(),
|
||||||
|
.pipeline = pipeline,
|
||||||
|
});
|
||||||
|
try updateWindowTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(game: *Mod) void {
|
||||||
|
game.state().pipeline.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(important): remove need for returning an error here
|
||||||
|
fn tick(
|
||||||
|
core: *mach.Core.Mod,
|
||||||
|
game: *Mod,
|
||||||
|
) !void {
|
||||||
|
// TODO(important): event polling should occur in mach.Core module and get fired as ECS event.
|
||||||
|
var iter = mach.core.pollEvents();
|
||||||
|
while (iter.next()) |event| {
|
||||||
|
switch (event) {
|
||||||
|
.close => core.send(.exit, .{}), // Tell mach.Core to exit the app
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const queue = mach.core.queue;
|
||||||
|
const back_buffer_view = mach.core.swap_chain.getCurrentTextureView().?;
|
||||||
|
const color_attachment = gpu.RenderPassColorAttachment{
|
||||||
|
.view = back_buffer_view,
|
||||||
|
.clear_value = std.mem.zeroes(gpu.Color),
|
||||||
|
.load_op = .clear,
|
||||||
|
.store_op = .store,
|
||||||
|
};
|
||||||
|
|
||||||
|
const encoder = mach.core.device.createCommandEncoder(null);
|
||||||
|
const render_pass_info = gpu.RenderPassDescriptor.init(.{
|
||||||
|
.color_attachments = &.{color_attachment},
|
||||||
|
});
|
||||||
|
const pass = encoder.beginRenderPass(&render_pass_info);
|
||||||
|
pass.setPipeline(game.state().pipeline);
|
||||||
|
pass.draw(3, 1, 0, 0);
|
||||||
|
pass.end();
|
||||||
|
pass.release();
|
||||||
|
|
||||||
|
var command = encoder.finish(null);
|
||||||
|
encoder.release();
|
||||||
|
|
||||||
|
queue.submit(&[_]*gpu.CommandBuffer{command});
|
||||||
|
command.release();
|
||||||
|
mach.core.swap_chain.present();
|
||||||
|
back_buffer_view.release();
|
||||||
|
|
||||||
|
// update the window title every second
|
||||||
|
if (game.state().title_timer.read() >= 1.0) {
|
||||||
|
game.state().title_timer.reset();
|
||||||
|
try updateWindowTitle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn updateWindowTitle() !void {
|
||||||
|
try mach.core.printTitle("mach.Core - custom entrypoint [ {d}fps ] [ Input {d}hz ]", .{
|
||||||
|
mach.core.frameRate(),
|
||||||
|
mach.core.inputRate(),
|
||||||
|
});
|
||||||
|
}
|
||||||
20
examples/core-custom-entrypoint/main.zig
Normal file
20
examples/core-custom-entrypoint/main.zig
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const mach = @import("mach");
|
||||||
|
const Game = @import("Game.zig");
|
||||||
|
|
||||||
|
// The global list of Mach modules registered for use in our application.
|
||||||
|
pub const modules = .{
|
||||||
|
mach.Core,
|
||||||
|
Game,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const GPUInterface = mach.core.wgpu.dawn.Interface;
|
||||||
|
|
||||||
|
pub fn main() !void {
|
||||||
|
// Initialize mach.Core
|
||||||
|
try mach.core.initModule();
|
||||||
|
|
||||||
|
// Main loop
|
||||||
|
while (try mach.core.tick()) {}
|
||||||
|
}
|
||||||
14
examples/core-custom-entrypoint/shader.wgsl
Normal file
14
examples/core-custom-entrypoint/shader.wgsl
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
@vertex fn vertex_main(
|
||||||
|
@builtin(vertex_index) VertexIndex : u32
|
||||||
|
) -> @builtin(position) vec4<f32> {
|
||||||
|
var pos = array<vec2<f32>, 3>(
|
||||||
|
vec2<f32>( 0.0, 0.5),
|
||||||
|
vec2<f32>(-0.5, -0.5),
|
||||||
|
vec2<f32>( 0.5, -0.5)
|
||||||
|
);
|
||||||
|
return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@fragment fn frag_main() -> @location(0) vec4<f32> {
|
||||||
|
return vec4<f32>(1.0, 0.0, 0.0, 1.0);
|
||||||
|
}
|
||||||
67
src/Core.zig
Normal file
67
src/Core.zig
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const mach = @import("main.zig");
|
||||||
|
|
||||||
|
pub const name = .mach_core;
|
||||||
|
|
||||||
|
pub const Mod = mach.Mod(@This());
|
||||||
|
|
||||||
|
pub const global_events = .{
|
||||||
|
.init = .{ .handler = fn () void },
|
||||||
|
.deinit = .{ .handler = fn () void },
|
||||||
|
.tick = .{ .handler = fn () void },
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const local_events = .{
|
||||||
|
.init = .{ .handler = init },
|
||||||
|
|
||||||
|
// TODO(important): need some way to tie event execution to a specific thread once we have a
|
||||||
|
// multithreaded dispatch implementation
|
||||||
|
.main_thread_tick = .{ .handler = mainThreadTick },
|
||||||
|
.main_thread_tick_done = .{ .handler = fn () void },
|
||||||
|
.deinit = .{ .handler = deinit },
|
||||||
|
.exit = .{ .handler = exit },
|
||||||
|
};
|
||||||
|
|
||||||
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
|
|
||||||
|
device: *mach.gpu.Device,
|
||||||
|
queue: *mach.gpu.Queue,
|
||||||
|
should_exit: bool = false,
|
||||||
|
|
||||||
|
fn init(core: *Mod) !void {
|
||||||
|
// Initialize GPU implementation
|
||||||
|
if (comptime mach.core.options.use_wgpu) try mach.core.wgpu.Impl.init(mach.core.allocator, .{});
|
||||||
|
if (comptime mach.core.options.use_sysgpu) try mach.core.sysgpu.Impl.init(mach.core.allocator, .{});
|
||||||
|
|
||||||
|
mach.core.allocator = gpa.allocator(); // TODO: banish this global allocator
|
||||||
|
try mach.core.init(.{});
|
||||||
|
|
||||||
|
core.init(.{
|
||||||
|
.device = mach.core.device,
|
||||||
|
.queue = mach.core.device.getQueue(),
|
||||||
|
});
|
||||||
|
|
||||||
|
core.sendGlobal(.init, .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deinit(core: *Mod) void {
|
||||||
|
core.state().queue.release();
|
||||||
|
// TODO: this triggers a device loss error, which we should handle correctly
|
||||||
|
// core.state().device.release();
|
||||||
|
mach.core.deinit();
|
||||||
|
_ = gpa.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mainThreadTick(core: *Mod) !void {
|
||||||
|
_ = try mach.core.update(null);
|
||||||
|
|
||||||
|
// Send .tick to anyone interested
|
||||||
|
core.sendGlobal(.tick, .{});
|
||||||
|
|
||||||
|
// Signal that mainThreadTick is done
|
||||||
|
core.send(.main_thread_tick_done, .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exit(core: *Mod) void {
|
||||||
|
core.state().should_exit = true;
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,30 @@ pub const Timer = @import("Timer.zig");
|
||||||
const Frequency = @import("Frequency.zig");
|
const Frequency = @import("Frequency.zig");
|
||||||
const platform = @import("platform.zig");
|
const platform = @import("platform.zig");
|
||||||
|
|
||||||
|
const mach = @import("../main.zig");
|
||||||
|
pub var mods: mach.Modules = undefined;
|
||||||
|
|
||||||
|
pub fn initModule() !void {
|
||||||
|
// Initialize the global set of Mach modules used in the program.
|
||||||
|
try mods.init(std.heap.c_allocator);
|
||||||
|
mods.mod.mach_core.send(.init, .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tick runs a single step of the main loop on the main OS thread.
|
||||||
|
///
|
||||||
|
/// Returns true if tick() should be called again, false if the application should exit.
|
||||||
|
pub fn tick() !bool {
|
||||||
|
mods.mod.mach_core.send(.main_thread_tick, .{});
|
||||||
|
|
||||||
|
// Dispatch events until this .mach_core.main_thread_tick_done is sent
|
||||||
|
try mods.dispatch(.{ .until = .{
|
||||||
|
.module_name = mods.moduleNameToID(.mach_core),
|
||||||
|
.local_event = mods.localEventToID(.mach_core, .main_thread_tick_done),
|
||||||
|
} });
|
||||||
|
|
||||||
|
return !mods.mod.mach_core.state().should_exit;
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the error set that the function F returns.
|
/// Returns the error set that the function F returns.
|
||||||
fn ErrorSet(comptime F: type) type {
|
fn ErrorSet(comptime F: type) type {
|
||||||
return @typeInfo(@typeInfo(F).Fn.return_type.?).ErrorUnion.error_set;
|
return @typeInfo(@typeInfo(F).Fn.return_type.?).ErrorUnion.error_set;
|
||||||
|
|
|
||||||
|
|
@ -581,6 +581,8 @@ pub fn appUpdateThreadTick(self: *Core, app: anytype) bool {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const use_app = @typeInfo(@TypeOf(app)) == .Pointer;
|
||||||
|
if (use_app) {
|
||||||
if (app.update() catch unreachable) {
|
if (app.update() catch unreachable) {
|
||||||
self.done.set();
|
self.done.set();
|
||||||
|
|
||||||
|
|
@ -589,6 +591,7 @@ pub fn appUpdateThreadTick(self: *Core, app: anytype) bool {
|
||||||
glfw.postEmptyEvent();
|
glfw.postEmptyEvent();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
self.gpu_device.tick();
|
self.gpu_device.tick();
|
||||||
self.gpu_device.machWaitForCommandsToBeScheduled();
|
self.gpu_device.machWaitForCommandsToBeScheduled();
|
||||||
|
|
||||||
|
|
@ -605,11 +608,14 @@ pub fn appUpdateThread(self: *Core, app: anytype) void {
|
||||||
// Called on the main thread
|
// Called on the main thread
|
||||||
pub fn update(self: *Core, app: anytype) !bool {
|
pub fn update(self: *Core, app: anytype) !bool {
|
||||||
if (self.done.isSet()) return true;
|
if (self.done.isSet()) return true;
|
||||||
|
const use_app = @typeInfo(@TypeOf(app)) == .Pointer;
|
||||||
|
if (use_app) {
|
||||||
if (!self.app_update_thread_started) {
|
if (!self.app_update_thread_started) {
|
||||||
self.app_update_thread_started = true;
|
self.app_update_thread_started = true;
|
||||||
const thread = try std.Thread.spawn(.{}, appUpdateThread, .{ self, app });
|
const thread = try std.Thread.spawn(.{}, appUpdateThread, .{ self, app });
|
||||||
thread.detach();
|
thread.detach();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (self.state_update.isSet()) {
|
if (self.state_update.isSet()) {
|
||||||
self.state_update.reset();
|
self.state_update.reset();
|
||||||
|
|
@ -748,6 +754,7 @@ pub fn update(self: *Core, app: anytype) !bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (use_app) {
|
||||||
const frequency_delay = @as(f32, @floatFromInt(self.input.delay_ns)) / @as(f32, @floatFromInt(std.time.ns_per_s));
|
const frequency_delay = @as(f32, @floatFromInt(self.input.delay_ns)) / @as(f32, @floatFromInt(std.time.ns_per_s));
|
||||||
glfw.waitEventsTimeout(frequency_delay);
|
glfw.waitEventsTimeout(frequency_delay);
|
||||||
|
|
||||||
|
|
@ -757,6 +764,9 @@ pub fn update(self: *Core, app: anytype) !bool {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
glfw.pollEvents();
|
||||||
|
}
|
||||||
|
|
||||||
glfw.getErrorCode() catch |err| switch (err) {
|
glfw.getErrorCode() catch |err| switch (err) {
|
||||||
error.PlatformError => log.err("glfw: failed to poll events", .{}),
|
error.PlatformError => log.err("glfw: failed to poll events", .{}),
|
||||||
|
|
@ -764,6 +774,8 @@ pub fn update(self: *Core, app: anytype) !bool {
|
||||||
else => unreachable,
|
else => unreachable,
|
||||||
};
|
};
|
||||||
self.input.tick();
|
self.input.tick();
|
||||||
|
|
||||||
|
if (!use_app) return !self.appUpdateThreadTick(app);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ pub const core = if (build_options.want_core) @import("core/main.zig") else stru
|
||||||
pub const Timer = if (build_options.want_core) core.Timer else struct {};
|
pub const Timer = if (build_options.want_core) core.Timer else struct {};
|
||||||
pub const gpu = if (build_options.want_core) core.gpu else struct {};
|
pub const gpu = if (build_options.want_core) core.gpu else struct {};
|
||||||
pub const sysjs = if (build_options.want_core) @import("mach-sysjs") else struct {};
|
pub const sysjs = if (build_options.want_core) @import("mach-sysjs") else struct {};
|
||||||
|
pub const Core = if (build_options.want_core) @import("Core.zig") else struct {};
|
||||||
|
|
||||||
// Mach standard library
|
// Mach standard library
|
||||||
// gamemode requires libc on linux
|
// gamemode requires libc on linux
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue