diff --git a/src/coral/coral.zig b/src/coral/coral.zig index 777aca3..e7ce11c 100644 --- a/src/coral/coral.zig +++ b/src/coral/coral.zig @@ -10,6 +10,8 @@ pub const heap = @import("./heap.zig"); pub const io = @import("./io.zig"); +pub const lina = @import("./lina.zig"); + pub const map = @import("./map.zig"); pub const scalars = @import("./scalars.zig"); @@ -22,8 +24,6 @@ const std = @import("std"); pub const utf8 = @import("./utf8.zig"); -pub const vectors = @import("./vectors.zig"); - pub fn Pool(comptime Value: type) type { return struct { entries: stack.Sequential(Entry), diff --git a/src/coral/vectors.zig b/src/coral/lina.zig similarity index 66% rename from src/coral/vectors.zig rename to src/coral/lina.zig index 8e265f2..0748791 100644 --- a/src/coral/vectors.zig +++ b/src/coral/lina.zig @@ -1,5 +1,18 @@ const std = @import("std"); +pub fn Matrix(comptime n: usize, comptime Element: type) type { + return [n]@Vector(n, Element); +} + +pub const ProjectionMatrix = Matrix(4, f32); + +pub const Rect = struct { + left: f32, + top: f32, + right: f32, + bottom: f32, +}; + pub fn cross(v1: anytype, v2: anytype) @typeInfo(@TypeOf(v1, v2)).Vector.child { const multipled = v1 * v2; const vector_info = @typeInfo(@TypeOf(v1)).Vector; @@ -48,3 +61,16 @@ pub fn normal(v: anytype) @TypeOf(v) { return v; } + +pub fn orthographic_projection(near: f32, far: f32, viewport: Rect) Matrix(4, f32) { + const width = viewport.right - viewport.left; + const height = viewport.bottom - viewport.top; + + return .{ + .{2 / width, 0, 0, 0}, + .{0, 2 / height, 0, 0}, + .{0, 0, 1 / (far - near), 0}, + .{-((viewport.left + viewport.right) / width), -((viewport.top + viewport.bottom) / height), near / (near - far), 1}, + }; +} + diff --git a/src/flow/World.zig b/src/flow/World.zig index e0d3650..71db4e9 100644 --- a/src/flow/World.zig +++ b/src/flow/World.zig @@ -1,5 +1,7 @@ const builtin = @import("builtin"); +const flow = @import("./flow.zig"); + const coral = @import("coral"); const states = @import("./states.zig"); @@ -19,7 +21,7 @@ const Self = @This(); pub fn create_event(self: *Self, label: []const u8) std.mem.Allocator.Error!Event { var systems = try system.Schedule.init(label); - errdefer systems.deinit(); + errdefer systems.deinit(self); const index = self.event_systems.len(); @@ -34,7 +36,7 @@ pub fn deinit(self: *Self) void { } for (self.event_systems.values) |*schedule| { - schedule.deinit(); + schedule.deinit(self); } if (self.thread_pool) |thread_pool| { @@ -47,11 +49,19 @@ pub fn deinit(self: *Self) void { self.* = undefined; } -pub fn get_resource(self: Self, comptime State: type) ?*State { - return @ptrCast(@alignCast(self.thread_restricted_resources[@intFromEnum(states.thread_restriction(State))].get(State))); +pub fn get_params(self: Self, comptime Value: type) flow.Params(Value) { + const params = self.get_state(flow.Params(Value)) orelse { + return .{}; + }; + + return params.*; } -pub fn set_get_resource(self: *Self, value: anytype) std.mem.Allocator.Error!*@TypeOf(value) { +pub fn get_state(self: Self, comptime Value: type) ?*Value { + return @ptrCast(@alignCast(self.thread_restricted_resources[@intFromEnum(states.thread_restriction(Value))].get(Value))); +} + +pub fn set_get_state(self: *Self, value: anytype) std.mem.Allocator.Error!*@TypeOf(value) { return self.thread_restricted_resources[@intFromEnum(states.thread_restriction(@TypeOf(value)))].set_get(value); } @@ -83,6 +93,6 @@ pub fn run_event(self: *Self, event: Event) anyerror!void { try self.event_systems.values[@intFromEnum(event)].run(self); } -pub fn set_resource(self: *Self, value: anytype) std.mem.Allocator.Error!void { +pub fn set_state(self: *Self, value: anytype) std.mem.Allocator.Error!void { try self.thread_restricted_resources[@intFromEnum(states.thread_restriction(@TypeOf(value)))].set(value); } diff --git a/src/flow/flow.zig b/src/flow/flow.zig index 34b84b9..566de79 100644 --- a/src/flow/flow.zig +++ b/src/flow/flow.zig @@ -6,6 +6,45 @@ pub const system = @import("./system.zig"); pub const World = @import("./World.zig"); +pub const Exclusive = struct { + world: *World, + + pub const Param = struct { + world: *World, + }; + + pub fn bind(context: system.BindContext) std.mem.Allocator.Error!Param { + return .{ + .world = context.world, + }; + } + + pub fn init(param: *Param) Exclusive { + return .{ + .world = param.world, + }; + } + + pub const thread_restriction = .main; +}; + +pub fn Params(comptime Value: type) type { + if (!@hasDecl(Value, "Param")) { + @compileError("System parameters must have a Params type declaration"); + } + + return struct { + has_head: ?*Node = null, + has_tail: ?*Node = null, + + pub const Node = struct { + param: Value.Param, + has_prev: ?*Node = null, + has_next: ?*Node = null, + }; + }; +} + pub fn Read(comptime Value: type) type { return Shared(Value, .{ .thread_restriction = states.thread_restriction(Value), @@ -58,19 +97,19 @@ pub fn Shared(comptime Value: type, comptime info: ShareInfo) type { const Self = @This(); - pub const State = struct { + pub const Param = struct { res: Qualified, }; - pub fn bind(context: system.BindContext) std.mem.Allocator.Error!State { + pub fn bind(context: system.BindContext) std.mem.Allocator.Error!Param { const thread_restriction_name = switch (info.thread_restriction) { .main => "main thread-restricted ", .none => "" }; const res = switch (info.read_only) { - true => (try context.register_readable_resource_access(Value)), - false => (try context.register_writable_resource_access(Value)), + true => (try context.register_readable_state_access(Value)), + false => (try context.register_writable_state_access(Value)), }; return .{ @@ -88,9 +127,9 @@ pub fn Shared(comptime Value: type, comptime info: ShareInfo) type { }; } - pub fn init(state: *State) Self { + pub fn init(param: *Param) Self { return .{ - .res = state.res, + .res = param.res, }; } }; @@ -104,60 +143,71 @@ pub fn Write(comptime Value: type) type { } fn parameter_type(comptime Value: type) *const system.Info.Parameter { - const has_state = @hasDecl(Value, "State"); + const ValueParams = Params(Value); if (@sizeOf(Value) == 0) { @compileError("System parameters must have a non-zero size"); } const parameters = struct { - fn bind(allocator: std.mem.Allocator, context: system.BindContext) std.mem.Allocator.Error!?*anyopaque { - if (has_state) { - const value_name = @typeName(Value); + fn bind(allocator: std.mem.Allocator, context: system.BindContext) std.mem.Allocator.Error!*anyopaque { + const value_name = @typeName(Value); - if (!@hasDecl(Value, "bind")) { - @compileError( - "a `bind` declaration on " ++ - value_name ++ - " is requied for parameter types with a `State` declaration"); - } - - const bind_type = @typeInfo(@TypeOf(Value.bind)); - - if (bind_type != .Fn) { - @compileError("`bind` declaration on " ++ value_name ++ " must be a fn"); - } - - if (bind_type.Fn.params.len != 1 or bind_type.Fn.params[0].type.? != system.BindContext) { - @compileError( - "`bind` fn on " ++ - value_name ++ - " must accept " ++ - @typeName(system.BindContext) ++ - " as it's one and only argument"); - } - - const state = try allocator.create(Value.State); - - state.* = switch (bind_type.Fn.return_type.?) { - Value.State => Value.bind(context), - std.mem.Allocator.Error!Value.State => try Value.bind(context), - else => @compileError( - "`bind` fn on " ++ - @typeName(Value) ++ - " must return " ++ - @typeName(Value.State) ++ - " or " ++ - @typeName(std.mem.Allocator.Error!Value.State)), - }; - - return @ptrCast(state); - } else { - return null; + if (!@hasDecl(Value, "bind")) { + @compileError( + "a `bind` declaration on " ++ + value_name ++ + " is requied for parameter types with a `Param` declaration"); } + + const bind_type = @typeInfo(@TypeOf(Value.bind)); + + if (bind_type != .Fn) { + @compileError("`bind` declaration on " ++ value_name ++ " must be a fn"); + } + + if (bind_type.Fn.params.len != 1 or bind_type.Fn.params[0].type.? != system.BindContext) { + @compileError( + "`bind` fn on " ++ + value_name ++ + " must accept " ++ + @typeName(system.BindContext) ++ + " as it's one and only argument"); + } + + const params_node = try allocator.create(ValueParams.Node); + + params_node.* = .{ + .param = switch (bind_type.Fn.return_type.?) { + Value.Param => Value.bind(context), + std.mem.Allocator.Error!Value.Param => try Value.bind(context), + + else => @compileError(std.fmt.comptimePrint("`bind` fn on {s} must return {s} or {s}", .{ + @typeName(Value), + @typeName(Value.Param), + @typeName(std.mem.Allocator.Error!Value.Param) + })), + }, + }; + + if (context.world.get_state(ValueParams)) |value_params| { + if (value_params.has_tail) |tail| { + tail.has_next = params_node; + } + + params_node.has_prev = value_params.has_tail; + value_params.has_tail = params_node; + } else { + try context.world.set_state(ValueParams{ + .has_head = params_node, + .has_tail = params_node, + }); + } + + return @ptrCast(params_node); } - fn init(argument: *anyopaque, state: ?*anyopaque) void { + fn init(argument: *anyopaque, erased_node: *anyopaque) void { const value_name = @typeName(Value); if (!@hasDecl(Value, "init")) { @@ -176,31 +226,37 @@ fn parameter_type(comptime Value: type) *const system.Info.Parameter { const concrete_argument = @as(*Value, @ptrCast(@alignCast(argument))); - if (has_state) { - if (init_type.Fn.params.len != 1 or init_type.Fn.params[0].type.? != *Value.State) { - @compileError("`init` fn on stateful " ++ value_name ++ " must accept a " ++ @typeName(*Value.State)); - } - - concrete_argument.* = Value.init(@ptrCast(@alignCast(state.?))); - } else { - if (init_type.Fn.params.len != 0) { - @compileError("`init` fn on statelss " ++ value_name ++ " cannot use parameters"); - } - - concrete_argument.* = Value.init(); + if (init_type.Fn.params.len != 1 or init_type.Fn.params[0].type.? != *Value.Param) { + @compileError("`init` fn on " ++ value_name ++ " must accept a " ++ @typeName(*Value.Param)); } + + concrete_argument.* = Value.init(&@as(*ValueParams.Node, @ptrCast(@alignCast(erased_node))).param); } - fn unbind(allocator: std.mem.Allocator, state: ?*anyopaque) void { + fn unbind(allocator: std.mem.Allocator, erased_node: *anyopaque, context: system.UnbindContext) void { if (@hasDecl(Value, "unbind")) { - if (has_state) { - const typed_state = @as(*Value.State, @ptrCast(@alignCast(state.?))); + const node = @as(*ValueParams.Node, @ptrCast(@alignCast(erased_node))); - Value.unbind(typed_state); - allocator.destroy(typed_state); - } else { - Value.unbind(); + if (node.has_prev) |prev| { + prev.has_next = node.has_next; } + + if (node.has_next) |next| { + next.has_prev = node.has_prev; + } + + if (context.world.get_state(ValueParams)) |params| { + if (node.has_prev == null) { + params.has_head = node.has_next; + } + + if (node.has_next == null) { + params.has_tail = node.has_prev; + } + } + + Value.unbind(node); + allocator.destroy(node); } } }; @@ -224,7 +280,7 @@ pub fn system_fn(comptime call: anytype) *const system.Info { } const systems = struct { - fn run(parameters: []const *const system.Info.Parameter, data: *const [system.max_parameters]?*anyopaque) anyerror!void { + fn run(parameters: []const *const system.Info.Parameter, data: *const [system.max_parameters]*anyopaque) anyerror!void { var call_args = @as(std.meta.ArgsTuple(Call), undefined); inline for (parameters, &call_args, data[0 .. parameters.len]) |parameter, *call_arg, state| { diff --git a/src/flow/system.zig b/src/flow/system.zig index 01e4a17..ba93cf8 100644 --- a/src/flow/system.zig +++ b/src/flow/system.zig @@ -37,8 +37,8 @@ pub const BindContext = struct { return false; } - pub fn register_writable_resource_access(self: BindContext, comptime Resource: type) std.mem.Allocator.Error!?*Resource { - const value = self.world.get_resource(Resource) orelse { + pub fn register_writable_state_access(self: BindContext, comptime Resource: type) std.mem.Allocator.Error!?*Resource { + const value = self.world.get_state(Resource) orelse { return null; }; @@ -65,8 +65,8 @@ pub const BindContext = struct { return value; } - pub fn register_readable_resource_access(self: BindContext, comptime Resource: type) std.mem.Allocator.Error!?*const Resource { - const value = self.world.get_resource(Resource) orelse { + pub fn register_readable_state_access(self: BindContext, comptime Resource: type) std.mem.Allocator.Error!?*const Resource { + const value = self.world.get_state(Resource) orelse { return null; }; @@ -95,16 +95,16 @@ pub const BindContext = struct { }; pub const Info = struct { - execute: *const fn ([]const *const Parameter, *const [max_parameters]?*anyopaque) anyerror!void, + execute: *const fn ([]const *const Parameter, *const [max_parameters]*anyopaque) anyerror!void, parameters: [max_parameters]*const Parameter = undefined, parameter_count: u4 = 0, thread_restriction: states.ThreadRestriction = .none, pub const Parameter = struct { thread_restriction: states.ThreadRestriction, - init: *const fn (*anyopaque, ?*anyopaque) void, - bind: *const fn (std.mem.Allocator, BindContext) std.mem.Allocator.Error!?*anyopaque, - unbind: *const fn (std.mem.Allocator, ?*anyopaque) void, + init: *const fn (*anyopaque, *anyopaque) void, + bind: *const fn (std.mem.Allocator, BindContext) std.mem.Allocator.Error!*anyopaque, + unbind: *const fn (std.mem.Allocator, *anyopaque, UnbindContext) void, }; pub fn used_parameters(self: *const Info) []const *const Parameter { @@ -142,7 +142,7 @@ pub const Schedule = struct { info: *const Info, label: [:0]u8, dependencies: []Dependency, - parameter_states: [max_parameters]?*anyopaque = [_]?*anyopaque{null} ** max_parameters, + parameter_states: [max_parameters]*anyopaque = [_]*anyopaque{undefined} ** max_parameters, resource_accesses: coral.stack.Sequential(ResourceAccess), }); @@ -157,7 +157,7 @@ pub const Schedule = struct { const ResourceNodeBundle = coral.map.Hashed(states.TypeID, NodeBundle, coral.map.enum_traits(states.TypeID)); - pub fn deinit(self: *Schedule) void { + pub fn deinit(self: *Schedule, world: *World) void { { var nodes = self.system_id_nodes.entries(); @@ -188,7 +188,9 @@ pub const Schedule = struct { const system = self.graph.get_ptr(node).?; for (system.info.used_parameters(), system.parameter_states[0 .. system.info.parameter_count]) |parameter, state| { - parameter.unbind(self.arena.allocator(), state); + parameter.unbind(self.arena.allocator(), state, .{ + .world = world, + }); } system.resource_accesses.deinit(); @@ -467,9 +469,9 @@ pub const Schedule = struct { errdefer { for (info.used_parameters(), system.parameter_states[0 .. info.parameter_count]) |parameter, state| { - if (state) |initialized_state| { - parameter.unbind(self.arena.allocator(), initialized_state); - } + parameter.unbind(self.arena.allocator(), state, .{ + .world = world, + }); } std.debug.assert(self.graph.remove_node(node) != null); @@ -489,4 +491,8 @@ pub const Schedule = struct { } }; +pub const UnbindContext = struct { + world: *World, +}; + pub const max_parameters = 16; diff --git a/src/main.zig b/src/main.zig index 4d84891..50a72c5 100644 --- a/src/main.zig +++ b/src/main.zig @@ -80,10 +80,14 @@ fn render(commands: ona.gfx.Commands, actors: ona.Write(Actors), app: ona.Read(o }, }); - try commands.set_effect(actors.res.crt_effect, CRT{ - .width = 1280, - .height = 720, - .time = @floatCast(app.res.elapsed_time), + try commands.set_effect(.{ + .effect = actors.res.crt_effect, + + .properties = std.mem.asBytes(&CRT{ + .width = 1280, + .height = 720, + .time = @floatCast(app.res.elapsed_time), + }), }); try commands.set_target(.{ @@ -112,8 +116,8 @@ fn update(player: ona.Read(Player), actors: ona.Write(Actors), mapping: ona.Read } fn setup(world: *ona.World, events: ona.App.Events) !void { - try world.set_resource(Actors{}); - try world.set_resource(Player{}); + try world.set_state(Actors{}); + try world.set_state(Player{}); try world.on_event(events.load, ona.system_fn(load), .{.label = "load"}); try world.on_event(events.update, ona.system_fn(update), .{.label = "update"}); diff --git a/src/ona/act.zig b/src/ona/act.zig index eb0ab3d..f8527d9 100644 --- a/src/ona/act.zig +++ b/src/ona/act.zig @@ -38,7 +38,7 @@ pub const Mapping = struct { }; pub fn setup(world: *flow.World, events: App.Events) std.mem.Allocator.Error!void { - try world.set_resource(Mapping{}); + try world.set_state(Mapping{}); try world.on_event(events.pre_update, flow.system_fn(update), .{ .label = "update act", diff --git a/src/ona/gfx.zig b/src/ona/gfx.zig index 99edf39..d393cff 100644 --- a/src/ona/gfx.zig +++ b/src/ona/gfx.zig @@ -10,15 +10,9 @@ const ext = @import("./ext.zig"); const flow = @import("flow"); -const handles = @import("./gfx/handles.zig"); - -pub const lina = @import("./gfx/lina.zig"); - const msg = @import("./msg.zig"); -const spirv = @import("./gfx/spirv.zig"); - -const rendering = @import("./gfx/rendering.zig"); +const sokol = @import("./gfx/sokol.zig"); const std = @import("std"); @@ -26,6 +20,8 @@ pub const Assets = struct { window: *ext.SDL_Window, texture_formats: coral.stack.Sequential(TextureFormat), frame_rendered: std.Thread.ResetEvent = .{}, + pending_work: WorkQueue = .{}, + has_worker_thread: ?std.Thread = null, pub const LoadError = std.mem.Allocator.Error; @@ -38,9 +34,51 @@ pub const Assets = struct { load_file: *const fn (*std.heap.ArenaAllocator, coral.files.Storage, []const u8) LoadFileError!Texture.Desc, }; + pub const WorkQueue = coral.asyncio.BlockingQueue(1024, union (enum) { + load_effect: LoadEffectWork, + load_texture: LoadTextureWork, + render_frame: RenderFrameWork, + shutdown, + unload_effect: UnloadEffectWork, + unload_texture: UnloadTextureWork, + + const LoadEffectWork = struct { + desc: Effect.Desc, + loaded: *coral.asyncio.Future(std.mem.Allocator.Error!Effect), + }; + + const LoadTextureWork = struct { + desc: Texture.Desc, + loaded: *coral.asyncio.Future(std.mem.Allocator.Error!Texture), + }; + + const RenderFrameWork = struct { + clear_color: Color, + width: u16, + height: u16, + finished: *std.Thread.ResetEvent, + has_command_params: ?*flow.Params(Commands).Node, + }; + + const UnloadEffectWork = struct { + handle: Effect, + }; + + const UnloadTextureWork = struct { + handle: Texture, + }; + }); + fn deinit(self: *Assets) void { - rendering.enqueue_work(.shutdown); + self.pending_work.enqueue(.shutdown); + + if (self.has_worker_thread) |worker_thread| { + worker_thread.join(); + } + self.texture_formats.deinit(); + + self.* = undefined; } fn init() !Assets { @@ -59,15 +97,13 @@ pub const Assets = struct { ext.SDL_DestroyWindow(window); } - try rendering.startup(window); - return .{ .texture_formats = .{.allocator = coral.heap.allocator}, .window = window, }; } - pub fn load_effect_file(_: *Assets, storage: coral.files.Storage, path: []const u8) LoadFileError!Effect { + pub fn load_effect_file(self: *Assets, storage: coral.files.Storage, path: []const u8) LoadFileError!Effect { if (!std.mem.endsWith(u8, path, ".spv")) { return error.FormatUnsupported; } @@ -85,7 +121,7 @@ pub const Assets = struct { var loaded = coral.asyncio.Future(std.mem.Allocator.Error!Effect){}; - rendering.enqueue_work(.{ + self.pending_work.enqueue(.{ .load_effect = .{ .desc = .{ .fragment_spirv_ops = fragment_spirv_ops, @@ -98,10 +134,10 @@ pub const Assets = struct { return loaded.get().*; } - pub fn load_texture(_: *Assets, desc: Texture.Desc) std.mem.Allocator.Error!Texture { + pub fn load_texture(self: *Assets, desc: Texture.Desc) std.mem.Allocator.Error!Texture { var loaded = coral.asyncio.Future(std.mem.Allocator.Error!Texture){}; - rendering.enqueue_work(.{ + self.pending_work.enqueue(.{ .load_texture = .{ .desc = desc, .loaded = &loaded, @@ -132,55 +168,127 @@ pub const Assets = struct { pub const thread_restriction = .main; }; -pub const Commands = struct { - list: *rendering.Commands.List, +pub const Color = @Vector(4, f32); - pub const State = struct { - commands: *rendering.Commands, +pub const Commands = struct { + pending: *List, + + const Command = union (enum) { + draw_texture: DrawTextureCommand, + set_effect: SetEffectCommand, + set_target: SetTargetCommand, }; - pub fn bind(_: flow.system.BindContext) std.mem.Allocator.Error!State { - var created_commands = coral.asyncio.Future(std.mem.Allocator.Error!*rendering.Commands){}; + pub const DrawTextureCommand = struct { + texture: Texture, + transform: Transform2D, + }; - rendering.enqueue_work(.{ - .create_commands = .{ - .created = &created_commands, + pub const SetEffectCommand = struct { + effect: Effect, + properties: []const coral.io.Byte, + }; + + pub const SetTargetCommand = struct { + texture: Texture, + clear_color: ?Color, + clear_depth: ?f32, + clear_stencil: ?u8, + }; + + pub const List = struct { + arena: std.heap.ArenaAllocator, + stack: coral.stack.Sequential(Command), + + fn clear(self: *List) void { + self.stack.clear(); + + if (!self.arena.reset(.retain_capacity)) { + std.log.warn("failed to reset the buffer of a gfx queue with retained capacity", .{}); + } + } + + fn deinit(self: *List) void { + self.arena.deinit(); + self.stack.deinit(); + + self.* = undefined; + } + + fn init(allocator: std.mem.Allocator) List { + return .{ + .arena = std.heap.ArenaAllocator.init(allocator), + .stack = .{.allocator = allocator}, + }; + } + }; + + pub const Param = struct { + swap_lists: [2]List, + swap_state: u1 = 0, + + fn deinit(self: *Param) void { + for (&self.swap_lists) |*list| { + list.deinit(); + } + + self.* = undefined; + } + + fn pending_list(self: *Param) *List { + return &self.swap_lists[self.swap_state]; + } + + fn rotate(self: *Param) void { + const swapped_state = self.swap_state ^ 1; + + self.swap_lists[swapped_state].clear(); + + self.swap_state = swapped_state; + } + + pub fn submitted_commands(self: Param) []const Command { + return self.swap_lists[self.swap_state ^ 1].stack.values; + } + }; + + pub fn bind(_: flow.system.BindContext) std.mem.Allocator.Error!Param { + return .{ + .swap_lists = .{ + List.init(coral.heap.allocator), + List.init(coral.heap.allocator), }, - }); - - return .{ - .commands = try created_commands.get().*, }; } - pub fn init(state: *State) Commands { + pub fn init(state: *Param) Commands { return .{ - .list = state.commands.pending_list(), + .pending = state.pending_list(), }; } - pub fn draw_texture(self: Commands, command: rendering.Command.DrawTexture) std.mem.Allocator.Error!void { - try self.list.append(.{.draw_texture = command}); + pub fn draw_texture(self: Commands, command: DrawTextureCommand) std.mem.Allocator.Error!void { + try self.pending.stack.push_grow(.{.draw_texture = command}); } - pub fn set_effect(self: Commands, effect: handles.Effect, properties: anytype) std.mem.Allocator.Error!void { - try self.list.append(.{ + pub fn set_effect(self: Commands, command: SetEffectCommand) std.mem.Allocator.Error!void { + try self.pending.stack.push_grow(.{ .set_effect = .{ - .properties = std.mem.asBytes(&properties), - .effect = effect, + .properties = try self.pending.arena.allocator().dupe(coral.io.Byte, command.properties), + .effect = command.effect, }, }); } - pub fn set_target(self: Commands, command: rendering.Command.SetTarget) std.mem.Allocator.Error!void { - try self.list.append(.{.set_target = command}); + pub fn set_target(self: Commands, command: SetTargetCommand) std.mem.Allocator.Error!void { + try self.pending.stack.push_grow(.{.set_target = command}); } }; pub const Config = struct { width: u16 = 1280, height: u16 = 720, - clear_color: lina.Color = colors.black, + clear_color: Color = colors.black, }; pub const Input = union (enum) { @@ -199,9 +307,59 @@ pub const Input = union (enum) { }; }; -pub const Effect = handles.Effect; +pub const Effect = enum (u32) { + default, + _, -pub const Texture = handles.Texture; + pub const Desc = struct { + fragment_spirv_ops: []const u32, + }; +}; + +pub const Texture = enum (u32) { + default, + backbuffer, + _, + + pub const Desc = struct { + format: Format, + access: Access, + + pub const Access = union (enum) { + static: StaticAccess, + render: RenderAccess, + }; + + pub const StaticAccess = struct { + width: u16, + data: []const coral.io.Byte, + }; + + pub const RenderAccess = struct { + width: u16, + height: u16, + }; + }; + + pub const Format = enum { + rgba8, + bgra8, + + pub fn byte_size(self: Format) usize { + return switch (self) { + .rgba8, .bgra8 => 4, + }; + } + }; +}; + +pub const Transform2D = extern struct { + xbasis: Vector = .{1, 0}, + ybasis: Vector = .{0, 1}, + origin: Vector = @splat(0), + + const Vector = @Vector(2, f32); +}; pub fn poll(app: flow.Write(App), inputs: msg.Send(Input)) !void { var event = @as(ext.SDL_Event, undefined); @@ -228,7 +386,7 @@ pub fn setup(world: *flow.World, events: App.Events) (error {Unsupported} || std assets.deinit(); } - break: create try world.set_get_resource(assets); + break: create try world.set_get_state(assets); }; assets.frame_rendered.set(); @@ -237,6 +395,8 @@ pub fn setup(world: *flow.World, events: App.Events) (error {Unsupported} || std assets.deinit(); } + assets.has_worker_thread = try std.Thread.spawn(.{}, sokol.run, .{&assets.pending_work, assets.window}); + const builtin_texture_formats = [_]Assets.TextureFormat{ .{ .extension = "bmp", @@ -248,7 +408,7 @@ pub fn setup(world: *flow.World, events: App.Events) (error {Unsupported} || std try assets.texture_formats.push_grow(format); } - try world.set_resource(Config{}); + try world.set_state(Config{}); try world.on_event(events.pre_update, flow.system_fn(poll), .{.label = "poll gfx"}); try world.on_event(events.exit, flow.system_fn(stop), .{.label = "stop gfx"}); try world.on_event(events.finish, flow.system_fn(synchronize), .{.label = "synchronize gfx"}); @@ -258,34 +418,40 @@ pub fn stop(assets: flow.Write(Assets)) void { assets.res.deinit(); } -pub fn synchronize(assets: flow.Write(Assets), config: flow.Read(Config)) !void { - assets.res.frame_rendered.wait(); - assets.res.frame_rendered.reset(); +pub fn synchronize(exclusive: flow.Exclusive) !void { + const assets = exclusive.world.get_state(Assets).?; + const config = exclusive.world.get_state(Config).?; - var commands_swapped = std.Thread.ResetEvent{}; + assets.frame_rendered.wait(); + assets.frame_rendered.reset(); - rendering.enqueue_work(.{ - .rotate_commands = .{ - .finished = &commands_swapped, - }, - }); + { + var has_command_param = exclusive.world.get_params(Commands).has_head; + + while (has_command_param) |command_param| : (has_command_param = command_param.has_next) { + command_param.param.rotate(); + } + } var display_width, var display_height = [_]c_int{0, 0}; - ext.SDL_GL_GetDrawableSize(assets.res.window, &display_width, &display_height); + ext.SDL_GL_GetDrawableSize(assets.window, &display_width, &display_height); - if (config.res.width != display_width or config.res.height != display_height) { - ext.SDL_SetWindowSize(assets.res.window, config.res.width, config.res.height); + if (config.width != display_width or config.height != display_height) { + ext.SDL_SetWindowSize(assets.window, config.width, config.height); } - commands_swapped.wait(); - - rendering.enqueue_work(.{ - .render_frame = .{ - .width = config.res.width, - .height = config.res.height, - .clear_color = config.res.clear_color, - .finished = &assets.res.frame_rendered, - }, - }); + if (exclusive.world.get_params(Commands).has_head) |command_param| { + assets.pending_work.enqueue(.{ + .render_frame = .{ + .has_command_params = command_param, + .width = config.width, + .height = config.height, + .clear_color = config.clear_color, + .finished = &assets.frame_rendered, + }, + }); + } else { + assets.frame_rendered.set(); + } } diff --git a/src/ona/gfx/bmp.zig b/src/ona/gfx/bmp.zig index b761a2d..8bd194a 100644 --- a/src/ona/gfx/bmp.zig +++ b/src/ona/gfx/bmp.zig @@ -1,10 +1,10 @@ const coral = @import("coral"); -const handles = @import("./handles.zig"); +const gfx = @import("../gfx.zig"); const std = @import("std"); -pub fn load_file(arena: *std.heap.ArenaAllocator, storage: coral.files.Storage, path: []const u8) !handles.Texture.Desc { +pub fn load_file(arena: *std.heap.ArenaAllocator, storage: coral.files.Storage, path: []const u8) !gfx.Texture.Desc { const header = try storage.read_little(path, 0, extern struct { type: [2]u8 align (1), file_size: u32 align (1), diff --git a/src/ona/gfx/colors.zig b/src/ona/gfx/colors.zig index 1682d07..e7ee635 100644 --- a/src/ona/gfx/colors.zig +++ b/src/ona/gfx/colors.zig @@ -1,13 +1,13 @@ -const lina = @import("./lina.zig"); +const gfx = @import("../gfx.zig"); pub const black = greyscale(0); pub const white = greyscale(1); -pub fn greyscale(v: f32) lina.Color { +pub fn greyscale(v: f32) gfx.Color { return .{v, v, v, 1}; } -pub fn rgb(r: f32, g: f32, b: f32) lina.Color { +pub fn rgb(r: f32, g: f32, b: f32) gfx.Color { return .{r, g, b, 1}; } diff --git a/src/ona/gfx/handles.zig b/src/ona/gfx/handles.zig deleted file mode 100644 index ac1680d..0000000 --- a/src/ona/gfx/handles.zig +++ /dev/null @@ -1,56 +0,0 @@ -const coral = @import("coral"); - -const std = @import("std"); - -pub const Effect = Handle(struct { - fragment_spirv_ops: []const u32, -}); - -fn Handle(comptime HandleDesc: type) type { - return enum (u32) { - default, - _, - - pub const Desc = HandleDesc; - - const Self = @This(); - }; -} - -pub const Texture = enum (u32) { - default, - backbuffer, - _, - - pub const Desc = struct { - format: Format, - access: Access, - - pub const Access = union (enum) { - static: Static, - render: Render, - - pub const Static = struct { - width: u16, - data: []const coral.io.Byte, - }; - - pub const Render = struct { - width: u16, - height: u16, - }; - }; - - pub const Format = enum { - rgba8, - bgra8, - - pub fn byte_size(self: Format) usize { - return switch (self) { - .rgba8, .bgra8 => 4, - }; - } - }; - }; -}; - diff --git a/src/ona/gfx/lina.zig b/src/ona/gfx/lina.zig deleted file mode 100644 index 5d3a599..0000000 --- a/src/ona/gfx/lina.zig +++ /dev/null @@ -1,34 +0,0 @@ -pub const Color = @Vector(4, f32); - -pub fn EvenOrderMatrix(comptime n: usize, comptime Element: type) type { - return [n]@Vector(n, Element); -} - -pub const Transform2D = extern struct { - xbasis: Vector = .{1, 0}, - ybasis: Vector = .{0, 1}, - origin: Vector = @splat(0), - - const Vector = @Vector(2, f32); -}; - -pub const ProjectionMatrix = EvenOrderMatrix(4, f32); - -pub const Rect = struct { - left: f32, - top: f32, - right: f32, - bottom: f32, -}; - -pub fn orthographic_projection(near: f32, far: f32, viewport: Rect) EvenOrderMatrix(4, f32) { - const width = viewport.right - viewport.left; - const height = viewport.bottom - viewport.top; - - return .{ - .{2 / width, 0, 0, 0}, - .{0, 2 / height, 0, 0}, - .{0, 0, 1 / (far - near), 0}, - .{-((viewport.left + viewport.right) / width), -((viewport.top + viewport.bottom) / height), near / (near - far), 1}, - }; -} diff --git a/src/ona/gfx/resources.zig b/src/ona/gfx/resources.zig deleted file mode 100644 index ca1e3e1..0000000 --- a/src/ona/gfx/resources.zig +++ /dev/null @@ -1,380 +0,0 @@ -const coral = @import("coral"); - -const handles = @import("./handles.zig"); - -const lina = @import("./lina.zig"); - -const sokol = @import("sokol"); - -const spirv = @import("./spirv.zig"); - -const std = @import("std"); - -pub const Effect = struct { - shader: sokol.gfx.Shader, - pipeline: sokol.gfx.Pipeline, - properties: []coral.io.Byte, - - pub fn deinit(self: *Effect) void { - coral.heap.allocator.free(self.properties); - sokol.gfx.destroyPipeline(self.pipeline); - sokol.gfx.destroyShader(self.shader); - - self.* = undefined; - } - - pub fn init(desc: handles.Effect.Desc) spirv.Error!Effect { - var spirv_arena = std.heap.ArenaAllocator.init(coral.heap.allocator); - - defer { - spirv_arena.deinit(); - } - - const spirv_program = try spirv.analyze(&spirv_arena, .{ - .target = try switch (sokol.gfx.queryBackend()) { - .GLCORE => spirv.Target.glsl, - else => error.InvalidSPIRV, - }, - - .vertex_source = .{ - .ops = &spirv.to_ops(@embedFile("./shaders/2d_default.vert.spv")), - }, - - .fragment_source = .{ - .ops = desc.fragment_spirv_ops, - }, - }); - - const shader = sokol.gfx.makeShader(shader_desc: { - const shader_desc = sokol.gfx.ShaderDesc{ - .vs = stage_desc(spirv_program.vertex_stage), - .fs = stage_desc(spirv_program.fragment_stage), - }; - - // TODO: Vertex attributes, for some reason they aren't needed? - - break: shader_desc shader_desc; - }); - - // TODO: Review blending rules. - const pipeline = sokol.gfx.makePipeline(pipeline_desc: { - var pipeline_desc = sokol.gfx.PipelineDesc{ - .label = "Effect pipeline", - .layout = vertex_layout_state, - .shader = shader, - .index_type = .UINT16, - .blend_color = .{.r = 1.0, .g = 1.0, .b = 1.0, .a = 1.0}, - }; - - pipeline_desc.colors[0] = .{ - .write_mask = .RGBA, - - .blend = .{ - .enabled = true, - .src_factor_rgb = .SRC_ALPHA, - .dst_factor_rgb = .ONE_MINUS_SRC_ALPHA, - }, - }; - - break: pipeline_desc pipeline_desc; - }); - - const properties = try coral.heap.allocator.alloc( - coral.io.Byte, - if (spirv_program.fragment_stage.has_uniform_blocks[0]) |uniform_block| uniform_block.size() else 0, - ); - - errdefer { - coral.heap.allocator.free(properties); - sokol.gfx.destroyPipeline(pipeline); - sokol.gfx.destroyShader(shader); - } - - return .{ - .shader = shader, - .pipeline = pipeline, - .properties = properties, - }; - } - - fn stage_desc(spirv_stage: spirv.Stage) sokol.gfx.ShaderStageDesc { - var stage = sokol.gfx.ShaderStageDesc{ - .entry = spirv_stage.entry_point, - .source = spirv_stage.source, - }; - - for (0 .. spirv.Stage.max_uniform_blocks) |slot| { - const uniform_block = &(spirv_stage.has_uniform_blocks[slot] orelse { - continue; - }); - - const stage_uniform_block = &stage.uniform_blocks[slot]; - - stage_uniform_block.layout = switch (uniform_block.layout) { - .std140 => .STD140, - }; - - stage_uniform_block.size = uniform_block.size(); - - for (stage_uniform_block.uniforms[0 .. uniform_block.uniforms.len], uniform_block.uniforms) |*stage_uniform, uniform| { - stage_uniform.* = .{ - .type = switch (uniform.type) { - .float => .FLOAT, - .float2 => .FLOAT2, - .float3 => .FLOAT3, - .float4 => .FLOAT4, - .integer => .INT, - .integer2 => .INT2, - .integer3 => .INT3, - .integer4 => .INT4, - .matrix4 => .MAT4, - }, - - .name = uniform.name, - .array_count = uniform.len, - }; - } - } - - for (0 .. spirv.Stage.max_images) |slot| { - const image = &(spirv_stage.has_images[slot] orelse { - continue; - }); - - stage.images[slot] = .{ - .multisampled = image.is_multisampled, - .image_type = ._2D, - .sample_type = .FLOAT, - .used = true, - }; - } - - for (0 .. spirv.Stage.max_samplers) |slot| { - const sampler = &(spirv_stage.has_samplers[slot] orelse { - continue; - }); - - stage.samplers[slot] = .{ - .sampler_type = switch (sampler.*) { - .filtering => .FILTERING, - .non_filtering => .NONFILTERING, - .comparison => .COMPARISON, - }, - - .used = true, - }; - } - - for (0 .. spirv.Stage.max_image_sampler_pairs) |slot| { - const image_sampler_pair = &(spirv_stage.has_image_sampler_pairs[slot] orelse { - continue; - }); - - stage.image_sampler_pairs[slot] = .{ - .glsl_name = image_sampler_pair.name, - .image_slot = @intCast(image_sampler_pair.image_slot), - .sampler_slot = @intCast(image_sampler_pair.sampler_slot), - .used = true, - }; - } - - return stage; - } - - const vertex_layout_state = sokol.gfx.VertexLayoutState{ - .attrs = get: { - var attrs = [_]sokol.gfx.VertexAttrState{.{}} ** 16; - - attrs[0] = .{ - .format = .FLOAT2, - .buffer_index = 0, - }; - - attrs[1] = .{ - .format = .FLOAT2, - .buffer_index = 0, - }; - - attrs[2] = .{ - .format = .FLOAT2, - .buffer_index = 1, - }; - - attrs[3] = .{ - .format = .FLOAT2, - .buffer_index = 1, - }; - - attrs[4] = .{ - .format = .FLOAT2, - .buffer_index = 1, - }; - - attrs[5] = .{ - .format = .UBYTE4N, - .buffer_index = 1, - }; - - attrs[6] = .{ - .format = .FLOAT, - .buffer_index = 1, - }; - - attrs[7] = .{ - .format = .FLOAT4, - .buffer_index = 1, - }; - - break: get attrs; - }, - - .buffers = get: { - var vertex_buffer_layouts = [_]sokol.gfx.VertexBufferLayoutState{.{}} ** 8; - - vertex_buffer_layouts[1].step_func = .PER_INSTANCE; - - break: get vertex_buffer_layouts; - }, - }; -}; - -pub const Texture = struct { - width: u16, - height: u16, - access: Access, - - pub const Access = union (enum) { - empty, - render: RenderAccess, - static: StaticAccess, - }; - - pub const RenderAccess = struct { - color_image: sokol.gfx.Image, - depth_image: sokol.gfx.Image, - attachments: sokol.gfx.Attachments, - }; - - pub const StaticAccess = struct { - image: sokol.gfx.Image, - }; - - pub fn deinit(self: *Texture) void { - switch (self.access) { - .render => |render| { - sokol.gfx.destroyImage(render.color_image); - sokol.gfx.destroyImage(render.depth_image); - sokol.gfx.destroyAttachments(render.attachments); - }, - - .static => |static| { - sokol.gfx.destroyImage(static.image); - }, - - .empty => {}, - } - - self.* = undefined; - } - - pub fn init(desc: handles.Texture.Desc) std.mem.Allocator.Error!Texture { - const pixel_format = switch (desc.format) { - .rgba8 => sokol.gfx.PixelFormat.RGBA8, - .bgra8 => sokol.gfx.PixelFormat.BGRA8, - }; - - switch (desc.access) { - .render => |render| { - if (render.width == 0 or render.height == 0) { - return .{ - .width = render.width, - .height = render.height, - .access = .empty, - }; - } - - const color_image = sokol.gfx.makeImage(.{ - .pixel_format = pixel_format, - .width = render.width, - .height = render.height, - .render_target = true, - }); - - const depth_image = sokol.gfx.makeImage(.{ - .width = render.width, - .height = render.height, - .render_target = true, - .pixel_format = .DEPTH_STENCIL, - }); - - const attachments = sokol.gfx.makeAttachments(attachments_desc: { - var attachments_desc = sokol.gfx.AttachmentsDesc{ - .depth_stencil = .{ - .image = depth_image, - }, - }; - - attachments_desc.colors[0] = .{ - .image = color_image, - }; - - break: attachments_desc attachments_desc; - }); - - return .{ - .width = render.width, - .height = render.height, - - .access = .{ - .render = .{ - .attachments = attachments, - .color_image = color_image, - .depth_image = depth_image, - }, - }, - }; - }, - - .static => |static| { - const height = std.math.cast(u16, static.data.len / (static.width * desc.format.byte_size())) orelse { - return error.OutOfMemory; - }; - - if (static.width == 0 or height == 0) { - return .{ - .width = static.width, - .height = height, - .access = .empty, - }; - } - - const image = sokol.gfx.makeImage(image_desc: { - var image_desc = sokol.gfx.ImageDesc{ - .height = height, - .pixel_format = pixel_format, - .width = static.width, - }; - - image_desc.data.subimage[0][0] = sokol.gfx.asRange(static.data); - - break: image_desc image_desc; - }); - - errdefer { - sokol.gfx.destroyImage(image); - } - - return .{ - .width = static.width, - .height = height, - - .access = .{ - .static = .{ - .image = image, - }, - }, - }; - }, - } - } -}; diff --git a/src/ona/gfx/rendering.zig b/src/ona/gfx/sokol.zig similarity index 52% rename from src/ona/gfx/rendering.zig rename to src/ona/gfx/sokol.zig index 95f19fb..815fcf1 100644 --- a/src/ona/gfx/rendering.zig +++ b/src/ona/gfx/sokol.zig @@ -2,11 +2,7 @@ const coral = @import("coral"); const ext = @import("../ext.zig"); -const handles = @import("./handles.zig"); - -const lina = @import("./lina.zig"); - -const resources = @import("./resources.zig"); +const gfx = @import("../gfx.zig"); const spirv = @import("./spirv.zig"); @@ -14,139 +10,23 @@ const sokol = @import("sokol"); const std = @import("std"); -pub const Command = union (enum) { - draw_texture: DrawTexture, - set_effect: SetEffect, - set_target: SetTarget, - - pub const DrawTexture = struct { - texture: handles.Texture, - transform: lina.Transform2D, - }; - - pub const SetEffect = struct { - effect: handles.Effect, - properties: []const coral.io.Byte, - }; - - pub const SetTarget = struct { - texture: handles.Texture, - clear_color: ?lina.Color, - clear_depth: ?f32, - clear_stencil: ?u8, - }; - - fn clone(self: Command, arena: *std.heap.ArenaAllocator) !Command { - return switch (self) { - .draw_texture => |draw_texture| .{.draw_texture = draw_texture}, - - .set_effect => |set_effect| .{ - .set_effect = .{ - .properties = try arena.allocator().dupe(coral.io.Byte, set_effect.properties), - .effect = set_effect.effect, - }, - }, - - .set_target => |set_target| .{.set_target = set_target}, - }; - } - - fn process(self: Command, pools: *Pools, frame: *Frame) !void { - try switch (self) { - .draw_texture => |draw_texture| frame.draw_texture(pools, draw_texture), - .set_effect => |set_effect| frame.set_effect(pools, set_effect), - .set_target => |set_target| frame.set_target(pools, set_target), - }; - } -}; - -pub const Commands = struct { - swap_lists: [2]List, - swap_state: u1 = 0, - has_next: ?*Commands = null, - has_prev: ?*Commands = null, - - pub const List = struct { - arena: std.heap.ArenaAllocator, - stack: coral.stack.Sequential(Command), - - pub fn append(self: *List, command: Command) std.mem.Allocator.Error!void { - return self.stack.push_grow(try command.clone(&self.arena)); - } - - pub fn clear(self: *List) void { - self.stack.clear(); - - if (!self.arena.reset(.retain_capacity)) { - std.log.warn("failed to reset the buffer of a gfx queue with retained capacity", .{}); - } - } - - fn deinit(self: *List) void { - self.arena.deinit(); - self.stack.deinit(); - - self.* = undefined; - } - - fn init(allocator: std.mem.Allocator) List { - return .{ - .arena = std.heap.ArenaAllocator.init(allocator), - .stack = .{.allocator = allocator}, - }; - } - }; - - const Self = @This(); - - pub fn deinit(self: *Self) void { - for (&self.swap_lists) |*list| { - list.deinit(); - } - - self.* = undefined; - } - - pub fn init(allocator: std.mem.Allocator) Self { - return .{ - .swap_lists = .{List.init(allocator), List.init(allocator)}, - }; - } - - pub fn pending_list(self: *Self) *List { - return &self.swap_lists[self.swap_state]; - } - - fn rotate(self: *Self) void { - const swapped_state = self.swap_state ^ 1; - - self.swap_lists[swapped_state].clear(); - - self.swap_state = swapped_state; - } - - pub fn submitted_commands(self: *const Self) []const Command { - return self.swap_lists[self.swap_state ^ 1].stack.values; - } -}; - const Frame = struct { swapchain: sokol.gfx.Swapchain, drawn_count: usize = 0, flushed_count: usize = 0, - current_source_texture: handles.Texture = .default, - current_target_texture: handles.Texture = .backbuffer, - current_effect: handles.Effect = .default, + current_source_texture: gfx.Texture = .default, + current_target_texture: gfx.Texture = .backbuffer, + current_effect: gfx.Effect = .default, const DrawTexture = extern struct { - transform: lina.Transform2D, + transform: gfx.Transform2D, tint: @Vector(4, u8) = @splat(std.math.maxInt(u8)), depth: f32 = 0, texture_offset: @Vector(2, f32) = @splat(0), texture_size: @Vector(2, f32) = @splat(1), }; - pub fn draw_texture(self: *Frame, pools: *Pools, command: Command.DrawTexture) !void { + pub fn draw_texture(self: *Frame, pools: *Resources, command: gfx.Commands.DrawTextureCommand) !void { if (command.texture != self.current_source_texture) { self.flush(pools); } @@ -174,7 +54,7 @@ const Frame = struct { self.drawn_count += 1; } - pub fn flush(self: *Frame, pools: *Pools) void { + pub fn flush(self: *Frame, pools: *Resources) void { if (self.flushed_count == self.drawn_count) { return; } @@ -207,7 +87,7 @@ const Frame = struct { const texture = pools.get_texture(self.current_target_texture).?; - sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&lina.orthographic_projection(-1.0, 1.0, .{ + sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&coral.lina.orthographic_projection(-1.0, 1.0, .{ .left = 0, .top = 0, .right = @floatFromInt(texture.width), @@ -236,7 +116,7 @@ const Frame = struct { } } - pub fn set_effect(self: *Frame, pools: *Pools, command: Command.SetEffect) void { + pub fn set_effect(self: *Frame, pools: *Resources, command: gfx.Commands.SetEffectCommand) void { if (command.effect != self.current_effect) { self.flush(pools); } @@ -248,7 +128,7 @@ const Frame = struct { } } - pub fn set_target(self: *Frame, pools: *Pools, command: Command.SetTarget) void { + pub fn set_target(self: *Frame, pools: *Resources, command: gfx.Commands.SetTargetCommand) void { sokol.gfx.endPass(); var pass = sokol.gfx.Pass{ @@ -298,31 +178,401 @@ const Frame = struct { const batches_per_buffer = 512; }; -const Pools = struct { +const Resources = struct { effects: EffectPool, textures: TexturePool, - const EffectPool = coral.Pool(resources.Effect); + pub const Effect = struct { + shader: sokol.gfx.Shader, + pipeline: sokol.gfx.Pipeline, + properties: []coral.io.Byte, - const TexturePool = coral.Pool(resources.Texture); + pub fn deinit(self: *Effect) void { + coral.heap.allocator.free(self.properties); + sokol.gfx.destroyPipeline(self.pipeline); + sokol.gfx.destroyShader(self.shader); - pub fn create_effect(self: *Pools, desc: handles.Effect.Desc) !handles.Effect { - var effect = try resources.Effect.init(desc); + self.* = undefined; + } + + pub fn init(desc: gfx.Effect.Desc) spirv.Error!Effect { + var spirv_arena = std.heap.ArenaAllocator.init(coral.heap.allocator); + + defer { + spirv_arena.deinit(); + } + + const spirv_program = try spirv.analyze(&spirv_arena, .{ + .target = try switch (sokol.gfx.queryBackend()) { + .GLCORE => spirv.Target.glsl, + else => error.InvalidSPIRV, + }, + + .vertex_source = .{ + .ops = &spirv.to_ops(@embedFile("./shaders/2d_default.vert.spv")), + }, + + .fragment_source = .{ + .ops = desc.fragment_spirv_ops, + }, + }); + + const shader = sokol.gfx.makeShader(shader_desc: { + const shader_desc = sokol.gfx.ShaderDesc{ + .vs = stage_desc(spirv_program.vertex_stage), + .fs = stage_desc(spirv_program.fragment_stage), + }; + + // TODO: Vertex attributes, for some reason they aren't needed? + + break: shader_desc shader_desc; + }); + + // TODO: Review blending rules. + const pipeline = sokol.gfx.makePipeline(pipeline_desc: { + var pipeline_desc = sokol.gfx.PipelineDesc{ + .label = "Effect pipeline", + .layout = vertex_layout_state, + .shader = shader, + .index_type = .UINT16, + .blend_color = .{.r = 1.0, .g = 1.0, .b = 1.0, .a = 1.0}, + }; + + pipeline_desc.colors[0] = .{ + .write_mask = .RGBA, + + .blend = .{ + .enabled = true, + .src_factor_rgb = .SRC_ALPHA, + .dst_factor_rgb = .ONE_MINUS_SRC_ALPHA, + }, + }; + + break: pipeline_desc pipeline_desc; + }); + + const properties = try coral.heap.allocator.alloc( + coral.io.Byte, + if (spirv_program.fragment_stage.has_uniform_blocks[0]) |uniform_block| uniform_block.size() else 0, + ); + + errdefer { + coral.heap.allocator.free(properties); + sokol.gfx.destroyPipeline(pipeline); + sokol.gfx.destroyShader(shader); + } + + return .{ + .shader = shader, + .pipeline = pipeline, + .properties = properties, + }; + } + + fn stage_desc(spirv_stage: spirv.Stage) sokol.gfx.ShaderStageDesc { + var stage = sokol.gfx.ShaderStageDesc{ + .entry = spirv_stage.entry_point, + .source = spirv_stage.source, + }; + + for (0 .. spirv.Stage.max_uniform_blocks) |slot| { + const uniform_block = &(spirv_stage.has_uniform_blocks[slot] orelse { + continue; + }); + + const stage_uniform_block = &stage.uniform_blocks[slot]; + + stage_uniform_block.layout = switch (uniform_block.layout) { + .std140 => .STD140, + }; + + stage_uniform_block.size = uniform_block.size(); + + for (stage_uniform_block.uniforms[0 .. uniform_block.uniforms.len], uniform_block.uniforms) |*stage_uniform, uniform| { + stage_uniform.* = .{ + .type = switch (uniform.type) { + .float => .FLOAT, + .float2 => .FLOAT2, + .float3 => .FLOAT3, + .float4 => .FLOAT4, + .integer => .INT, + .integer2 => .INT2, + .integer3 => .INT3, + .integer4 => .INT4, + .matrix4 => .MAT4, + }, + + .name = uniform.name, + .array_count = uniform.len, + }; + } + } + + for (0 .. spirv.Stage.max_images) |slot| { + const image = &(spirv_stage.has_images[slot] orelse { + continue; + }); + + stage.images[slot] = .{ + .multisampled = image.is_multisampled, + .image_type = ._2D, + .sample_type = .FLOAT, + .used = true, + }; + } + + for (0 .. spirv.Stage.max_samplers) |slot| { + const sampler = &(spirv_stage.has_samplers[slot] orelse { + continue; + }); + + stage.samplers[slot] = .{ + .sampler_type = switch (sampler.*) { + .filtering => .FILTERING, + .non_filtering => .NONFILTERING, + .comparison => .COMPARISON, + }, + + .used = true, + }; + } + + for (0 .. spirv.Stage.max_image_sampler_pairs) |slot| { + const image_sampler_pair = &(spirv_stage.has_image_sampler_pairs[slot] orelse { + continue; + }); + + stage.image_sampler_pairs[slot] = .{ + .glsl_name = image_sampler_pair.name, + .image_slot = @intCast(image_sampler_pair.image_slot), + .sampler_slot = @intCast(image_sampler_pair.sampler_slot), + .used = true, + }; + } + + return stage; + } + + const vertex_layout_state = sokol.gfx.VertexLayoutState{ + .attrs = get: { + var attrs = [_]sokol.gfx.VertexAttrState{.{}} ** 16; + + attrs[0] = .{ + .format = .FLOAT2, + .buffer_index = 0, + }; + + attrs[1] = .{ + .format = .FLOAT2, + .buffer_index = 0, + }; + + attrs[2] = .{ + .format = .FLOAT2, + .buffer_index = 1, + }; + + attrs[3] = .{ + .format = .FLOAT2, + .buffer_index = 1, + }; + + attrs[4] = .{ + .format = .FLOAT2, + .buffer_index = 1, + }; + + attrs[5] = .{ + .format = .UBYTE4N, + .buffer_index = 1, + }; + + attrs[6] = .{ + .format = .FLOAT, + .buffer_index = 1, + }; + + attrs[7] = .{ + .format = .FLOAT4, + .buffer_index = 1, + }; + + break: get attrs; + }, + + .buffers = get: { + var vertex_buffer_layouts = [_]sokol.gfx.VertexBufferLayoutState{.{}} ** 8; + + vertex_buffer_layouts[1].step_func = .PER_INSTANCE; + + break: get vertex_buffer_layouts; + }, + }; + }; + + pub const Texture = struct { + width: u16, + height: u16, + access: Access, + + pub const Access = union (enum) { + empty, + render: RenderAccess, + static: StaticAccess, + }; + + pub const RenderAccess = struct { + color_image: sokol.gfx.Image, + depth_image: sokol.gfx.Image, + attachments: sokol.gfx.Attachments, + }; + + pub const StaticAccess = struct { + image: sokol.gfx.Image, + }; + + pub fn deinit(self: *Texture) void { + switch (self.access) { + .render => |render| { + sokol.gfx.destroyImage(render.color_image); + sokol.gfx.destroyImage(render.depth_image); + sokol.gfx.destroyAttachments(render.attachments); + }, + + .static => |static| { + sokol.gfx.destroyImage(static.image); + }, + + .empty => {}, + } + + self.* = undefined; + } + + pub fn init(desc: gfx.Texture.Desc) std.mem.Allocator.Error!Texture { + const pixel_format = switch (desc.format) { + .rgba8 => sokol.gfx.PixelFormat.RGBA8, + .bgra8 => sokol.gfx.PixelFormat.BGRA8, + }; + + switch (desc.access) { + .render => |render| { + if (render.width == 0 or render.height == 0) { + return .{ + .width = render.width, + .height = render.height, + .access = .empty, + }; + } + + const color_image = sokol.gfx.makeImage(.{ + .pixel_format = pixel_format, + .width = render.width, + .height = render.height, + .render_target = true, + }); + + const depth_image = sokol.gfx.makeImage(.{ + .width = render.width, + .height = render.height, + .render_target = true, + .pixel_format = .DEPTH_STENCIL, + }); + + const attachments = sokol.gfx.makeAttachments(attachments_desc: { + var attachments_desc = sokol.gfx.AttachmentsDesc{ + .depth_stencil = .{ + .image = depth_image, + }, + }; + + attachments_desc.colors[0] = .{ + .image = color_image, + }; + + break: attachments_desc attachments_desc; + }); + + return .{ + .width = render.width, + .height = render.height, + + .access = .{ + .render = .{ + .attachments = attachments, + .color_image = color_image, + .depth_image = depth_image, + }, + }, + }; + }, + + .static => |static| { + const height = std.math.cast(u16, static.data.len / (static.width * desc.format.byte_size())) orelse { + return error.OutOfMemory; + }; + + if (static.width == 0 or height == 0) { + return .{ + .width = static.width, + .height = height, + .access = .empty, + }; + } + + const image = sokol.gfx.makeImage(image_desc: { + var image_desc = sokol.gfx.ImageDesc{ + .height = height, + .pixel_format = pixel_format, + .width = static.width, + }; + + image_desc.data.subimage[0][0] = sokol.gfx.asRange(static.data); + + break: image_desc image_desc; + }); + + errdefer { + sokol.gfx.destroyImage(image); + } + + return .{ + .width = static.width, + .height = height, + + .access = .{ + .static = .{ + .image = image, + }, + }, + }; + }, + } + } + }; + + + const EffectPool = coral.Pool(Effect); + + const TexturePool = coral.Pool(Texture); + + pub fn create_effect(self: *Resources, desc: gfx.Effect.Desc) !gfx.Effect { + var effect = try Effect.init(desc); errdefer effect.deinit(); return @enumFromInt(try self.effects.insert(effect)); } - pub fn create_texture(self: *Pools, desc: handles.Texture.Desc) !handles.Texture { - var texture = try resources.Texture.init(desc); + pub fn create_texture(self: *Resources, desc: gfx.Texture.Desc) !gfx.Texture { + var texture = try Texture.init(desc); errdefer texture.deinit(); return @enumFromInt(try self.textures.insert(texture)); } - pub fn deinit(self: *Pools) void { + pub fn deinit(self: *Resources) void { var textures = self.textures.values(); while (textures.next()) |texture| { @@ -332,7 +582,7 @@ const Pools = struct { self.textures.deinit(); } - pub fn destroy_effect(self: *Pools, handle: handles.Effect) bool { + pub fn destroy_effect(self: *Resources, handle: gfx.Effect) bool { switch (handle) { .default => {}, @@ -348,7 +598,7 @@ const Pools = struct { return true; } - pub fn destroy_texture(self: *Pools, handle: handles.Texture) bool { + pub fn destroy_texture(self: *Resources, handle: gfx.Texture) bool { switch (handle) { .default => {}, @@ -364,16 +614,16 @@ const Pools = struct { return true; } - pub fn get_effect(self: *Pools, handle: handles.Effect) ?*resources.Effect { + pub fn get_effect(self: *Resources, handle: gfx.Effect) ?*Effect { return self.effects.get(@intFromEnum(handle)); } - pub fn get_texture(self: *Pools, handle: handles.Texture) ?*resources.Texture { + pub fn get_texture(self: *Resources, handle: gfx.Texture) ?*Texture { return self.textures.get(@intFromEnum(handle)); } - pub fn init(allocator: std.mem.Allocator) !Pools { - var pools = Pools{ + pub fn init(allocator: std.mem.Allocator) !Resources { + var pools = Resources{ .effects = EffectPool.init(allocator), .textures = TexturePool.init(allocator), }; @@ -388,11 +638,11 @@ const Pools = struct { } }; - assert.is_handle(handles.Effect.default, try pools.create_effect(.{ + assert.is_handle(gfx.Effect.default, try pools.create_effect(.{ .fragment_spirv_ops = &spirv.to_ops(@embedFile("./shaders/2d_default.frag.spv")), })); - assert.is_handle(handles.Texture.default, try pools.create_texture(.{ + assert.is_handle(gfx.Texture.default, try pools.create_texture(.{ .format = .rgba8, .access = .{ @@ -413,7 +663,7 @@ const Pools = struct { }, })); - assert.is_handle(handles.Texture.backbuffer, try pools.create_texture(.{ + assert.is_handle(gfx.Texture.backbuffer, try pools.create_texture(.{ .format = .rgba8, .access = .{ @@ -428,62 +678,8 @@ const Pools = struct { } }; -pub const Work = union (enum) { - create_commands: CreateCommandsWork, - load_effect: LoadEffectWork, - load_texture: LoadTextureWork, - render_frame: RenderFrameWork, - rotate_commands: RotateCommandsWork, - shutdown, - unload_effect: UnloadEffectWork, - unload_texture: UnloadTextureWork, - - pub const CreateCommandsWork = struct { - created: *coral.asyncio.Future(std.mem.Allocator.Error!*Commands), - }; - - pub const LoadEffectWork = struct { - desc: handles.Effect.Desc, - loaded: *coral.asyncio.Future(std.mem.Allocator.Error!handles.Effect), - }; - - pub const LoadTextureWork = struct { - desc: handles.Texture.Desc, - loaded: *coral.asyncio.Future(std.mem.Allocator.Error!handles.Texture), - }; - - pub const RotateCommandsWork = struct { - finished: *std.Thread.ResetEvent, - }; - - pub const RenderFrameWork = struct { - clear_color: lina.Color, - width: u16, - height: u16, - finished: *std.Thread.ResetEvent, - }; - - pub const UnloadEffectWork = struct { - handle: handles.Effect, - }; - - pub const UnloadTextureWork = struct { - handle: handles.Texture, - }; - - var pending: coral.asyncio.BlockingQueue(1024, Work) = .{}; -}; - var default_sampler: sokol.gfx.Sampler = undefined; -pub fn enqueue_work(work: Work) void { - Work.pending.enqueue(work); -} - -pub fn startup(window: *ext.SDL_Window) !void { - work_thread = try std.Thread.spawn(.{}, run, .{window}); -} - var quad_index_buffer: sokol.gfx.Buffer = undefined; var quad_vertex_buffer: sokol.gfx.Buffer = undefined; @@ -493,7 +689,7 @@ const vertex_indices = .{ .instance = 1, }; -fn run(window: *ext.SDL_Window) !void { +pub fn run(pending_work: *gfx.Assets.WorkQueue, window: *ext.SDL_Window) !void { const context = configure_and_create: { var result = @as(c_int, 0); @@ -530,7 +726,7 @@ fn run(window: *ext.SDL_Window) !void { sokol.gfx.shutdown(); } - var pools = try Pools.init(coral.heap.allocator); + var pools = try Resources.init(coral.heap.allocator); defer { pools.deinit(); @@ -562,36 +758,8 @@ fn run(window: *ext.SDL_Window) !void { default_sampler = sokol.gfx.makeSampler(.{}); - var has_commands_head: ?*Commands = null; - var has_commands_tail: ?*Commands = null; - while (true) { - switch (Work.pending.dequeue()) { - .create_commands => |create_commands| { - const commands = coral.heap.allocator.create(Commands) catch { - _ = create_commands.created.resolve(error.OutOfMemory); - - continue; - }; - - commands.* = Commands.init(coral.heap.allocator); - - if (create_commands.created.resolve(commands)) { - if (has_commands_tail) |tail_commands| { - tail_commands.has_next = commands; - commands.has_prev = tail_commands; - } else { - std.debug.assert(has_commands_head == null); - - has_commands_head = commands; - } - - has_commands_tail = commands; - } else { - coral.heap.allocator.destroy(commands); - } - }, - + switch (pending_work.dequeue()) { .load_effect => |load| { const effect = try pools.create_effect(load.desc); @@ -614,7 +782,7 @@ fn run(window: *ext.SDL_Window) !void { if (backbuffer.width != render_frame.width or backbuffer.height != render_frame.height) { backbuffer.deinit(); - backbuffer.* = try resources.Texture.init(.{ + backbuffer.* = try Resources.Texture.init(.{ .format = .rgba8, .access = .{ @@ -661,11 +829,15 @@ fn run(window: *ext.SDL_Window) !void { break: pass pass; }); - var has_commands = has_commands_head; + var has_command_params = render_frame.has_command_params; - while (has_commands) |commands| : (has_commands = commands.has_next) { - for (commands.submitted_commands()) |command| { - try command.process(&pools, &frame); + while (has_command_params) |command_params| : (has_command_params = command_params.has_next) { + for (command_params.param.submitted_commands()) |command| { + try switch (command) { + .draw_texture => |draw_texture| frame.draw_texture(&pools, draw_texture), + .set_effect => |set_effect| frame.set_effect(&pools, set_effect), + .set_target => |set_target| frame.set_target(&pools, set_target), + }; } frame.flush(&pools); @@ -714,16 +886,6 @@ fn run(window: *ext.SDL_Window) !void { render_frame.finished.set(); }, - .rotate_commands => |rotate_commands| { - var has_commands = has_commands_head; - - while (has_commands) |commands| : (has_commands = commands.has_next) { - commands.rotate(); - } - - rotate_commands.finished.set(); - }, - .shutdown => { break; }, diff --git a/src/ona/gfx/textures.zig b/src/ona/gfx/textures.zig new file mode 100644 index 0000000..206a49f --- /dev/null +++ b/src/ona/gfx/textures.zig @@ -0,0 +1,38 @@ +const coral = @import("coral"); + +pub const Format = enum { + rgba8, + bgra8, + + pub fn byte_size(self: Format) usize { + return switch (self) { + .rgba8, .bgra8 => 4, + }; + } +}; + +pub const Desc = struct { + format: Format, + access: Access, + + pub const Access = union (enum) { + static: StaticAccess, + render: RenderAccess, + }; + + pub const StaticAccess = struct { + width: u16, + data: []const coral.io.Byte, + }; + + pub const RenderAccess = struct { + width: u16, + height: u16, + }; +}; + +pub const Handle = enum (u32) { + default, + backbuffer, + _, +}; diff --git a/src/ona/msg.zig b/src/ona/msg.zig index f90b225..becee99 100644 --- a/src/ona/msg.zig +++ b/src/ona/msg.zig @@ -63,23 +63,23 @@ pub fn Receive(comptime Message: type) type { const Self = @This(); - pub const State = struct { + pub const Param = struct { channel: *const TypedChannel, }; - pub fn bind(context: flow.system.BindContext) std.mem.Allocator.Error!State { + pub fn bind(context: flow.system.BindContext) std.mem.Allocator.Error!Param { return .{ - .channel = (try context.register_readable_resource_access(TypedChannel)) orelse set: { - try context.world.set_resource(TypedChannel.init(coral.heap.allocator)); + .channel = (try context.register_readable_state_access(TypedChannel)) orelse set: { + try context.world.set_state(TypedChannel.init(coral.heap.allocator)); - break: set (try context.register_readable_resource_access(TypedChannel)).?; + break: set (try context.register_readable_state_access(TypedChannel)).?; }, }; } - pub fn init(state: *State) Self { + pub fn init(param: *Param) Self { return .{ - .channel = state.channel, + .channel = param.channel, }; } @@ -97,16 +97,16 @@ pub fn Send(comptime Message: type) type { const Self = @This(); - pub const State = struct { + pub const Param = struct { channel: *TypedChannel, }; - pub fn bind(context: flow.system.BindContext) std.mem.Allocator.Error!State { + pub fn bind(context: flow.system.BindContext) std.mem.Allocator.Error!Param { return .{ - .channel = (try context.register_writable_resource_access(TypedChannel)) orelse set: { - try context.world.set_resource(TypedChannel.init(coral.heap.allocator)); + .channel = (try context.register_writable_state_access(TypedChannel)) orelse set: { + try context.world.set_state(TypedChannel.init(coral.heap.allocator)); - const app = context.world.get_resource(App).?; + const app = context.world.get_state(App).?; try context.world.on_event(app.events.post_update, flow.system_fn(TypedChannel.swap), .{ .label = "swap channel of " ++ @typeName(Message), @@ -116,14 +116,14 @@ pub fn Send(comptime Message: type) type { .label = "clean up channel of " ++ @typeName(Message), }); - break: set (try context.register_writable_resource_access(TypedChannel)).?; + break: set (try context.register_writable_state_access(TypedChannel)).?; }, }; } - pub fn init(state: *State) Self { + pub fn init(param: *Param) Self { return .{ - .channel = state.channel, + .channel = param.channel, }; } diff --git a/src/ona/ona.zig b/src/ona/ona.zig index da5b237..b14e529 100644 --- a/src/ona/ona.zig +++ b/src/ona/ona.zig @@ -72,7 +72,7 @@ pub fn start_app(setup: Setup, options: Options) anyerror!void { .exit = try world.create_event("exit"), }; - const app = try world.set_get_resource(App{ + const app = try world.set_get_state(App{ .events = &events, .target_frame_time = 1.0 / @as(f64, @floatFromInt(options.tick_rate)), .elapsed_time = 0,