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 = &.{},
|
||||
std_platform_only: bool = false,
|
||||
has_assets: bool = false,
|
||||
use_module_api: bool = false,
|
||||
}{
|
||||
.{ .name = "sysaudio", .deps = &.{} },
|
||||
.{
|
||||
|
|
@ -634,6 +635,7 @@ fn buildExamples(
|
|||
.deps = &.{ .zigimg, .freetype, .assets },
|
||||
.std_platform_only = true,
|
||||
},
|
||||
.{ .name = "core-custom-entrypoint", .deps = &.{}, .use_module_api = true },
|
||||
.{ .name = "custom-renderer", .deps = &.{} },
|
||||
.{
|
||||
.name = "sprite",
|
||||
|
|
@ -658,6 +660,28 @@ fn buildExamples(
|
|||
if (target.result.cpu.arch == .wasm32)
|
||||
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);
|
||||
for (example.deps) |d| try deps.append(d.dependency(b, target, optimize));
|
||||
const app = try App.init(
|
||||
|
|
@ -692,6 +716,7 @@ fn buildExamples(
|
|||
run_step.dependOn(&app.run.step);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn buildCoreExamples(
|
||||
b: *std.Build,
|
||||
|
|
|
|||
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 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.
|
||||
fn ErrorSet(comptime F: type) type {
|
||||
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) {
|
||||
self.done.set();
|
||||
|
||||
|
|
@ -589,6 +591,7 @@ pub fn appUpdateThreadTick(self: *Core, app: anytype) bool {
|
|||
glfw.postEmptyEvent();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
self.gpu_device.tick();
|
||||
self.gpu_device.machWaitForCommandsToBeScheduled();
|
||||
|
||||
|
|
@ -605,11 +608,14 @@ pub fn appUpdateThread(self: *Core, app: anytype) void {
|
|||
// Called on the main thread
|
||||
pub fn update(self: *Core, app: anytype) !bool {
|
||||
if (self.done.isSet()) return true;
|
||||
const use_app = @typeInfo(@TypeOf(app)) == .Pointer;
|
||||
if (use_app) {
|
||||
if (!self.app_update_thread_started) {
|
||||
self.app_update_thread_started = true;
|
||||
const thread = try std.Thread.spawn(.{}, appUpdateThread, .{ self, app });
|
||||
thread.detach();
|
||||
}
|
||||
}
|
||||
|
||||
if (self.state_update.isSet()) {
|
||||
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));
|
||||
glfw.waitEventsTimeout(frequency_delay);
|
||||
|
||||
|
|
@ -757,6 +764,9 @@ pub fn update(self: *Core, app: anytype) !bool {
|
|||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
glfw.pollEvents();
|
||||
}
|
||||
|
||||
glfw.getErrorCode() catch |err| switch (err) {
|
||||
error.PlatformError => log.err("glfw: failed to poll events", .{}),
|
||||
|
|
@ -764,6 +774,8 @@ pub fn update(self: *Core, app: anytype) !bool {
|
|||
else => unreachable,
|
||||
};
|
||||
self.input.tick();
|
||||
|
||||
if (!use_app) return !self.appUpdateThreadTick(app);
|
||||
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 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 Core = if (build_options.want_core) @import("Core.zig") else struct {};
|
||||
|
||||
// Mach standard library
|
||||
// gamemode requires libc on linux
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue