This commit is contained in:
parent
1a03f1585f
commit
8c447eac1a
@ -12,6 +12,8 @@ pub const heap = @import("./heap.zig");
|
||||
|
||||
pub const map = @import("./map.zig");
|
||||
|
||||
pub const meta = @import("./meta.zig");
|
||||
|
||||
pub const scalars = @import("./scalars.zig");
|
||||
|
||||
pub const stack = @import("./stack.zig");
|
||||
@ -113,15 +115,26 @@ pub fn KeyValuePair(comptime Key: type, comptime Value: type) type {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn require(function: anytype, args: std.meta.ArgsTuple(@TypeOf(function))) switch (@typeInfo(@typeInfo(@TypeOf(function)).@"fn".return_type.?)) {
|
||||
.error_union => |error_union| error_union.payload,
|
||||
else => @compileError("fn parameter `function` must return an error union"),
|
||||
} {
|
||||
return @call(.auto, function, args) catch |call_error| {
|
||||
if (@errorReturnTrace()) |trace| {
|
||||
std.debug.dumpStackTrace(trace.*);
|
||||
}
|
||||
pub fn expect(function: anytype, args: std.meta.ArgsTuple(@TypeOf(function))) meta.Unwrapped(@typeInfo(@TypeOf(function)).@"fn".return_type.?) {
|
||||
const Returns = @typeInfo(@TypeOf(function)).@"fn".return_type.?;
|
||||
|
||||
std.debug.panic("requirement failed: {s}", .{@errorName(call_error)});
|
||||
return switch (@typeInfo(Returns)) {
|
||||
.error_union => @call(.auto, function, args) catch |call_error| {
|
||||
if (@errorReturnTrace()) |trace| {
|
||||
std.debug.dumpStackTrace(trace.*);
|
||||
}
|
||||
|
||||
std.debug.panic("requirement failed: {s}", .{@errorName(call_error)});
|
||||
},
|
||||
|
||||
.optional => @call(.auto, function, args) orelse {
|
||||
if (@errorReturnTrace()) |trace| {
|
||||
std.debug.dumpStackTrace(trace.*);
|
||||
}
|
||||
|
||||
@panic("requirement failed: null");
|
||||
},
|
||||
|
||||
else => @call(.auto, function, args),
|
||||
};
|
||||
}
|
||||
|
35
src/coral/meta.zig
Normal file
35
src/coral/meta.zig
Normal file
@ -0,0 +1,35 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub fn Unwrapped(comptime Value: type) type {
|
||||
return switch (@typeInfo(Value)) {
|
||||
.error_union => |error_union| error_union.payload,
|
||||
.optional => |optional| optional.child,
|
||||
else => Value,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn isContainer(@"type": std.builtin.Type) bool {
|
||||
return switch (@"type") {
|
||||
.@"struct", .@"union", .@"enum", .@"opaque" => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn hasFn(comptime Type: type, comptime fn_name: []const u8) ?std.builtin.Type.Fn {
|
||||
const @"type" = @typeInfo(Type);
|
||||
|
||||
if (!isContainer(@"type")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!@hasDecl(Type, fn_name)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const Fn = @TypeOf(@field(Type, fn_name));
|
||||
|
||||
return switch (@typeInfo(Fn)) {
|
||||
.@"fn" => |@"fn"| @"fn",
|
||||
else => null,
|
||||
};
|
||||
}
|
@ -6,5 +6,10 @@ pub fn main() void {
|
||||
ona.realtime_app
|
||||
.with(.initModule(ona.hid))
|
||||
.with(.initModule(ona.gfx))
|
||||
.with(.initSystem(.render, .of(render)))
|
||||
.run();
|
||||
}
|
||||
|
||||
fn render(commands: ona.gfx.Commands) void {
|
||||
_ = commands;
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ pub fn hasState(self: *Self, comptime State: type) ?*State {
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn init() std.mem.Allocator.Error!Self {
|
||||
pub fn init() error{OutOfMemory}!Self {
|
||||
var self = Self{
|
||||
.initialized_states = .empty,
|
||||
.named_systems = .empty,
|
||||
@ -80,19 +80,13 @@ pub fn init() std.mem.Allocator.Error!Self {
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn on(self: *Self, comptime schedule: anytype, behavior: *const Behavior) std.mem.Allocator.Error!void {
|
||||
pub fn on(self: *Self, comptime schedule: anytype, behavior: *const Behavior) error{OutOfMemory}!void {
|
||||
const schedule_name = scheduleName(schedule);
|
||||
const systems = self.named_systems.get(schedule_name) orelse (try self.named_systems.insert(coral.heap.allocator, schedule_name, .empty)).?;
|
||||
|
||||
try systems.insert(self, behavior);
|
||||
}
|
||||
|
||||
pub fn putState(self: *Self, state: anytype) std.mem.Allocator.Error!*@TypeOf(state) {
|
||||
try self.setState(state);
|
||||
|
||||
return self.hasState(@TypeOf(state)).?;
|
||||
}
|
||||
|
||||
pub fn run(self: *Self, tasks: *coral.asio.TaskQueue, comptime schedule: anytype) RunError!void {
|
||||
const schedule_name = scheduleName(schedule);
|
||||
const systems = self.named_systems.get(schedule_name) orelse (try self.named_systems.insert(coral.heap.allocator, schedule_name, .empty)).?;
|
||||
@ -100,7 +94,7 @@ pub fn run(self: *Self, tasks: *coral.asio.TaskQueue, comptime schedule: anytype
|
||||
try systems.run(self, tasks);
|
||||
}
|
||||
|
||||
pub fn setState(self: *Self, state: anytype) std.mem.Allocator.Error!void {
|
||||
pub fn setState(self: *Self, state: anytype) error{OutOfMemory}!void {
|
||||
var boxed_state = try coral.Box.init(coral.heap.allocator, state);
|
||||
|
||||
errdefer {
|
||||
|
@ -65,13 +65,17 @@ const Param = struct {
|
||||
@compileError("generic behavior params are not permitted");
|
||||
};
|
||||
|
||||
const init_fn = hasFn(FnParam, "init", null) orelse {
|
||||
const init_fn = coral.meta.hasFn(FnParam, "init") orelse {
|
||||
@compileError(std.fmt.comptimePrint("{s} must contain a .init declaration", .{@typeName(FnParam)}));
|
||||
};
|
||||
|
||||
comptime var uses_bind = false;
|
||||
|
||||
if (hasFn(FnParam, "bind", &.{})) |bind_fn| {
|
||||
if (coral.meta.hasFn(FnParam, "bind")) |bind_fn| {
|
||||
if (bind_fn.params.len != 0) {
|
||||
@compileError(std.fmt.comptimePrint("{s}.bind cannot accept any parameters", .{@typeName(FnParam)}));
|
||||
}
|
||||
|
||||
const State = switch (@typeInfo(bind_fn.return_type.?)) {
|
||||
.error_union => |error_union| error_union.payload,
|
||||
else => bind_fn.return_type.?,
|
||||
@ -90,10 +94,13 @@ const Param = struct {
|
||||
uses_bind = true;
|
||||
}
|
||||
|
||||
if (init_fn.return_type != FnParam) {
|
||||
if (coral.meta.Unwrapped(init_fn.return_type.?) != FnParam) {
|
||||
const param_type_name = @typeName(FnParam);
|
||||
|
||||
@compileError(std.fmt.comptimePrint("Fn {s}.init must return {s}", .{ param_type_name, param_type_name }));
|
||||
@compileError(std.fmt.comptimePrint("Fn {s}.init must return some variation of {s}", .{
|
||||
param_type_name,
|
||||
param_type_name,
|
||||
}));
|
||||
}
|
||||
|
||||
return .{
|
||||
@ -143,54 +150,6 @@ pub fn before(comptime self: *const Self, comptime dependant: *const Self) *cons
|
||||
return &afters.instance;
|
||||
}
|
||||
|
||||
fn hasFn(comptime SystemParam: type, comptime name: []const u8, comptime has_expected_param_types: ?[]const type) ?std.builtin.Type.Fn {
|
||||
switch (@typeInfo(SystemParam)) {
|
||||
.@"struct", .@"opaque" => {},
|
||||
|
||||
else => @compileError(std.fmt.comptimePrint("{s} must be a struct or opaque types", .{
|
||||
@typeName(SystemParam),
|
||||
})),
|
||||
}
|
||||
|
||||
if (@hasDecl(SystemParam, name)) {
|
||||
const named_fn = switch (@typeInfo(@TypeOf(@field(SystemParam, name)))) {
|
||||
.@"fn" => |@"fn"| @"fn",
|
||||
|
||||
else => @compileError(std.fmt.comptimePrint("{s}.{s} declaration must be made an fn type", .{
|
||||
@typeName(SystemParam),
|
||||
name,
|
||||
})),
|
||||
};
|
||||
|
||||
if (has_expected_param_types) |expected_param_types| {
|
||||
if (named_fn.params.len != expected_param_types.len) {
|
||||
@compileError(std.fmt.comptimePrint("{s}.{s} fn must accept {d} parameters, not {d}", .{
|
||||
@typeName(SystemParam),
|
||||
name,
|
||||
expected_param_types.len,
|
||||
named_fn.params.len,
|
||||
}));
|
||||
}
|
||||
|
||||
inline for (named_fn.params, expected_param_types, 1..expected_param_types.len + 1) |fn_param, ExpectedParam, i| {
|
||||
if (fn_param.type != ExpectedParam) {
|
||||
@compileError(std.fmt.comptimePrint("Parameter {d} of {s}.{s} fn must be {s}, not {s}", .{
|
||||
i,
|
||||
@typeName(SystemParam),
|
||||
name,
|
||||
@typeName(ExpectedParam),
|
||||
@typeName(fn_param.type.?),
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return named_fn;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn of(comptime call: anytype) *const Self {
|
||||
const Call = @TypeOf(call);
|
||||
|
||||
@ -255,17 +214,16 @@ pub fn of(comptime call: anytype) *const Self {
|
||||
}
|
||||
}
|
||||
|
||||
call_args[i] = @call(.auto, fn_param.type.?.init, init_args);
|
||||
call_args[i] = coral.expect(fn_param.type.?.init, init_args);
|
||||
}
|
||||
|
||||
switch (@typeInfo(call_fn.return_type.?)) {
|
||||
.void => @call(.auto, call, call_args),
|
||||
.error_union => coral.require(call, call_args),
|
||||
|
||||
else => @compileError(std.fmt.comptimePrint("parameter `call` must be fn (*ona.App) void or fn (*ona.App) !void, not {s}", .{
|
||||
if (coral.meta.Unwrapped(call_fn.return_type.?) != void) {
|
||||
@compileError(std.fmt.comptimePrint("parameter `call` must return some variation of void", .{
|
||||
@typeName(Call),
|
||||
})),
|
||||
}));
|
||||
}
|
||||
|
||||
coral.expect(call, call_args);
|
||||
}
|
||||
|
||||
const instance = Self{
|
||||
|
@ -29,7 +29,7 @@ pub fn init(comptime start: anytype) Self {
|
||||
|
||||
switch (@typeInfo(start_fn.return_type.?)) {
|
||||
.error_union => |error_union| switch (error_union.payload) {
|
||||
void => coral.require(start, .{app}),
|
||||
void => coral.expect(start, .{app}),
|
||||
else => @compileError(error_message),
|
||||
},
|
||||
|
||||
@ -52,12 +52,22 @@ pub fn initModule(comptime namespace: anytype) Self {
|
||||
return init(namespace.setup);
|
||||
}
|
||||
|
||||
pub fn initSystem(comptime schedule: anytype, comptime behavior: *const ona.App.Behavior) Self {
|
||||
const system_initialization = struct {
|
||||
fn apply(app: *ona.App) !void {
|
||||
try app.on(schedule, behavior);
|
||||
}
|
||||
};
|
||||
|
||||
return init(system_initialization.apply);
|
||||
}
|
||||
|
||||
pub fn run(self: Self) void {
|
||||
defer {
|
||||
coral.heap.traceLeaks();
|
||||
}
|
||||
|
||||
var app = coral.require(ona.App.init, .{});
|
||||
var app = coral.expect(ona.App.init, .{});
|
||||
|
||||
defer {
|
||||
app.deinit();
|
||||
|
279
src/ona/gfx.zig
279
src/ona/gfx.zig
@ -1,8 +1,8 @@
|
||||
const builtin = @import("builtin");
|
||||
const Context = @import("./gfx/Context.zig");
|
||||
|
||||
const coral = @import("coral");
|
||||
|
||||
const ext = @cImport({
|
||||
const c = @cImport({
|
||||
@cInclude("SDL3/SDL.h");
|
||||
@cInclude("shaderc/shaderc.h");
|
||||
});
|
||||
@ -13,72 +13,14 @@ const ona = @import("./ona.zig");
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
const Context = struct {
|
||||
window: *ext.SDL_Window,
|
||||
frame_arena: std.heap.ArenaAllocator,
|
||||
gpu_device: *ext.SDL_GPUDevice,
|
||||
shader_compiler: ext.shaderc_compiler_t,
|
||||
spirv_options: ext.shaderc_compile_options_t,
|
||||
pub const Commands = struct {
|
||||
pending: *Context.Queue,
|
||||
|
||||
pub const AssembleError = std.mem.Allocator.Error || error{BadSyntax};
|
||||
|
||||
pub const AssemblyKind = enum(c_int) {
|
||||
vertex,
|
||||
fragment,
|
||||
};
|
||||
|
||||
pub fn assembleShader(self: *Context, kind: AssemblyKind, name: [*:0]const u8, source: []const u8, injections: anytype) AssembleError![]const u8 {
|
||||
const frame_allocator = self.frame_arena.allocator();
|
||||
const injected_source = try glsl.inject(frame_allocator, source, 430, injections);
|
||||
|
||||
const result = ext.shaderc_compile_into_spv(self.shader_compiler, injected_source.ptr, injected_source.len, switch (kind) {
|
||||
.fragment => ext.shaderc_glsl_fragment_shader,
|
||||
.vertex => ext.shaderc_glsl_vertex_shader,
|
||||
}, name, "main", self.spirv_options) orelse {
|
||||
return error.OutOfMemory;
|
||||
};
|
||||
|
||||
defer {
|
||||
ext.shaderc_result_release(result);
|
||||
}
|
||||
|
||||
return switch (ext.shaderc_result_get_compilation_status(result)) {
|
||||
ext.shaderc_compilation_status_success => {
|
||||
const compiled_len = ext.shaderc_result_get_length(result);
|
||||
const compiled_ptr = ext.shaderc_result_get_bytes(result);
|
||||
|
||||
return frame_allocator.dupe(u8, compiled_ptr[0..compiled_len]);
|
||||
},
|
||||
|
||||
ext.shaderc_compilation_status_compilation_error, ext.shaderc_compilation_status_invalid_stage => {
|
||||
std.log.err("{s}", .{ext.shaderc_result_get_error_message(result)});
|
||||
std.log.debug("problematic shader:\n{s}", .{injected_source});
|
||||
|
||||
return error.BadSyntax;
|
||||
},
|
||||
|
||||
ext.shaderc_compilation_status_internal_error => @panic("shaderc internal compiler error"),
|
||||
else => unreachable,
|
||||
pub fn init(context: *const Context) !Commands {
|
||||
return .{
|
||||
.pending = try context.shared.acquireQueue(),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn assembleShaderEmbedded(self: *Context, kind: AssemblyKind, comptime path: [:0]const u8, injections: anytype) AssembleError![]const u8 {
|
||||
return self.assembleShader(kind, path, @embedFile(path), injections);
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Context) void {
|
||||
ext.shaderc_compile_options_release(self.spirv_options);
|
||||
ext.shaderc_compiler_release(self.shader_compiler);
|
||||
ext.SDL_DestroyGPUDevice(self.gpu_device);
|
||||
ext.SDL_DestroyWindow(self.window);
|
||||
self.frame_arena.deinit();
|
||||
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub const traits = ona.Traits{
|
||||
.is_thread_unsafe = true,
|
||||
};
|
||||
};
|
||||
|
||||
pub const Display = struct {
|
||||
@ -87,209 +29,42 @@ pub const Display = struct {
|
||||
is_hidden: bool,
|
||||
};
|
||||
|
||||
pub const Effect = struct {
|
||||
pipeline: *ext.SDL_GPUGraphicsPipeline,
|
||||
};
|
||||
|
||||
fn compile_shaders(context: ona.Exclusive(Context), _: ona.Assets(Effect)) !void {
|
||||
const Camera = extern struct {
|
||||
projection: [4]@Vector(4, f32),
|
||||
};
|
||||
|
||||
const vertex_spirv = try context.ptr.assembleShaderEmbedded(.vertex, "./gfx/canvas.vert", .{
|
||||
.model_xy = glsl.nativeInput(0, @Vector(2, f32)),
|
||||
.model_uv = glsl.nativeInput(1, @Vector(2, f32)),
|
||||
.model_rgba = glsl.nativeInput(2, @Vector(4, f32)),
|
||||
.instance_uv_offset = glsl.nativeInput(3, @Vector(2, f32)),
|
||||
.instance_uv_scale = glsl.nativeInput(4, @Vector(2, f32)),
|
||||
.instance_xbasis = glsl.nativeInput(5, @Vector(2, f32)),
|
||||
.instance_ybasis = glsl.nativeInput(6, @Vector(2, f32)),
|
||||
.instance_origin = glsl.nativeInput(7, @Vector(2, f32)),
|
||||
.instance_pivot = glsl.nativeInput(8, @Vector(2, f32)),
|
||||
.instance_rgb = glsl.nativeInput(9, @Vector(3, f32)),
|
||||
.instance_bits = glsl.nativeInput(10, u32),
|
||||
.uv = glsl.nativeOutput(0, @Vector(2, f32)),
|
||||
.rgba = glsl.nativeOutput(1, @Vector(4, f32)),
|
||||
.camera = glsl.nativeUniformBlock(0, Camera),
|
||||
});
|
||||
|
||||
const fragment_spirv = try context.ptr.assembleShaderEmbedded(.fragment, "./gfx/canvas.frag", .{
|
||||
.vertex_uv = glsl.nativeInput(0, @Vector(2, f32)),
|
||||
.vertex_rgba = glsl.nativeInput(1, @Vector(4, f32)),
|
||||
.source_texture = glsl.Sampler{ .sampler_2d = 0 },
|
||||
.color = glsl.nativeOutput(0, @Vector(4, f32)),
|
||||
.camera = glsl.nativeUniformBlock(0, Camera),
|
||||
});
|
||||
|
||||
const effect_vertex = ext.SDL_CreateGPUShader(context.ptr.gpu_device, &.{
|
||||
.code = vertex_spirv.ptr,
|
||||
.code_size = vertex_spirv.len,
|
||||
.format = ext.SDL_GPU_SHADERFORMAT_SPIRV,
|
||||
.stage = ext.SDL_GPU_SHADERSTAGE_VERTEX,
|
||||
.entrypoint = "main",
|
||||
});
|
||||
|
||||
defer {
|
||||
ext.SDL_ReleaseGPUShader(context.ptr.gpu_device, effect_vertex);
|
||||
}
|
||||
|
||||
const effect_fragment = ext.SDL_CreateGPUShader(context.ptr.gpu_device, &.{
|
||||
.code = fragment_spirv.ptr,
|
||||
.code_size = fragment_spirv.len,
|
||||
.format = ext.SDL_GPU_SHADERFORMAT_SPIRV,
|
||||
.stage = ext.SDL_GPU_SHADERSTAGE_FRAGMENT,
|
||||
.entrypoint = "main",
|
||||
});
|
||||
|
||||
defer {
|
||||
ext.SDL_ReleaseGPUShader(context.ptr.gpu_device, effect_fragment);
|
||||
}
|
||||
|
||||
// _ = try effects.insert(.{
|
||||
// .pipeline = ext.SDL_CreateGPUGraphicsPipeline(context.ptr.gpu_device, &.{
|
||||
// .vertex_shader = effect_vertex,
|
||||
// .fragment_shader = effect_fragment,
|
||||
// .primitive_type = ext.SDL_GPU_PRIMITIVETYPE_TRIANGLELIST,
|
||||
// }).?,
|
||||
// });
|
||||
}
|
||||
|
||||
pub fn prepare(display: ona.Write(Display)) void {
|
||||
display.ptr.is_hidden = false;
|
||||
}
|
||||
|
||||
pub fn render(context: ona.Exclusive(Context)) !void {
|
||||
const commands = ext.SDL_AcquireGPUCommandBuffer(context.ptr.gpu_device) orelse {
|
||||
return error.SdlFailure;
|
||||
};
|
||||
|
||||
errdefer {
|
||||
_ = !ext.SDL_CancelGPUCommandBuffer(commands);
|
||||
}
|
||||
|
||||
var has_swapchain_texture: ?*ext.SDL_GPUTexture = null;
|
||||
var sawpchain_width, var swapchain_height = [2]u32{ 0, 0 };
|
||||
|
||||
if (!ext.SDL_WaitAndAcquireGPUSwapchainTexture(
|
||||
commands,
|
||||
context.ptr.window,
|
||||
&has_swapchain_texture,
|
||||
&sawpchain_width,
|
||||
&swapchain_height,
|
||||
)) {
|
||||
return error.SdlFailure;
|
||||
}
|
||||
|
||||
if (has_swapchain_texture) |swapchain_texture| {
|
||||
const color_targets = [_]ext.SDL_GPUColorTargetInfo{
|
||||
.{
|
||||
.texture = swapchain_texture,
|
||||
.load_op = ext.SDL_GPU_LOADOP_CLEAR,
|
||||
.clear_color = .{ .r = 0, .g = 0.2, .b = 0.4, .a = 1.0 },
|
||||
},
|
||||
};
|
||||
|
||||
const render_pass = ext.SDL_BeginGPURenderPass(commands, &color_targets, color_targets.len, null) orelse {
|
||||
return error.SdlFailure;
|
||||
};
|
||||
|
||||
defer {
|
||||
ext.SDL_EndGPURenderPass(render_pass);
|
||||
}
|
||||
}
|
||||
|
||||
if (!ext.SDL_SubmitGPUCommandBuffer(commands)) {
|
||||
return;
|
||||
}
|
||||
pub fn queueSynchronize(context: ona.Exclusive(Context), display: ona.Read(Display)) !void {
|
||||
context.ptr.shared.swapBuffers(display.ptr.*);
|
||||
}
|
||||
|
||||
pub fn setup(app: *ona.App) !void {
|
||||
if (!ext.SDL_Init(ext.SDL_INIT_VIDEO)) {
|
||||
if (!c.SDL_Init(c.SDL_INIT_VIDEO)) {
|
||||
return error.SystemFailure;
|
||||
}
|
||||
|
||||
const display = try app.putState(Display{
|
||||
const default_display = Display{
|
||||
.width = 1280,
|
||||
.height = 720,
|
||||
.is_hidden = false,
|
||||
});
|
||||
|
||||
const window = ext.SDL_CreateWindow("Ona", display.width, display.height, ext.SDL_WINDOW_HIDDEN) orelse {
|
||||
return error.SdlFailure;
|
||||
};
|
||||
|
||||
errdefer {
|
||||
ext.SDL_DestroyWindow(window);
|
||||
}
|
||||
try app.setState(default_display);
|
||||
|
||||
const gpu_device = ext.SDL_CreateGPUDevice(ext.SDL_GPU_SHADERFORMAT_SPIRV, builtin.mode == .Debug, null) orelse {
|
||||
return error.SdlFailure;
|
||||
};
|
||||
// var shader_compiler = try glsl.Assembly.init(.spirv, switch (builtin.mode) {
|
||||
// .Debug, .ReleaseSafe => .unoptimized,
|
||||
// .ReleaseSmall => .optimize_size,
|
||||
// .ReleaseFast => .optimize_speed,
|
||||
// });
|
||||
|
||||
errdefer {
|
||||
ext.SDL_DestroyGPUDevice(gpu_device);
|
||||
}
|
||||
// errdefer {
|
||||
// shader_compiler.deinit();
|
||||
// }
|
||||
|
||||
if (!ext.SDL_ClaimWindowForGPUDevice(gpu_device, window)) {
|
||||
return error.SdlFailure;
|
||||
}
|
||||
{
|
||||
var context = try Context.init(default_display);
|
||||
|
||||
const shader_compiler = ext.shaderc_compiler_initialize() orelse {
|
||||
return error.OutOfMemory;
|
||||
};
|
||||
|
||||
errdefer {
|
||||
ext.shaderc_compiler_release(shader_compiler);
|
||||
}
|
||||
|
||||
const spirv_options = ext.shaderc_compile_options_initialize() orelse {
|
||||
return error.OutOfMemory;
|
||||
};
|
||||
|
||||
errdefer {
|
||||
ext.shaderc_compile_options_release(spirv_options);
|
||||
}
|
||||
|
||||
ext.shaderc_compile_options_set_target_env(spirv_options, ext.shaderc_target_env_vulkan, ext.shaderc_env_version_vulkan_1_1);
|
||||
|
||||
ext.shaderc_compile_options_set_optimization_level(spirv_options, switch (builtin.mode) {
|
||||
.Debug, .ReleaseSafe => ext.shaderc_optimization_level_zero,
|
||||
.ReleaseSmall => ext.shaderc_optimization_level_size,
|
||||
.ReleaseFast => ext.shaderc_optimization_level_performance,
|
||||
});
|
||||
|
||||
try ona.registerAsset(app, Effect);
|
||||
|
||||
try app.setState(Context{
|
||||
.frame_arena = .init(coral.heap.allocator),
|
||||
.shader_compiler = shader_compiler,
|
||||
.spirv_options = spirv_options,
|
||||
.window = window,
|
||||
.gpu_device = gpu_device,
|
||||
});
|
||||
|
||||
try app.on(.load, .of(prepare));
|
||||
try app.on(.load, .of(compile_shaders));
|
||||
try app.on(.render, .of(render));
|
||||
try app.on(.finish, .of(synchronize));
|
||||
}
|
||||
|
||||
pub fn synchronize(context: ona.Exclusive(Context), display: ona.Read(Display)) !void {
|
||||
const window_flags = ext.SDL_GetWindowFlags(context.ptr.window);
|
||||
const is_window_hidden = (window_flags & ext.SDL_WINDOW_HIDDEN) != 0;
|
||||
|
||||
if (is_window_hidden != display.ptr.is_hidden) {
|
||||
const was_window_changed = switch (display.ptr.is_hidden) {
|
||||
true => ext.SDL_HideWindow(context.ptr.window),
|
||||
false => ext.SDL_ShowWindow(context.ptr.window),
|
||||
};
|
||||
|
||||
if (!was_window_changed) {
|
||||
std.log.warn("failed to change window visibility", .{});
|
||||
errdefer {
|
||||
context.deinit();
|
||||
}
|
||||
|
||||
try app.setState(context);
|
||||
}
|
||||
|
||||
if (!context.ptr.frame_arena.reset(.{ .retain_with_limit = 1024 * 1024 })) {
|
||||
std.log.warn("failed to shrink frame arena during reset, freeing instead...", .{});
|
||||
}
|
||||
try app.on(.render, .of(queueSynchronize));
|
||||
}
|
||||
|
250
src/ona/gfx/Context.zig
Normal file
250
src/ona/gfx/Context.zig
Normal file
@ -0,0 +1,250 @@
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const c = @cImport({
|
||||
@cInclude("SDL3/SDL.h");
|
||||
});
|
||||
|
||||
const coral = @import("coral");
|
||||
|
||||
const ona = @import("../ona.zig");
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
shared: *Shared,
|
||||
thread: std.Thread,
|
||||
|
||||
pub const Command = union(enum) {};
|
||||
|
||||
pub const Queue = struct {
|
||||
commands: coral.stack.Sequential(Command) = .empty,
|
||||
has_next: ?*Queue = null,
|
||||
|
||||
pub fn cancel(self: *Queue) void {
|
||||
self.commands.clear();
|
||||
}
|
||||
|
||||
fn deinit(self: *Queue) void {
|
||||
self.commands.deinit(coral.heap.allocator);
|
||||
}
|
||||
};
|
||||
|
||||
const Self = @This();
|
||||
|
||||
const Shared = struct {
|
||||
device: *c.SDL_GPUDevice,
|
||||
display: ona.gfx.Display,
|
||||
frame_started: std.Thread.ResetEvent = .{},
|
||||
frame_finished: std.Thread.ResetEvent = .{},
|
||||
has_pooled_queue: std.atomic.Value(?*Queue) = .init(null),
|
||||
swap_buffers: [2]Buffer = .{ .{}, .{} },
|
||||
is_swapped: bool = false,
|
||||
is_running: bool = true,
|
||||
|
||||
const Buffer = struct {
|
||||
has_head_queue: ?*Queue = null,
|
||||
has_tail_queue: ?*Queue = null,
|
||||
|
||||
pub fn dequeue(self: *Buffer) ?*Queue {
|
||||
if (self.has_head_queue) |head| {
|
||||
self.has_head_queue = head.has_next;
|
||||
head.has_next = null;
|
||||
|
||||
if (self.has_head_queue == null) {
|
||||
self.has_tail_queue = null;
|
||||
}
|
||||
|
||||
return head;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn enqueue(self: *Buffer, queue: *Queue) void {
|
||||
if (self.has_tail_queue) |tail| {
|
||||
std.debug.assert(self.has_head_queue != null);
|
||||
|
||||
tail.has_next = queue;
|
||||
self.has_tail_queue = queue;
|
||||
} else {
|
||||
self.has_head_queue = queue;
|
||||
self.has_tail_queue = queue;
|
||||
}
|
||||
|
||||
queue.has_next = null;
|
||||
}
|
||||
};
|
||||
|
||||
pub fn acquireQueue(self: *Shared) error{OutOfMemory}!*Queue {
|
||||
const queue = fetch: {
|
||||
if (self.has_pooled_queue.load(.acquire)) |queue| {
|
||||
self.has_pooled_queue.store(queue.has_next, .release);
|
||||
|
||||
break :fetch queue;
|
||||
}
|
||||
|
||||
const queue = try coral.heap.allocator.create(Queue);
|
||||
|
||||
queue.* = .{};
|
||||
|
||||
break :fetch queue;
|
||||
};
|
||||
|
||||
self.swap_buffers[@intFromBool(self.is_swapped)].enqueue(queue);
|
||||
|
||||
return queue;
|
||||
}
|
||||
|
||||
fn deinit(self: *Shared, thread: std.Thread) void {
|
||||
self.frame_finished.wait();
|
||||
self.frame_finished.reset();
|
||||
|
||||
self.is_running = false;
|
||||
|
||||
self.frame_started.set();
|
||||
thread.join();
|
||||
c.SDL_DestroyGPUDevice(self.device);
|
||||
|
||||
while (self.has_pooled_queue.load(.acquire)) |queue| {
|
||||
self.has_pooled_queue.store(queue.has_next, .release);
|
||||
coral.heap.allocator.destroy(queue);
|
||||
}
|
||||
|
||||
for (&self.swap_buffers) |*buffer| {
|
||||
while (buffer.dequeue()) |queue| {
|
||||
queue.deinit();
|
||||
coral.heap.allocator.destroy(queue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run(self: *Shared) !void {
|
||||
const window = c.SDL_CreateWindow("Ona", 1280, 720, c.SDL_WINDOW_HIDDEN) orelse {
|
||||
return error.SdlFailure;
|
||||
};
|
||||
|
||||
defer {
|
||||
c.SDL_DestroyWindow(window);
|
||||
}
|
||||
|
||||
if (!c.SDL_ClaimWindowForGPUDevice(self.device, window)) {
|
||||
return error.SdlFailure;
|
||||
}
|
||||
|
||||
while (self.is_running) {
|
||||
const window_flags = c.SDL_GetWindowFlags(window);
|
||||
const is_window_hidden = (window_flags & c.SDL_WINDOW_HIDDEN) != 0;
|
||||
|
||||
if (is_window_hidden != self.display.is_hidden) {
|
||||
const was_window_changed = switch (self.display.is_hidden) {
|
||||
true => c.SDL_HideWindow(window),
|
||||
false => c.SDL_ShowWindow(window),
|
||||
};
|
||||
|
||||
if (!was_window_changed) {
|
||||
std.log.warn("failed to change window visibility", .{});
|
||||
}
|
||||
}
|
||||
|
||||
const commands = c.SDL_AcquireGPUCommandBuffer(self.device) orelse {
|
||||
return error.SdlFailure;
|
||||
};
|
||||
|
||||
errdefer {
|
||||
_ = c.SDL_CancelGPUCommandBuffer(commands);
|
||||
}
|
||||
|
||||
var has_swapchain_texture: ?*c.SDL_GPUTexture = null;
|
||||
var sawpchain_width, var swapchain_height = [2]u32{ 0, 0 };
|
||||
|
||||
if (!c.SDL_WaitAndAcquireGPUSwapchainTexture(
|
||||
commands,
|
||||
window,
|
||||
&has_swapchain_texture,
|
||||
&sawpchain_width,
|
||||
&swapchain_height,
|
||||
)) {
|
||||
return error.SdlFailure;
|
||||
}
|
||||
|
||||
if (has_swapchain_texture) |swapchain_texture| {
|
||||
const color_targets = [_]c.SDL_GPUColorTargetInfo{
|
||||
.{
|
||||
.texture = swapchain_texture,
|
||||
.load_op = c.SDL_GPU_LOADOP_DONT_CARE,
|
||||
},
|
||||
};
|
||||
|
||||
const render_pass = c.SDL_BeginGPURenderPass(commands, &color_targets, color_targets.len, null) orelse {
|
||||
return error.SdlFailure;
|
||||
};
|
||||
|
||||
defer {
|
||||
c.SDL_EndGPURenderPass(render_pass);
|
||||
}
|
||||
|
||||
while (self.swap_buffers[@intFromBool(!self.is_swapped)].dequeue()) |queue| {
|
||||
defer {
|
||||
queue.cancel();
|
||||
|
||||
queue.has_next = self.has_pooled_queue.load(.acquire);
|
||||
|
||||
self.has_pooled_queue.store(queue, .release);
|
||||
}
|
||||
|
||||
for (queue.commands.items.slice()) |_| {}
|
||||
}
|
||||
}
|
||||
|
||||
_ = c.SDL_SubmitGPUCommandBuffer(commands);
|
||||
|
||||
self.frame_finished.set();
|
||||
self.frame_started.wait();
|
||||
self.frame_started.reset();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn swapBuffers(self: *Shared, display: ona.gfx.Display) void {
|
||||
self.frame_finished.wait();
|
||||
self.frame_finished.reset();
|
||||
|
||||
self.display = display;
|
||||
self.is_swapped = !self.is_swapped;
|
||||
|
||||
self.frame_started.set();
|
||||
}
|
||||
};
|
||||
|
||||
pub fn init(display: ona.gfx.Display) !Self {
|
||||
const device = c.SDL_CreateGPUDevice(c.SDL_GPU_SHADERFORMAT_SPIRV, builtin.mode == .Debug, null) orelse {
|
||||
return error.SdlFailure;
|
||||
};
|
||||
|
||||
errdefer {
|
||||
c.SDL_DestroyGPUDevice(device);
|
||||
}
|
||||
|
||||
const shared = try coral.heap.allocator.create(Shared);
|
||||
|
||||
errdefer {
|
||||
coral.heap.allocator.destroy(shared);
|
||||
}
|
||||
|
||||
shared.* = .{
|
||||
.device = device,
|
||||
.display = display,
|
||||
};
|
||||
|
||||
shared.frame_started.set();
|
||||
|
||||
return .{
|
||||
.thread = try .spawn(.{}, Shared.run, .{shared}),
|
||||
.shared = shared,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
self.shared.deinit(self.thread);
|
||||
coral.heap.allocator.destroy(self.shared);
|
||||
|
||||
self.* = undefined;
|
||||
}
|
3
src/ona/gfx/composite.frag
Normal file
3
src/ona/gfx/composite.frag
Normal file
@ -0,0 +1,3 @@
|
||||
void main() {
|
||||
color = texture(texture, texCoord);
|
||||
}
|
25
src/ona/gfx/composite.vert
Normal file
25
src/ona/gfx/composite.vert
Normal file
@ -0,0 +1,25 @@
|
||||
|
||||
const vec2 quadVertices[6] = vec2[](
|
||||
vec2(-1.0, 1.0),
|
||||
vec2(-1.0, -1.0),
|
||||
vec2( 1.0, -1.0),
|
||||
|
||||
vec2(-1.0, 1.0),
|
||||
vec2( 1.0, -1.0),
|
||||
vec2( 1.0, 1.0)
|
||||
);
|
||||
|
||||
const vec2 quadTexCoords[6] = vec2[](
|
||||
vec2(0.0, 1.0),
|
||||
vec2(0.0, 0.0),
|
||||
vec2(1.0, 0.0),
|
||||
|
||||
vec2(0.0, 1.0),
|
||||
vec2(1.0, 0.0),
|
||||
vec2(1.0, 1.0)
|
||||
);
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(quadVertices[gl_VertexID], 0.0, 1.0);
|
||||
texCoord = quadTexCoords[gl_VertexID];
|
||||
}
|
@ -1,9 +1,140 @@
|
||||
const c = @cImport({
|
||||
@cInclude("shaderc/shaderc.h");
|
||||
});
|
||||
|
||||
const coral = @import("coral");
|
||||
|
||||
const ona = @import("../ona.zig");
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
///
|
||||
/// State machine for compiling GLSL shader source code into targeted executable formats.
|
||||
///
|
||||
pub const Assembly = struct {
|
||||
compiler: c.shaderc_compiler_t,
|
||||
options: c.shaderc_compile_options_t,
|
||||
|
||||
///
|
||||
/// Optimization preference.
|
||||
///
|
||||
pub const Optimizations = enum {
|
||||
unoptimized,
|
||||
optimize_speed,
|
||||
optimize_size,
|
||||
};
|
||||
|
||||
///
|
||||
/// Target format used for shader binaries.
|
||||
///
|
||||
pub const Target = enum {
|
||||
spirv,
|
||||
};
|
||||
|
||||
///
|
||||
/// Assembles and allocates the Ona glsl-encoded `source` into a shader binary using `allocator` as the buffer
|
||||
/// allocation strategy.
|
||||
///
|
||||
/// `stage` determines how the compiler attempts to interpret and compile the GLSL, with different stages requiring
|
||||
/// different source code.
|
||||
///
|
||||
/// Failing to compile `source` will result in a errors being logged with what went wrong. It is advised that
|
||||
/// `name` be supplied a meaningful name to disambiguate which shader the error came from at runtime. In debug
|
||||
/// builds, a full dump of the problematic shader with the injected code is logged after any error messages.
|
||||
///
|
||||
/// Syntax errors will result in `error.BadSyntax` being returned instead.
|
||||
///
|
||||
/// **Note** the shader assumes the entry-point of the GLSL program matches the signature `void main()`
|
||||
///
|
||||
pub fn assemble(
|
||||
self: Assembly,
|
||||
allocator: std.mem.Allocator,
|
||||
stage: Stage,
|
||||
name: [*:0]const u8,
|
||||
source: []const u8,
|
||||
) (std.mem.Allocator.Error || error{BadSyntax})![]const u8 {
|
||||
const result = c.shaderc_compile_into_spv(self.shader_compiler, source.ptr, source.len, switch (stage) {
|
||||
.fragment => c.shaderc_glsl_fragment_shader,
|
||||
.vertex => c.shaderc_glsl_vertex_shader,
|
||||
}, name, "main", self.spirv_options) orelse {
|
||||
return error.OutOfMemory;
|
||||
};
|
||||
|
||||
defer {
|
||||
c.shaderc_result_release(result);
|
||||
}
|
||||
|
||||
return switch (c.shaderc_result_get_compilation_status(result)) {
|
||||
c.shaderc_compilation_status_success => {
|
||||
const compiled_len = c.shaderc_result_get_length(result);
|
||||
const compiled_ptr = c.shaderc_result_get_bytes(result);
|
||||
|
||||
return allocator.dupe(u8, compiled_ptr[0..compiled_len]);
|
||||
},
|
||||
|
||||
c.shaderc_compilation_status_compilation_error, c.shaderc_compilation_status_invalid_stage => {
|
||||
std.log.err("{s}", .{c.shaderc_result_get_error_message(result)});
|
||||
std.log.debug("problematic shader:\n{s}", .{source});
|
||||
|
||||
return error.BadSyntax;
|
||||
},
|
||||
|
||||
c.shaderc_compilation_status_internal_error => @panic("shaderc internal compiler error"),
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Assembly) void {
|
||||
c.shaderc_compile_options_release(self.spirv_options);
|
||||
c.shaderc_compiler_release(self.shader_compiler);
|
||||
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns an `Assembly` with `target` as the desired compilation format and `optimization` as the desired
|
||||
/// optimization level.
|
||||
///
|
||||
/// Should not be enough memory be available, `error.OutOfMemory` is returned instead.
|
||||
///
|
||||
/// **Note** optimizations are not guaranteed and will also depend on the `target` specified.
|
||||
///
|
||||
pub fn init(target: Target, optimizations: Optimizations) error{OutOfMemory}!Assembly {
|
||||
const compiler = c.shaderc_compiler_initialize() orelse {
|
||||
return error.OutOfMemory;
|
||||
};
|
||||
|
||||
errdefer {
|
||||
c.shaderc_compiler_release(compiler);
|
||||
}
|
||||
|
||||
const options = c.shaderc_compile_options_initialize() orelse {
|
||||
return error.OutOfMemory;
|
||||
};
|
||||
|
||||
errdefer {
|
||||
c.shaderc_compile_options_release(options);
|
||||
}
|
||||
|
||||
switch (target) {
|
||||
.spirv => {
|
||||
c.shaderc_compile_options_set_target_env(options, c.shaderc_target_env_vulkan, c.shaderc_env_version_vulkan_1_1);
|
||||
},
|
||||
}
|
||||
|
||||
c.shaderc_compile_options_set_optimization_level(options, switch (optimizations) {
|
||||
.unoptimized => c.shaderc_optimization_level_zero,
|
||||
.optimize_size => c.shaderc_optimization_level_size,
|
||||
.optimize_speed => c.shaderc_optimization_level_performance,
|
||||
});
|
||||
|
||||
return .{
|
||||
.compiler = compiler,
|
||||
.options = options,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const Field = struct {
|
||||
name: [:0]const u8,
|
||||
primitive: Primitive,
|
||||
@ -81,6 +212,11 @@ pub const Sampler = union(enum) {
|
||||
}
|
||||
};
|
||||
|
||||
pub const Stage = enum {
|
||||
vertex,
|
||||
fragment,
|
||||
};
|
||||
|
||||
pub const UniformBlock = struct {
|
||||
name: [:0]const u8,
|
||||
binding: usize,
|
||||
|
@ -1,4 +1,4 @@
|
||||
const ext = @cImport({
|
||||
const c = @cImport({
|
||||
@cInclude("SDL3/SDL.h");
|
||||
});
|
||||
|
||||
@ -201,22 +201,22 @@ pub const KeyScancode = enum(u32) {
|
||||
};
|
||||
|
||||
pub fn poll(exit: ona.Send(ona.App.Exit), hid_events: ona.Send(ona.hid.Event)) !void {
|
||||
var event: ext.SDL_Event = undefined;
|
||||
var event: c.SDL_Event = undefined;
|
||||
|
||||
while (ext.SDL_PollEvent(&event)) {
|
||||
while (c.SDL_PollEvent(&event)) {
|
||||
try hid_events.push(switch (event.type) {
|
||||
ext.SDL_EVENT_QUIT => {
|
||||
c.SDL_EVENT_QUIT => {
|
||||
return exit.push(.success);
|
||||
},
|
||||
|
||||
ext.SDL_EVENT_KEY_UP => .{ .key_up = @enumFromInt(event.key.scancode) },
|
||||
ext.SDL_EVENT_KEY_DOWN => .{ .key_down = @enumFromInt(event.key.scancode) },
|
||||
c.SDL_EVENT_KEY_UP => .{ .key_up = @enumFromInt(event.key.scancode) },
|
||||
c.SDL_EVENT_KEY_DOWN => .{ .key_down = @enumFromInt(event.key.scancode) },
|
||||
|
||||
ext.SDL_EVENT_MOUSE_BUTTON_UP => .{
|
||||
c.SDL_EVENT_MOUSE_BUTTON_UP => .{
|
||||
.mouse_up = switch (event.button.button) {
|
||||
ext.SDL_BUTTON_LEFT => .left,
|
||||
ext.SDL_BUTTON_RIGHT => .right,
|
||||
ext.SDL_BUTTON_MIDDLE => .middle,
|
||||
c.SDL_BUTTON_LEFT => .left,
|
||||
c.SDL_BUTTON_RIGHT => .right,
|
||||
c.SDL_BUTTON_MIDDLE => .middle,
|
||||
|
||||
else => {
|
||||
continue;
|
||||
@ -224,11 +224,11 @@ pub fn poll(exit: ona.Send(ona.App.Exit), hid_events: ona.Send(ona.hid.Event)) !
|
||||
},
|
||||
},
|
||||
|
||||
ext.SDL_EVENT_MOUSE_BUTTON_DOWN => .{
|
||||
c.SDL_EVENT_MOUSE_BUTTON_DOWN => .{
|
||||
.mouse_down = switch (event.button.button) {
|
||||
ext.SDL_BUTTON_LEFT => .left,
|
||||
ext.SDL_BUTTON_RIGHT => .right,
|
||||
ext.SDL_BUTTON_MIDDLE => .middle,
|
||||
c.SDL_BUTTON_LEFT => .left,
|
||||
c.SDL_BUTTON_RIGHT => .right,
|
||||
c.SDL_BUTTON_MIDDLE => .middle,
|
||||
|
||||
else => {
|
||||
continue;
|
||||
@ -236,7 +236,7 @@ pub fn poll(exit: ona.Send(ona.App.Exit), hid_events: ona.Send(ona.hid.Event)) !
|
||||
},
|
||||
},
|
||||
|
||||
ext.SDL_EVENT_MOUSE_MOTION => .{
|
||||
c.SDL_EVENT_MOUSE_MOTION => .{
|
||||
.mouse_motion = .{
|
||||
.relative_position = .{ event.motion.xrel, event.motion.yrel },
|
||||
.absolute_position = .{ event.motion.x, event.motion.y },
|
||||
@ -251,6 +251,10 @@ pub fn poll(exit: ona.Send(ona.App.Exit), hid_events: ona.Send(ona.hid.Event)) !
|
||||
}
|
||||
|
||||
pub fn setup(app: *ona.App) !void {
|
||||
if (!c.SDL_Init(c.SDL_INIT_EVENTS)) {
|
||||
return error.SystemFailure;
|
||||
}
|
||||
|
||||
try app.on(.post_update, .of(poll));
|
||||
try ona.registerChannel(app, Event);
|
||||
}
|
||||
|
@ -296,7 +296,6 @@ fn run_realtime_loop(app: *App) !void {
|
||||
|
||||
try app.run(&tasks, .post_update);
|
||||
try app.run(&tasks, .render);
|
||||
try app.run(&tasks, .finish);
|
||||
|
||||
const exit_messages = exit_channel.messages();
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user