From 04e8d69c375b716e4b02e8650627cb56f4d743d6 Mon Sep 17 00:00:00 2001 From: kayomn Date: Thu, 20 Jun 2024 21:54:00 +0100 Subject: [PATCH 1/6] Refactor rendering logic ahead of new asset pipeline --- src/coral/World.zig | 2 +- src/coral/coral.zig | 2 - src/coral/dag.zig | 4 +- src/coral/debug.zig | 5 - src/coral/resource.zig | 2 +- src/coral/stack.zig | 60 ++-- src/coral/system.zig | 17 +- src/coral/utf8.zig | 6 +- src/main.zig | 35 +- src/ona/gfx.zig | 254 +++++++++------ src/ona/gfx/Device.zig | 534 ------------------------------ src/ona/gfx/Queue.zig | 309 ------------------ src/ona/gfx/commands.zig | 80 +++++ src/ona/gfx/device.zig | 680 +++++++++++++++++++++++++++++++++++++++ src/ona/gfx/formats.zig | 9 + src/ona/msg.zig | 10 +- 16 files changed, 993 insertions(+), 1016 deletions(-) delete mode 100644 src/coral/debug.zig delete mode 100644 src/ona/gfx/Device.zig delete mode 100644 src/ona/gfx/Queue.zig create mode 100644 src/ona/gfx/commands.zig create mode 100644 src/ona/gfx/device.zig create mode 100644 src/ona/gfx/formats.zig diff --git a/src/coral/World.zig b/src/coral/World.zig index d2509ec..e7329ac 100644 --- a/src/coral/World.zig +++ b/src/coral/World.zig @@ -32,7 +32,7 @@ pub fn create_event(self: *Self, label: []const u8) std.mem.Allocator.Error!Even const index = self.event_systems.len(); - try self.event_systems.push(systems); + try self.event_systems.push_grow(systems); return @enumFromInt(index); } diff --git a/src/coral/coral.zig b/src/coral/coral.zig index 0ceb622..24c5946 100644 --- a/src/coral/coral.zig +++ b/src/coral/coral.zig @@ -2,8 +2,6 @@ pub const ascii = @import("./ascii.zig"); pub const dag = @import("./dag.zig"); -pub const debug = @import("./debug.zig"); - pub const hashes = @import("./hashes.zig"); pub const heap = @import("./heap.zig"); diff --git a/src/coral/dag.zig b/src/coral/dag.zig index 7761208..5aafa3c 100644 --- a/src/coral/dag.zig +++ b/src/coral/dag.zig @@ -21,7 +21,7 @@ pub fn Graph(comptime Payload: type) type { pub fn append(self: *Self, payload: Payload) std.mem.Allocator.Error!Node { const node = @as(Node, @enumFromInt(self.table.len())); - try self.table.push(.{ + try self.table.push_grow(.{ .payload = payload, .edges = .{.allocator = self.table.allocator}, }); @@ -83,7 +83,7 @@ pub fn Graph(comptime Payload: type) type { }; if (slices.index_of(edges.values, 0, edge_node) == null) { - try edges.push(edge_node); + try edges.push_grow(edge_node); } return true; diff --git a/src/coral/debug.zig b/src/coral/debug.zig deleted file mode 100644 index 4e5caaa..0000000 --- a/src/coral/debug.zig +++ /dev/null @@ -1,5 +0,0 @@ -const std = @import("std"); - -pub fn assert_ok(error_union: anytype) @typeInfo(@TypeOf(error_union)).ErrorUnion.payload { - return error_union catch unreachable; -} diff --git a/src/coral/resource.zig b/src/coral/resource.zig index 617c38b..d1640f7 100644 --- a/src/coral/resource.zig +++ b/src/coral/resource.zig @@ -53,7 +53,7 @@ pub const Table = struct { errdefer resource_allocator.destroy(allocated_resource); std.debug.assert(try self.table.emplace(value_id, .{ - .ptr = allocated_resource, + .ptr = @ptrCast(allocated_resource), })); allocated_resource.* = value; diff --git a/src/coral/stack.zig b/src/coral/stack.zig index 8c17d73..cb70bd9 100644 --- a/src/coral/stack.zig +++ b/src/coral/stack.zig @@ -29,6 +29,10 @@ pub fn Sequential(comptime Value: type) type { } pub fn grow(self: *Self, additional: usize) std.mem.Allocator.Error!void { + if (additional == 0) { + return; + } + const grown_capacity = self.cap + additional; const buffer = try self.allocator.alloc(Value, grown_capacity); @@ -87,7 +91,39 @@ pub fn Sequential(comptime Value: type) type { return true; } - pub fn push(self: *Self, value: Value) std.mem.Allocator.Error!void { + pub fn push(self: *Self, value: Value) bool { + if (self.values.len == self.cap) { + return false; + } + + const offset_index = self.values.len; + + self.values = self.values.ptr[0 .. self.values.len + 1]; + + self.values[offset_index] = value; + + return true; + } + + pub fn push_all(self: *Self, values: []const Value) bool { + const new_length = self.values.len + values.len; + + if (new_length > self.cap) { + return false; + } + + const offset_index = self.values.len; + + self.values = self.values.ptr[0 .. new_length]; + + for (0 .. values.len) |index| { + self.values[offset_index + index] = values[index]; + } + + return true; + } + + pub fn push_grow(self: *Self, value: Value) std.mem.Allocator.Error!void { if (self.values.len == self.cap) { try self.grow(@max(1, self.cap)); } @@ -99,27 +135,11 @@ pub fn Sequential(comptime Value: type) type { self.values[offset_index] = value; } - pub fn push_all(self: *Self, values: []const Value) std.mem.Allocator.Error!void { - const new_length = self.values.len + values.len; - - if (new_length > self.cap) { - try self.grow(new_length); - } - - const offset_index = self.values.len; - - self.values = self.values.ptr[0 .. new_length]; - - for (0 .. values.len) |index| { - self.values[offset_index + index] = values[index]; - } - } - pub fn push_many(self: *Self, n: usize, value: Value) std.mem.Allocator.Error!void { const new_length = self.values.len + n; if (new_length > self.cap) { - try self.grow(new_length); + return false; } const offset_index = self.values.len; @@ -129,6 +149,8 @@ pub fn Sequential(comptime Value: type) type { for (0 .. n) |index| { self.values[offset_index + index] = value; } + + return true; } pub fn resize(self: *Self, size: usize, default_value: Value) std.mem.Allocator.Error!void { @@ -237,7 +259,7 @@ pub fn Parallel(comptime Value: type) type { return self.values.len; } - pub fn push(self: *Self, value: Value) std.mem.Allocator.Error!void { + pub fn push_grow(self: *Self, value: Value) std.mem.Allocator.Error!void { if (self.len() == self.cap) { try self.grow(@max(1, self.cap)); } diff --git a/src/coral/system.zig b/src/coral/system.zig index cabef6b..2224690 100644 --- a/src/coral/system.zig +++ b/src/coral/system.zig @@ -51,7 +51,7 @@ pub const BindContext = struct { const id = resource.type_id(Resource); if (!self.accesses_resource(.read_write, id)) { - try self.systems.graph.get_ptr(self.node).?.resource_accesses.push(.{.read_write = id}); + try self.systems.graph.get_ptr(self.node).?.resource_accesses.push_grow(.{.read_write = id}); } const read_write_resource_nodes = lazily_create: { @@ -65,7 +65,7 @@ pub const BindContext = struct { }; if (slices.index_of(read_write_resource_nodes.values, 0, self.node) == null) { - try read_write_resource_nodes.push(self.node); + try read_write_resource_nodes.push_grow(self.node); } return value; @@ -79,7 +79,7 @@ pub const BindContext = struct { const id = resource.type_id(Resource); if (!self.accesses_resource(.read_only, id)) { - try self.systems.graph.get_ptr(self.node).?.resource_accesses.push(.{.read_only = id}); + try self.systems.graph.get_ptr(self.node).?.resource_accesses.push_grow(.{.read_only = id}); } const read_only_resource_nodes = lazily_create: { @@ -93,7 +93,7 @@ pub const BindContext = struct { }; if (slices.index_of(read_only_resource_nodes.values, 0, self.node) == null) { - try read_only_resource_nodes.push(self.node); + try read_only_resource_nodes.push_grow(self.node); } return value; @@ -291,7 +291,7 @@ pub const Schedule = struct { try populate_bundle(bundle, graph, edge); } - try bundle.push(node); + try bundle.push_grow(node); } fn sort(schedule: *Schedule) !void { @@ -304,7 +304,7 @@ pub const Schedule = struct { continue; } - try schedule.parallel_work_bundles.push(.{.allocator = heap.allocator}); + try schedule.parallel_work_bundles.push_grow(.{.allocator = heap.allocator}); const bundle = schedule.parallel_work_bundles.get_ptr().?; @@ -328,8 +328,9 @@ pub const Schedule = struct { .main => { const extracted_work = work.values[index ..]; - try schedule.blocking_work.push_all(extracted_work); + try schedule.blocking_work.grow(extracted_work.len); + std.debug.assert(schedule.blocking_work.push_all(extracted_work)); std.debug.assert(work.pop_many(extracted_work.len)); }, } @@ -488,7 +489,7 @@ pub const Schedule = struct { }); } - try nodes.push(node); + try nodes.push_grow(node); self.invalidate_work(); } diff --git a/src/coral/utf8.zig b/src/coral/utf8.zig index a0971e0..2d02b5c 100644 --- a/src/coral/utf8.zig +++ b/src/coral/utf8.zig @@ -2,8 +2,6 @@ const ascii = @import("./ascii.zig"); const coral = @import("./coral.zig"); -const debug = @import("./debug.zig"); - const io = @import("./io.zig"); const std = @import("std"); @@ -16,7 +14,7 @@ pub fn alloc_formatted(allocator: std.mem.Allocator, comptime format: []const u8 errdefer buffer.deinit(); - debug.assert_try(print_formatted(buffer.writer(), format, args)); + print_formatted(buffer.writer(), format, args) catch unreachable; return buffer.to_allocation(formatted_len, 0); } @@ -24,7 +22,7 @@ pub fn alloc_formatted(allocator: std.mem.Allocator, comptime format: []const u8 fn count_formatted(comptime format: []const u8, args: anytype) usize { var count = io.defaultWritable{}; - debug.assert_try(print_formatted(count.writer(), format, args)); + print_formatted(count.writer(), format, args) catch unreachable; return count.written; } diff --git a/src/main.zig b/src/main.zig index 0512d73..c5d050d 100644 --- a/src/main.zig +++ b/src/main.zig @@ -21,45 +21,28 @@ pub fn main() !void { }); } -fn load(display: coral.ReadBlocking(ona.gfx.Display), actors: coral.Write(Actors), gfx: ona.gfx.Work) !void { - display.res.resize(1280, 720); +fn load(display: coral.Write(ona.gfx.Display), actors: coral.Write(Actors), assets: coral.Write(ona.gfx.Assets)) !void { + display.res.width, display.res.height = .{1280, 720}; + actors.res.body_texture = try assets.res.open_file("actor.bmp"); - const crap = [_]u32{ - 0xFFFFFFFF, 0xFF000000, 0xFFFFFFFF, 0xFF000000, - 0xFF000000, 0xFFFFFFFF, 0xFF000000, 0xFFFFFFFF, - 0xFFFFFFFF, 0xFF000000, 0xFFFFFFFF, 0xFF000000, - 0xFF000000, 0xFFFFFFFF, 0xFF000000, 0xFFFFFFFF, - }; - - actors.res.body_texture = try gfx.queue.open(.{ - .resource = .{ - .texture = .{ - .data = coral.io.bytes_of(&crap), - .width = 4, - .access = .static, - .format = .bgra8888, - }, - }, - }); - - try actors.res.instances.push(.{0, 0}); + try actors.res.instances.push_grow(.{0, 0}); } fn exit(actors: coral.Write(Actors)) void { actors.res.instances.deinit(); } -fn render(gfx: ona.gfx.Work, actors: coral.Write(Actors)) !void { +fn render(queue: ona.gfx.Queue, actors: coral.Write(Actors), assets: coral.Read(ona.gfx.Assets)) !void { for (actors.res.instances.values) |instance| { - try gfx.queue.draw(.{ + try queue.commands.append(.{ .instance_2d = .{ - .mesh_2d = gfx.primitives.quad_mesh, + .mesh_2d = assets.res.primitives.quad_mesh, .texture = actors.res.body_texture, .transform = .{ .origin = instance, - .xbasis = .{100, 0}, - .ybasis = .{0, 100}, + .xbasis = .{64, 0}, + .ybasis = .{0, 64}, }, }, }); diff --git a/src/ona/gfx.zig b/src/ona/gfx.zig index f934cd2..074485a 100644 --- a/src/ona/gfx.zig +++ b/src/ona/gfx.zig @@ -1,10 +1,10 @@ const App = @import("./App.zig"); -pub const Queue = @import("./gfx/Queue.zig"); - const coral = @import("coral"); -const Device = @import("./gfx/Device.zig"); +const formats = @import("./gfx/formats.zig"); + +const device = @import("./gfx/device.zig"); const ext = @import("./ext.zig"); @@ -12,26 +12,47 @@ const msg = @import("./msg.zig"); const std = @import("std"); +pub const Assets = struct { + context: device.Context, + primitives: Primitives, + formats: coral.stack.Sequential(Format), + + pub const Format = struct { + extension: []const u8, + open: *const fn ([]const u8) std.mem.Allocator.Error!Handle, + }; + + const Primitives = struct { + quad_mesh: Handle, + }; + + pub fn close(self: *Assets, handle: Handle) void { + return self.context.close(handle); + } + + pub fn open_file(self: *Assets, path: []const u8) std.mem.Allocator.Error!Handle { + for (self.formats.values) |format| { + if (!std.mem.endsWith(u8, path, format.extension)) { + continue; + } + + return format.open(path); + } + + return .none; + } + + pub fn open_mesh_2d(self: *Assets, mesh_2d: Mesh2D) std.mem.Allocator.Error!Handle { + return self.context.open(.{.mesh_2d = mesh_2d}); + } +}; + pub const Color = @Vector(4, f32); pub const Display = struct { - sdl_window: *ext.SDL_Window, - device: Device, - - pub fn resize(self: Display, width: u16, height: u16) void { - ext.SDL_SetWindowSize(self.sdl_window, width, height); - } - - pub fn retitle(self: Display, title: []const u8) void { - var sentineled_title = [_:0]u8{0} ** 255; - - @memcpy(sentineled_title[0 .. @min(title.len, sentineled_title.len)], title); - ext.SDL_SetWindowTitle(self.sdl_window, &sentineled_title); - } - - pub fn set_resizable(self: Display, resizable: bool) void { - ext.SDL_SetWindowResizable(self.sdl_window, @intFromBool(resizable)); - } + width: u16 = 1280, + height: u16 = 720, + clear_color: Color = colors.black, }; pub const Handle = enum (usize) { @@ -64,69 +85,86 @@ pub const Input = union (enum) { pub const Point2D = @Vector(2, f32); +pub const Mesh2D = struct { + vertices: []const Vertex, + indices: []const u16, + + pub const Vertex = struct { + xy: Point2D, + uv: Point2D, + }; +}; + +pub const Queue = struct { + commands: *device.RenderList, + + pub const State = struct { + command_index: usize, + }; + + pub fn bind(_: coral.system.BindContext) std.mem.Allocator.Error!State { + // TODO: Review how good of an idea this global state is, even if bind is guaranteed to always be ran on main. + const command_index = renders.len(); + + try renders.push_grow(device.RenderChain.init(coral.heap.allocator)); + + return .{ + .command_index = command_index, + }; + } + + pub fn init(state: *State) Queue { + return .{ + .commands = renders.values[state.command_index].pending(), + }; + } + + pub fn unbind(state: *State) void { + std.debug.assert(!renders.is_empty()); + std.mem.swap(device.RenderChain, &renders.values[state.command_index], renders.get_ptr().?); + std.debug.assert(renders.pop()); + } + + var renders = coral.stack.Sequential(device.RenderChain){.allocator = coral.heap.allocator}; +}; + +pub const Texture = struct { + data: []const coral.io.Byte, + width: u16, + format: Format, + access: Access, + + pub const Access = enum { + static, + }; + + pub const Format = enum { + rgba8888, + bgra8888, + argb8888, + rgb888, + bgr888, + + pub fn byte_size(self: Format) usize { + return switch (self) { + .rgba8888, .bgra8888, .argb8888 => 4, + .rgb888, .bgr888 => 3, + }; + } + }; +}; + pub const Transform2D = extern struct { xbasis: Point2D = .{1, 0}, ybasis: Point2D = .{0, 1}, origin: Point2D = @splat(0), }; -pub const Work = struct { - queue: *Queue.Buffer, - primitives: *const Primitives, - - const Primitives = struct { - quad_mesh: Handle, - }; - - pub const State = struct { - queue: *Queue, - primitives: *const Primitives, - }; - - pub fn bind(context: coral.system.BindContext) std.mem.Allocator.Error!State { - const queue = try Queue.create(); - - return .{ - .primitives = (try context.register_read_only_resource_access(.none, Primitives)) orelse create: { - const buffer = queue.pending(); - const half_extent = 0.5; - - try context.world.set_resource(.none, Primitives{ - .quad_mesh = try buffer.open(.{ - .label = "quad mesh primitive", - - .resource = .{ - .mesh_2d = .{ - .indices = &.{0, 1, 2, 0, 2, 3}, - - .vertices = &.{ - .{.xy = .{-half_extent, half_extent}, .uv = .{0, 1}}, - .{.xy = .{half_extent, half_extent}, .uv = .{1, 1}}, - .{.xy = .{half_extent, -half_extent}, .uv = .{1, 0}}, - .{.xy = .{-half_extent, -half_extent}, .uv = .{0, 0}}, - }, - }, - }, - }), - }); - - break: create (try context.register_read_only_resource_access(.none, Primitives)).?; - }, - - .queue = queue, - }; - } - - pub fn init(state: *State) Work { - return .{ - .queue = state.queue.pending(), - .primitives = state.primitives, - }; - } - - pub fn unbind(state: *State) void { - state.queue.release(); - } +const builtin_formats = [_]Assets.Format{ + .{ + .extension = "bmp", + .open = formats.open_bmp, + }, }; pub const colors = struct { @@ -156,43 +194,59 @@ pub fn poll(app: coral.Write(App), inputs: msg.Send(Input)) !void { } } -pub fn setup(world: *coral.World, events: App.Events) (error {SDLError} || std.Thread.SpawnError || std.mem.Allocator.Error)!void { +pub fn setup(world: *coral.World, events: App.Events) (error {Unsupported} || std.Thread.SpawnError || std.mem.Allocator.Error)!void { if (ext.SDL_Init(ext.SDL_INIT_VIDEO) != 0) { - return error.SDLError; + return error.Unsupported; } - const sdl_window = create: { - const position = ext.SDL_WINDOWPOS_CENTERED; - const flags = ext.SDL_WINDOW_OPENGL; - const width = 640; - const height = 480; + var context = try device.Context.init(); - break: create ext.SDL_CreateWindow("Ona", position, position, width, height, flags) orelse { - return error.SDLError; - }; - }; + errdefer context.deinit(); - errdefer ext.SDL_DestroyWindow(sdl_window); + var registered_formats = coral.stack.Sequential(Assets.Format){.allocator = coral.heap.allocator}; - var device = try Device.init(sdl_window); + errdefer registered_formats.deinit(); - errdefer device.deinit(); + try registered_formats.grow(builtin_formats.len); + std.debug.assert(registered_formats.push_all(&builtin_formats)); - try world.set_resource(.main, Display{ - .device = device, - .sdl_window = sdl_window, + const half_extent = 0.5; + + try world.set_resource(.none, Assets{ + .primitives = .{ + .quad_mesh = try context.open(.{ + .mesh_2d = .{ + .indices = &.{0, 1, 2, 0, 2, 3}, + + .vertices = &.{ + .{.xy = .{-half_extent, half_extent}, .uv = .{0, 1}}, + .{.xy = .{half_extent, half_extent}, .uv = .{1, 1}}, + .{.xy = .{half_extent, -half_extent}, .uv = .{1, 0}}, + .{.xy = .{-half_extent, -half_extent}, .uv = .{0, 0}}, + }, + }, + }), + }, + + .formats = registered_formats, + .context = context, }); + try world.set_resource(.none, Display{}); try world.on_event(events.pre_update, coral.system_fn(poll), .{.label = "poll gfx"}); try world.on_event(events.exit, coral.system_fn(stop), .{.label = "stop gfx"}); - try world.on_event(events.finish, coral.system_fn(submit), .{.label = "submit gfx"}); + try world.on_event(events.finish, coral.system_fn(synchronize), .{.label = "synchronize gfx"}); } -pub fn stop(display: coral.WriteBlocking(Display)) void { - display.res.device.deinit(); - ext.SDL_DestroyWindow(display.res.sdl_window); +pub fn stop(assets: coral.Write(Assets)) void { + assets.res.context.deinit(); } -pub fn submit(display: coral.WriteBlocking(Display)) void { - display.res.device.submit(); +pub fn synchronize(assets: coral.Write(Assets), display: coral.Read(Display)) !void { + assets.res.context.submit(.{ + .width = display.res.width, + .height = display.res.height, + .clear_color = display.res.clear_color, + .renders = Queue.renders.values, + }); } diff --git a/src/ona/gfx/Device.zig b/src/ona/gfx/Device.zig deleted file mode 100644 index 9b2948c..0000000 --- a/src/ona/gfx/Device.zig +++ /dev/null @@ -1,534 +0,0 @@ -const Queue = @import("./Queue.zig"); - -const coral = @import("coral"); - -const ext = @import("../ext.zig"); - -const gfx = @import("../gfx.zig"); - -const sokol = @import("sokol"); - -const std = @import("std"); - -thread: std.Thread, -clear_color: gfx.Color = gfx.colors.black, -state: *State, - -const AtomicBool = std.atomic.Value(bool); - -const Frame = struct { - width: u16, - height: u16, - flushed_instance_2d_count: usize = 0, - pushed_instance_2d_count: usize = 0, - mesh_2d: gfx.Handle = .none, - texture: gfx.Handle = .none, - - fn unflushed_instance_2d_count(self: Frame) usize { - return self.pushed_instance_2d_count - self.flushed_instance_2d_count; - } -}; - -const Render = struct { - resources: coral.stack.Sequential(Resource), - instance_2d_pipeline: sokol.gfx.Pipeline, - instance_2d_buffers: coral.stack.Sequential(sokol.gfx.Buffer), - - const Instance2D = extern struct { - transform: gfx.Transform2D, - tint: @Vector(4, u8) = @splat(std.math.maxInt(u8)), - depth: f32 = 0, - texture_offset: gfx.Point2D = @splat(0), - texture_size: gfx.Point2D = @splat(1), - - const buffer_indices = .{ - .mesh = 0, - .instance = 1, - }; - - const instances_per_buffer = 512; - - const shader = @import("./shaders/instance_2d.glsl.zig"); - }; - - const Resource = union (enum) { - empty, - mesh_2d: Mesh2D, - texture: Texture, - - const Mesh2D = struct { - index_count: u32, - vertex_buffer: sokol.gfx.Buffer, - index_buffer: sokol.gfx.Buffer, - }; - - const Texture = struct { - image: sokol.gfx.Image, - sampler: sokol.gfx.Sampler, - }; - }; - - fn deinit(self: *Render) void { - for (self.instance_2d_buffers.values) |buffer| { - sokol.gfx.destroyBuffer(buffer); - } - - self.instance_2d_buffers.deinit(); - sokol.gfx.destroyPipeline(self.instance_2d_pipeline); - self.resources.deinit(); - } - - fn init() Render { - sokol.gfx.setup(.{ - .environment = .{ - .defaults = .{ - .color_format = .RGBA8, - .depth_format = .DEPTH_STENCIL, - .sample_count = 1, - }, - }, - - .logger = .{ - .func = sokol.log.func, - }, - }); - - return .{ - .instance_2d_pipeline = sokol.gfx.makePipeline(.{ - .label = "2D drawing pipeline", - - .layout = .{ - .attrs = get: { - var attrs = [_]sokol.gfx.VertexAttrState{.{}} ** 16; - - attrs[Instance2D.shader.ATTR_vs_mesh_xy] = .{ - .format = .FLOAT2, - .buffer_index = Instance2D.buffer_indices.mesh, - }; - - attrs[Instance2D.shader.ATTR_vs_mesh_uv] = .{ - .format = .FLOAT2, - .buffer_index = Instance2D.buffer_indices.mesh, - }; - - attrs[Instance2D.shader.ATTR_vs_instance_xbasis] = .{ - .format = .FLOAT2, - .buffer_index = Instance2D.buffer_indices.instance, - }; - - attrs[Instance2D.shader.ATTR_vs_instance_ybasis] = .{ - .format = .FLOAT2, - .buffer_index = Instance2D.buffer_indices.instance, - }; - - attrs[Instance2D.shader.ATTR_vs_instance_origin] = .{ - .format = .FLOAT2, - .buffer_index = Instance2D.buffer_indices.instance, - }; - - attrs[Instance2D.shader.ATTR_vs_instance_tint] = .{ - .format = .UBYTE4N, - .buffer_index = Instance2D.buffer_indices.instance, - }; - - attrs[Instance2D.shader.ATTR_vs_instance_depth] = .{ - .format = .FLOAT, - .buffer_index = Instance2D.buffer_indices.instance, - }; - - attrs[Instance2D.shader.ATTR_vs_instance_rect] = .{ - .format = .FLOAT4, - .buffer_index = Instance2D.buffer_indices.instance, - }; - - break: get attrs; - }, - - .buffers = get: { - var buffers = [_]sokol.gfx.VertexBufferLayoutState{.{}} ** 8; - - buffers[Instance2D.buffer_indices.instance].step_func = .PER_INSTANCE; - - break: get buffers; - }, - }, - - .shader = sokol.gfx.makeShader(Instance2D.shader.draw2dShaderDesc(sokol.gfx.queryBackend())), - .index_type = .UINT16, - }), - - .instance_2d_buffers = .{.allocator = coral.heap.allocator}, - .resources = .{.allocator = coral.heap.allocator}, - }; - } - - fn insert_resource(self: *Render, handle: gfx.Handle, resource: Resource) !void { - const handle_index = handle.index() orelse { - return error.InvalidHandle; - }; - - const resource_count = self.resources.len(); - - if (handle_index < resource_count) { - const empty_resource = &self.resources.values[handle_index]; - - if (empty_resource.* != .empty) { - return error.InvalidHandle; - } - - empty_resource.* = resource; - } else { - if (handle_index != resource_count) { - return error.InvalidIndex; - } - - try self.resources.push(resource); - } - } - - fn flush_instance_2ds(self: *Render, frame: *Frame) void { - const unflushed_count = frame.unflushed_instance_2d_count(); - - if (unflushed_count == 0) { - return; - } - - sokol.gfx.applyPipeline(self.instance_2d_pipeline); - - sokol.gfx.applyUniforms(.VS, Instance2D.shader.SLOT_Screen, sokol.gfx.asRange(&Instance2D.shader.Screen{ - .screen_size = .{@floatFromInt(frame.width), @floatFromInt(frame.height)}, - })); - - const mesh_2d = self.resources.values[frame.mesh_2d.index().?].mesh_2d; - const texture = self.resources.values[frame.texture.index().?].texture; - - var bindings = sokol.gfx.Bindings{ - .vertex_buffers = get: { - var buffers = [_]sokol.gfx.Buffer{.{}} ** 8; - - buffers[Instance2D.buffer_indices.mesh] = mesh_2d.vertex_buffer; - - break: get buffers; - }, - - .index_buffer = mesh_2d.index_buffer, - - .fs = .{ - .images = get: { - var images = [_]sokol.gfx.Image{.{}} ** 12; - - images[0] = texture.image; - - break: get images; - }, - - .samplers = get: { - var samplers = [_]sokol.gfx.Sampler{.{}} ** 8; - - samplers[0] = texture.sampler; - - break: get samplers; - }, - }, - }; - - while (frame.flushed_instance_2d_count < frame.pushed_instance_2d_count) { - const buffer_index = frame.flushed_instance_2d_count / Instance2D.instances_per_buffer; - const buffer_offset = frame.flushed_instance_2d_count % Instance2D.instances_per_buffer; - const instances_to_flush = @min(Instance2D.instances_per_buffer - buffer_offset, unflushed_count); - - bindings.vertex_buffers[Instance2D.buffer_indices.instance] = self.instance_2d_buffers.values[buffer_index]; - bindings.vertex_buffer_offsets[Instance2D.buffer_indices.instance] = @intCast(buffer_offset); - - sokol.gfx.applyBindings(bindings); - sokol.gfx.draw(0, mesh_2d.index_count, @intCast(instances_to_flush)); - - frame.flushed_instance_2d_count += instances_to_flush; - } - } - - fn push_instance_2d(self: *Render, frame: *Frame, command: Queue.DrawCommand.Instance) std.mem.Allocator.Error!void { - if (command.mesh_2d != frame.mesh_2d or command.texture != frame.texture) { - self.flush_instance_2ds(frame); - } - - frame.mesh_2d = command.mesh_2d; - frame.texture = command.texture; - - const has_filled_buffer = (frame.pushed_instance_2d_count % Instance2D.instances_per_buffer) == 0; - const pushed_buffer_count = frame.pushed_instance_2d_count / Instance2D.instances_per_buffer; - - if (has_filled_buffer and pushed_buffer_count == self.instance_2d_buffers.len()) { - const instance_buffer = sokol.gfx.makeBuffer(.{ - .size = @sizeOf(Instance2D) * Instance2D.instances_per_buffer, - .usage = .STREAM, - .label = "2D drawing instance buffer", - }); - - errdefer sokol.gfx.destroyBuffer(instance_buffer); - - try self.instance_2d_buffers.push(instance_buffer); - } - - _ = sokol.gfx.appendBuffer(self.instance_2d_buffers.get().?, sokol.gfx.asRange(&Instance2D{ - .transform = command.transform, - })); - - frame.pushed_instance_2d_count += 1; - } - - fn remove_resource(self: *Render, handle: gfx.Handle) ?Resource { - if (handle.index()) |handle_index| { - const resource = self.resources.values[handle_index]; - - if (resource != .empty) { - self.resources.values[handle_index] = .empty; - - return resource; - } - } - - return null; - } -}; - -const Self = @This(); - -const State = struct { - finished: std.Thread.Semaphore = .{}, - is_running: AtomicBool = AtomicBool.init(true), - ready: std.Thread.Semaphore = .{}, - clear_color: gfx.Color = gfx.colors.black, -}; - -pub fn deinit(self: *Self) void { - self.state.is_running.store(false, .monotonic); - self.state.ready.post(); - self.thread.join(); - coral.heap.allocator.destroy(self.state); - - self.* = undefined; -} - -pub fn init(window: *ext.SDL_Window) (std.mem.Allocator.Error || std.Thread.SpawnError)!Self { - const state = try coral.heap.allocator.create(State); - - errdefer coral.heap.allocator.destroy(state); - - state.* = .{}; - - const thread = try std.Thread.spawn(.{}, run, .{window, state}); - - thread.setName("Ona Graphics") catch { - std.log.warn("failed to name the graphics thread", .{}); - }; - - return .{ - .thread = thread, - .state = state, - }; -} - -fn process_close_command(command: Queue.CloseCommand, rendering: *Render) !void { - const resource = &rendering.resources.values[command.handle.index().?]; - - switch (resource.*) { - .empty => {}, // TODO: Handle this. - - .mesh_2d => |mesh_2d| { - sokol.gfx.destroyBuffer(mesh_2d.vertex_buffer); - sokol.gfx.destroyBuffer(mesh_2d.index_buffer); - }, - - .texture => |texture| { - sokol.gfx.destroyImage(texture.image); - sokol.gfx.destroySampler(texture.sampler); - }, - } - - resource.* = .empty; -} - -fn process_draw_command(command: Queue.DrawCommand, render: *Render, frame: *Frame) !void { - switch (command) { - .instance_2d => |instance_2d| { - try render.push_instance_2d(frame, instance_2d); - }, - - .post_process => |post_process| { - render.flush_instance_2ds(frame); - // sokol.gfx.applyPipeline(self.post_process_pipeline); - - _ = post_process; - }, - } - - render.flush_instance_2ds(frame); -} - -fn process_open_command(command: Queue.OpenCommand, render: *Render) !void { - switch (command.resource) { - .texture => |texture| { - const stride = texture.width * texture.format.byte_size(); - - const image = sokol.gfx.makeImage(.{ - .width = texture.width, - .height = @intCast(texture.data.len / stride), - - .data = .{ - .subimage = get: { - var subimage = [_][16]sokol.gfx.Range{.{.{}} ** 16} ** 6; - - subimage[0][0] = sokol.gfx.asRange(texture.data); - - break: get subimage; - }, - }, - }); - - errdefer sokol.gfx.destroyImage(image); - - const sampler = sokol.gfx.makeSampler(.{}); - - errdefer sokol.gfx.destroySampler(sampler); - - try render.insert_resource(command.handle, .{ - .texture = .{ - .sampler = sampler, - .image = image, - }, - }); - }, - - .mesh_2d => |mesh_2d| { - const index_buffer = sokol.gfx.makeBuffer(.{ - .data = sokol.gfx.asRange(mesh_2d.indices), - .type = .INDEXBUFFER, - }); - - const vertex_buffer = sokol.gfx.makeBuffer(.{ - .data = sokol.gfx.asRange(mesh_2d.vertices), - .type = .VERTEXBUFFER, - }); - - errdefer { - sokol.gfx.destroyBuffer(index_buffer); - sokol.gfx.destroyBuffer(vertex_buffer); - } - - if (mesh_2d.indices.len > std.math.maxInt(u32)) { - return error.OutOfMemory; - } - - try render.insert_resource(command.handle, .{ - .mesh_2d = .{ - .index_buffer = index_buffer, - .vertex_buffer = vertex_buffer, - .index_count = @intCast(mesh_2d.indices.len), - }, - }); - }, - } -} - -fn run(window: *ext.SDL_Window, state: *State) !void { - const context = configure_and_create: { - var result = @as(c_int, 0); - - result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_FLAGS, ext.SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); - result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_PROFILE_MASK, ext.SDL_GL_CONTEXT_PROFILE_CORE); - result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_MAJOR_VERSION, 3); - result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_MINOR_VERSION, 3); - result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_DOUBLEBUFFER, 1); - - if (result != 0) { - return error.Unsupported; - } - - break: configure_and_create ext.SDL_GL_CreateContext(window); - }; - - sokol.gfx.setup(.{ - .environment = .{ - .defaults = .{ - .color_format = .RGBA8, - .depth_format = .DEPTH_STENCIL, - .sample_count = 1, - }, - }, - - .logger = .{ - .func = sokol.log.func, - }, - }); - - defer { - sokol.gfx.shutdown(); - ext.SDL_GL_DeleteContext(context); - } - - var render = Render.init(); - - defer render.deinit(); - - state.finished.post(); - - while (state.is_running.load(.monotonic)) { - state.ready.wait(); - - defer state.finished.post(); - - var frame = init_frame: { - var width, var height = [_]c_int{0, 0}; - - ext.SDL_GL_GetDrawableSize(window, &width, &height); - std.debug.assert(width > 0 and height > 0); - - break: init_frame Frame{ - .width = @intCast(width), - .height = @intCast(height), - }; - }; - - sokol.gfx.beginPass(.{ - .swapchain = .{ - .width = frame.width, - .height = frame.height, - .sample_count = 1, - .color_format = .RGBA8, - .depth_format = .DEPTH_STENCIL, - .gl = .{.framebuffer = 0}, - }, - - .action = .{ - .colors = get: { - var actions = [_]sokol.gfx.ColorAttachmentAction{.{}} ** 4; - - actions[0] = .{ - .load_action = .CLEAR, - .clear_value = @as(sokol.gfx.Color, @bitCast(state.clear_color)), - }; - - break: get actions; - }, - }, - }); - - try Queue.visit_open_commands(process_open_command, .{&render}); - try Queue.visit_draw_commands(process_draw_command, .{&render, &frame}); - try Queue.visit_close_commands(process_close_command, .{&render}); - - sokol.gfx.endPass(); - sokol.gfx.commit(); - ext.SDL_GL_SwapWindow(window); - } -} - -pub fn submit(self: *Self) void { - self.state.finished.wait(); - - self.state.clear_color = self.clear_color; - - Queue.swap(); - self.state.ready.post(); -} diff --git a/src/ona/gfx/Queue.zig b/src/ona/gfx/Queue.zig deleted file mode 100644 index 76a2029..0000000 --- a/src/ona/gfx/Queue.zig +++ /dev/null @@ -1,309 +0,0 @@ -const coral = @import("coral"); - -const gfx = @import("../gfx.zig"); - -const std = @import("std"); - -buffers: [2]Buffer, -is_swapped: bool = false, -ref_count: AtomicCount = AtomicCount.init(1), -has_next: ?*Self = null, -has_prev: ?*Self = null, - -const AtomicCount = std.atomic.Value(usize); - -pub const Buffer = struct { - arena: std.heap.ArenaAllocator, - closed_handles: coral.stack.Sequential(usize), - open_commands: coral.stack.Sequential(OpenCommand), - draw_commands: coral.stack.Sequential(DrawCommand), - close_commands: coral.stack.Sequential(CloseCommand), - - pub fn clear(self: *Buffer) void { - self.close_commands.clear(); - self.draw_commands.clear(); - self.open_commands.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: *Buffer) void { - self.arena.deinit(); - self.closed_handles.deinit(); - self.open_commands.deinit(); - self.draw_commands.deinit(); - self.close_commands.deinit(); - } - - fn init(allocator: std.mem.Allocator) Buffer { - return .{ - .arena = std.heap.ArenaAllocator.init(allocator), - .closed_handles = .{.allocator = allocator}, - .open_commands = .{.allocator = allocator}, - .draw_commands = .{.allocator = allocator}, - .close_commands = .{.allocator = allocator}, - }; - } - - pub fn draw(self: *Buffer, command: DrawCommand) std.mem.Allocator.Error!void { - try self.draw_commands.push(switch (command) { - .instance_2d => |instance_2d| .{.instance_2d = instance_2d}, - .post_process => |post_process| .{.post_process = post_process}, - }); - } - - pub fn open(self: *Buffer, command: OpenCommand) std.mem.Allocator.Error!gfx.Handle { - const reserved_handle = @as(gfx.Handle, switch (command.handle) { - .none => @enumFromInt(reserve_handle: { - if (self.closed_handles.get()) |handle| { - std.debug.assert(self.closed_handles.pop()); - - break: reserve_handle handle; - } - - break: reserve_handle next_handle.fetchAdd(1, .monotonic); - }), - - _ => |handle| handle, - }); - - std.debug.assert(reserved_handle != .none); - - const arena_allocator = self.arena.allocator(); - - try self.open_commands.push(.{ - .resource = switch (command.resource) { - .texture => |texture| .{ - .texture = .{ - .data = try arena_allocator.dupe(coral.io.Byte, texture.data), - .width = texture.width, - .format = texture.format, - .access = texture.access, - }, - }, - - .mesh_2d => |mesh_2d| .{ - .mesh_2d = .{ - .indices = try arena_allocator.dupe(u16, mesh_2d.indices), - .vertices = try arena_allocator.dupe(OpenCommand.Mesh2D.Vertex, mesh_2d.vertices), - }, - }, - }, - - .label = if (command.label) |label| try arena_allocator.dupe(coral.io.Byte, label) else null, - .handle = reserved_handle, - }); - - return reserved_handle; - } -}; - -pub const CloseCommand = struct { - handle: gfx.Handle, -}; - -pub const DrawCommand = union (enum) { - instance_2d: Instance, - post_process: PostProcess, - - pub const Instance = struct { - texture: gfx.Handle, - mesh_2d: gfx.Handle, - transform: gfx.Transform2D, - }; - - pub const PostProcess = struct { - - }; -}; - -pub const OpenCommand = struct { - handle: gfx.Handle = .none, - label: ?[]const u8 = null, - - resource: union (enum) { - texture: Texture, - mesh_2d: Mesh2D, - }, - - pub const Mesh2D = struct { - vertices: []const Vertex, - indices: []const u16, - - pub const Vertex = struct { - xy: gfx.Point2D, - uv: gfx.Point2D, - }; - }; - - pub const Texture = struct { - data: []const coral.io.Byte, - width: u16, - format: Format, - access: Access, - - pub const Access = enum { - static, - }; - - pub const Format = enum { - rgba8888, - bgra8888, - argb8888, - rgb888, - bgr888, - - pub fn byte_size(self: Format) usize { - return switch (self) { - .rgba8888, .bgra8888, .argb8888 => 4, - .rgb888, .bgr888 => 3, - }; - } - }; - }; -}; - -pub fn acquire(self: *Self) void { - self.ref_count.fetchAdd(1, .monotonic); -} - -pub fn create() std.mem.Allocator.Error!*Self { - const queue = try coral.heap.allocator.create(Self); - - errdefer coral.heap.allocator.destroy(queue); - - queue.* = .{ - .buffers = .{Buffer.init(coral.heap.allocator), Buffer.init(coral.heap.allocator)}, - }; - - mutex.lock(); - - defer mutex.unlock(); - - if (has_tail) |tail| { - tail.has_next = queue; - queue.has_prev = tail; - } else { - std.debug.assert(has_head == null); - - has_head = queue; - } - - has_tail = queue; - - return queue; -} - -pub fn pending(self: *Self) *Buffer { - return &self.buffers[@intFromBool(self.is_swapped)]; -} - -pub fn release(self: *Self) void { - if (self.ref_count.fetchSub(1, .monotonic) == 1) { - mutex.lock(); - - defer mutex.unlock(); - - if (self.has_prev) |prev| { - prev.has_next = self.has_next; - } else { - has_head = self.has_next; - } - - if (self.has_next) |next| { - next.has_prev = self.has_prev; - } else { - has_tail = self.has_prev; - } - - for (&self.buffers) |*buffer| { - buffer.deinit(); - } - - coral.heap.allocator.destroy(self); - } -} - -pub fn submitted(self: *Self) *Buffer { - return &self.buffers[@intFromBool(!self.is_swapped)]; -} - -const Self = @This(); - -var has_head = @as(?*Self, null); - -var has_tail = @as(?*Self, null); - -var mutex = std.Thread.Mutex{}; - -var next_handle = AtomicCount.init(1); - -pub fn swap() void { - mutex.lock(); - - defer mutex.unlock(); - - var has_node = has_head; - - while (has_node) |node| : (has_node = node.has_next) { - node.is_swapped = !node.is_swapped; - - node.pending().clear(); - } -} - -pub fn visit_close_commands(visit: anytype, args: anytype) !void { - mutex.lock(); - - defer mutex.unlock(); - - var has_node = has_head; - var iterations = @as(usize, 0); - - while (has_node) |node| : ({ - has_node = node.has_next; - iterations += 1; - }) { - for (node.submitted().close_commands.values) |command| { - try @call(.auto, visit, .{command} ++ args); - } - } -} - -pub fn visit_draw_commands(visit: anytype, args: anytype) !void { - mutex.lock(); - - defer mutex.unlock(); - - var has_node = has_head; - var iterations = @as(usize, 0); - - while (has_node) |node| : ({ - has_node = node.has_next; - iterations += 1; - }) { - for (node.submitted().draw_commands.values) |command| { - try @call(.auto, visit, .{command} ++ args); - } - } -} - -pub fn visit_open_commands(visit: anytype, args: anytype) !void { - mutex.lock(); - - defer mutex.unlock(); - - var has_node = has_head; - var iterations = @as(usize, 0); - - while (has_node) |node| : ({ - has_node = node.has_next; - iterations += 1; - }) { - for (node.submitted().open_commands.values) |command| { - try @call(.auto, visit, .{command} ++ args); - } - } -} diff --git a/src/ona/gfx/commands.zig b/src/ona/gfx/commands.zig new file mode 100644 index 0000000..cece28c --- /dev/null +++ b/src/ona/gfx/commands.zig @@ -0,0 +1,80 @@ +const coral = @import("coral"); + +const std = @import("std"); + +pub fn Chain(comptime Command: type, comptime clone_command: ?Clone(Command)) type { + return struct { + swap_lists: [2]CommandList, + swap_state: u1 = 0, + + const CommandList = List(Command, clone_command); + + 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 = .{CommandList.init(allocator), CommandList.init(allocator)}, + }; + } + + pub fn pending(self: *Self) *CommandList { + return &self.swap_lists[self.swap_state]; + } + + pub fn submitted(self: *Self) *CommandList { + return &self.swap_lists[self.swap_state ^ 1]; + } + + pub fn swap(self: *Self) void { + self.swap_state ^= 1; + } + }; +} + +pub fn Clone(comptime Command: type) type { + return fn (Command, *std.heap.ArenaAllocator) std.mem.Allocator.Error!Command; +} + +pub fn List(comptime Command: type, comptime clone_command: ?Clone(Command)) type { + return struct { + arena: std.heap.ArenaAllocator, + stack: coral.stack.Sequential(Command), + + const Self = @This(); + + pub fn append(self: *Self, command: Command) std.mem.Allocator.Error!void { + return self.stack.push_grow(if (clone_command) |clone| try clone(command, &self.arena) else command); + } + + pub fn clear(self: *Self) 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", .{}); + } + } + + pub fn deinit(self: *Self) void { + self.arena.deinit(); + self.stack.deinit(); + + self.* = undefined; + } + + pub fn init(allocator: std.mem.Allocator) Self { + return .{ + .arena = std.heap.ArenaAllocator.init(allocator), + .stack = .{.allocator = allocator}, + }; + } + }; +} + diff --git a/src/ona/gfx/device.zig b/src/ona/gfx/device.zig new file mode 100644 index 0000000..f1284f3 --- /dev/null +++ b/src/ona/gfx/device.zig @@ -0,0 +1,680 @@ +const commands = @import("./commands.zig"); + +const coral = @import("coral"); + +const ext = @import("../ext.zig"); + +const gfx = @import("../gfx.zig"); + +const sokol = @import("sokol"); + +const std = @import("std"); + +pub const Asset = union (enum) { + texture: gfx.Texture, + mesh_2d: gfx.Mesh2D, +}; + +pub const Context = struct { + window: *ext.SDL_Window, + thread: std.Thread, + loop: *Loop, + + pub const Submission = struct { + width: u16, + height: u16, + clear_color: gfx.Color = gfx.colors.black, + renders: []RenderChain, + }; + + pub fn close(self: *Context, handle: gfx.Handle) void { + const handle_index = handle.index() orelse { + return; + }; + + const close_commands = self.loop.closes.pending(); + + std.debug.assert(close_commands.stack.cap > close_commands.stack.len()); + close_commands.append(.{.index = handle_index}) catch unreachable; + } + + pub fn deinit(self: *Context) void { + self.loop.is_running.store(false, .monotonic); + self.loop.ready.post(); + self.thread.join(); + coral.heap.allocator.destroy(self.loop); + ext.SDL_DestroyWindow(self.window); + + self.* = undefined; + } + + pub fn init() !Context { + const window = create: { + const position = ext.SDL_WINDOWPOS_CENTERED; + const flags = ext.SDL_WINDOW_OPENGL; + const width = 640; + const height = 480; + + break: create ext.SDL_CreateWindow("Ona", position, position, width, height, flags) orelse { + return error.Unsupported; + }; + }; + + errdefer ext.SDL_DestroyWindow(window); + + const loop = try coral.heap.allocator.create(Loop); + + errdefer coral.heap.allocator.destroy(loop); + + loop.* = .{}; + + return .{ + .loop = loop, + .window = window, + + .thread = spawn: { + const thread = try std.Thread.spawn(.{}, Loop.run, .{loop, window}); + + thread.setName("Ona Graphics") catch { + std.log.warn("failed to name the graphics thread", .{}); + }; + + break: spawn thread; + }, + }; + } + + pub fn open(self: *Context, asset: Asset) std.mem.Allocator.Error!gfx.Handle { + const open_commands = self.loop.opens.pending(); + const index = self.loop.closed_indices.get() orelse open_commands.stack.len(); + + try open_commands.append(.{ + .index = index, + .payload = asset, + }); + + try self.loop.closes.pending().stack.grow(1); + + _ = self.loop.closed_indices.pop(); + + return @enumFromInt(index + 1); + } + + pub fn submit(self: *Context, submission: Submission) void { + self.loop.finished.wait(); + + defer self.loop.ready.post(); + + for (submission.renders) |*render| { + render.swap(); + } + + self.loop.opens.swap(); + self.loop.closes.swap(); + + var last_width, var last_height = [_]c_int{0, 0}; + + ext.SDL_GetWindowSize(self.window, &last_width, &last_height); + + if (submission.width != last_width or submission.height != last_height) { + ext.SDL_SetWindowSize(self.window, submission.width, submission.height); + } + + self.loop.clear_color = submission.clear_color; + self.loop.renders = submission.renders; + + self.loop.ready.post(); + } +}; + +const Loop = struct { + ready: std.Thread.Semaphore = .{}, + finished: std.Thread.Semaphore = .{}, + clear_color: gfx.Color = gfx.colors.black, + is_running: AtomicBool = AtomicBool.init(true), + renders: []RenderChain = &.{}, + closes: CloseChain = CloseChain.init(coral.heap.allocator), + opens: OpenChain = OpenChain.init(coral.heap.allocator), + closed_indices: coral.stack.Sequential(usize) = .{.allocator = coral.heap.allocator}, + + const AtomicBool = std.atomic.Value(bool); + + const CloseCommand = struct { + index: usize, + }; + + const OpenCommand = struct { + index: usize, + payload: Asset, + + fn clone(command: OpenCommand, arena: *std.heap.ArenaAllocator) std.mem.Allocator.Error!OpenCommand { + const allocator = arena.allocator(); + + return .{ + .payload = switch (command.payload) { + .texture => |texture| .{ + .texture = .{ + .data = try allocator.dupe(coral.io.Byte, texture.data), + .width = texture.width, + .format = texture.format, + .access = texture.access, + }, + }, + + .mesh_2d => |mesh_2d| .{ + .mesh_2d = .{ + .vertices = try allocator.dupe(gfx.Mesh2D.Vertex, mesh_2d.vertices), + .indices = try allocator.dupe(u16, mesh_2d.indices), + }, + }, + }, + + .index = command.index, + }; + } + }; + + const CloseChain = commands.Chain(CloseCommand, null); + + const OpenChain = commands.Chain(OpenCommand, OpenCommand.clone); + + fn run(self: *Loop, window: *ext.SDL_Window) !void { + const context = configure_and_create: { + var result = @as(c_int, 0); + + result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_FLAGS, ext.SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); + result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_PROFILE_MASK, ext.SDL_GL_CONTEXT_PROFILE_CORE); + result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_MAJOR_VERSION, 3); + result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_MINOR_VERSION, 3); + result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_DOUBLEBUFFER, 1); + + if (result != 0) { + return error.Unsupported; + } + + break: configure_and_create ext.SDL_GL_CreateContext(window); + }; + + sokol.gfx.setup(.{ + .environment = .{ + .defaults = .{ + .color_format = .RGBA8, + .depth_format = .DEPTH_STENCIL, + .sample_count = 1, + }, + }, + + .logger = .{ + .func = sokol.log.func, + }, + }); + + defer { + sokol.gfx.shutdown(); + ext.SDL_GL_DeleteContext(context); + } + + var rendering = Rendering.init(); + + defer rendering.deinit(); + + self.finished.post(); + + while (self.is_running.load(.monotonic)) { + self.ready.wait(); + + defer self.finished.post(); + + const open_commands = self.opens.submitted(); + + defer open_commands.clear(); + + for (open_commands.stack.values) |command| { + switch (command.payload) { + .texture => |texture| { + const stride = texture.width * texture.format.byte_size(); + + const image = sokol.gfx.makeImage(.{ + .width = texture.width, + .height = @intCast(texture.data.len / stride), + + .data = .{ + .subimage = get: { + var subimage = [_][16]sokol.gfx.Range{.{.{}} ** 16} ** 6; + + subimage[0][0] = sokol.gfx.asRange(texture.data); + + break: get subimage; + }, + }, + }); + + errdefer sokol.gfx.destroyImage(image); + + const sampler = sokol.gfx.makeSampler(.{}); + + errdefer sokol.gfx.destroySampler(sampler); + + try rendering.insert_object(command.index, .{ + .texture = .{ + .sampler = sampler, + .image = image, + }, + }); + }, + + .mesh_2d => |mesh_2d| { + const index_buffer = sokol.gfx.makeBuffer(.{ + .data = sokol.gfx.asRange(mesh_2d.indices), + .type = .INDEXBUFFER, + }); + + const vertex_buffer = sokol.gfx.makeBuffer(.{ + .data = sokol.gfx.asRange(mesh_2d.vertices), + .type = .VERTEXBUFFER, + }); + + errdefer { + sokol.gfx.destroyBuffer(index_buffer); + sokol.gfx.destroyBuffer(vertex_buffer); + } + + if (mesh_2d.indices.len > std.math.maxInt(u32)) { + return error.OutOfMemory; + } + + try rendering.insert_object(command.index, .{ + .mesh_2d = .{ + .index_buffer = index_buffer, + .vertex_buffer = vertex_buffer, + .index_count = @intCast(mesh_2d.indices.len), + }, + }); + }, + } + } + + var frame = init_frame: { + var width, var height = [_]c_int{0, 0}; + + ext.SDL_GL_GetDrawableSize(window, &width, &height); + std.debug.assert(width > 0 and height > 0); + + break: init_frame Rendering.Frame{ + .width = @intCast(width), + .height = @intCast(height), + }; + }; + + sokol.gfx.beginPass(.{ + .swapchain = .{ + .width = frame.width, + .height = frame.height, + .sample_count = 1, + .color_format = .RGBA8, + .depth_format = .DEPTH_STENCIL, + .gl = .{.framebuffer = 0}, + }, + + .action = .{ + .colors = get: { + var actions = [_]sokol.gfx.ColorAttachmentAction{.{}} ** 4; + + actions[0] = .{ + .load_action = .CLEAR, + .clear_value = @as(sokol.gfx.Color, @bitCast(self.clear_color)), + }; + + break: get actions; + }, + }, + }); + + for (self.renders) |*render| { + const render_commands = render.submitted(); + + defer render_commands.clear(); + + for (render_commands.stack.values) |command| { + switch (command) { + .instance_2d => |instance_2d| { + try rendering.push_instance_2d(&frame, instance_2d); + }, + + .post_process => |post_process| { + rendering.flush_instance_2ds(&frame); + // sokol.gfx.applyPipeline(self.post_process_pipeline); + + _ = post_process; + }, + } + } + } + + rendering.flush_instance_2ds(&frame); + sokol.gfx.endPass(); + sokol.gfx.commit(); + ext.SDL_GL_SwapWindow(window); + + const close_commands = self.closes.submitted(); + + defer close_commands.clear(); + + for (close_commands.stack.values) |command| { + const object = &rendering.objects.values[command.index]; + + switch (object.*) { + .empty => {}, // TODO: Handle double-closes. + + .mesh_2d => |mesh_2d| { + sokol.gfx.destroyBuffer(mesh_2d.vertex_buffer); + sokol.gfx.destroyBuffer(mesh_2d.index_buffer); + }, + + .texture => |texture| { + sokol.gfx.destroyImage(texture.image); + sokol.gfx.destroySampler(texture.sampler); + }, + } + + object.* = .empty; + } + } + } +}; + +const Rendering = struct { + objects: coral.stack.Sequential(Object), + instance_2d_pipeline: sokol.gfx.Pipeline, + instance_2d_buffers: coral.stack.Sequential(sokol.gfx.Buffer), + + const Instance2D = extern struct { + transform: gfx.Transform2D, + tint: @Vector(4, u8) = @splat(std.math.maxInt(u8)), + depth: f32 = 0, + texture_offset: gfx.Point2D = @splat(0), + texture_size: gfx.Point2D = @splat(1), + + const buffer_indices = .{ + .mesh = 0, + .instance = 1, + }; + + const instances_per_buffer = 512; + + const shader = @import("./shaders/instance_2d.glsl.zig"); + }; + + const Frame = struct { + width: u16, + height: u16, + flushed_instance_2d_count: usize = 0, + pushed_instance_2d_count: usize = 0, + mesh_2d: gfx.Handle = .none, + texture: gfx.Handle = .none, + + fn unflushed_instance_2d_count(self: Frame) usize { + return self.pushed_instance_2d_count - self.flushed_instance_2d_count; + } + }; + + const Object = union (enum) { + empty, + + mesh_2d: struct { + index_count: u32, + vertex_buffer: sokol.gfx.Buffer, + index_buffer: sokol.gfx.Buffer, + }, + + texture: struct { + image: sokol.gfx.Image, + sampler: sokol.gfx.Sampler, + }, + }; + + fn deinit(self: *Rendering) void { + for (self.instance_2d_buffers.values) |buffer| { + sokol.gfx.destroyBuffer(buffer); + } + + self.instance_2d_buffers.deinit(); + sokol.gfx.destroyPipeline(self.instance_2d_pipeline); + self.objects.deinit(); + } + + fn init() Rendering { + sokol.gfx.setup(.{ + .environment = .{ + .defaults = .{ + .color_format = .RGBA8, + .depth_format = .DEPTH_STENCIL, + .sample_count = 1, + }, + }, + + .logger = .{ + .func = sokol.log.func, + }, + }); + + return .{ + .instance_2d_pipeline = sokol.gfx.makePipeline(.{ + .label = "2D drawing pipeline", + + .layout = .{ + .attrs = get: { + var attrs = [_]sokol.gfx.VertexAttrState{.{}} ** 16; + + attrs[Instance2D.shader.ATTR_vs_mesh_xy] = .{ + .format = .FLOAT2, + .buffer_index = Instance2D.buffer_indices.mesh, + }; + + attrs[Instance2D.shader.ATTR_vs_mesh_uv] = .{ + .format = .FLOAT2, + .buffer_index = Instance2D.buffer_indices.mesh, + }; + + attrs[Instance2D.shader.ATTR_vs_instance_xbasis] = .{ + .format = .FLOAT2, + .buffer_index = Instance2D.buffer_indices.instance, + }; + + attrs[Instance2D.shader.ATTR_vs_instance_ybasis] = .{ + .format = .FLOAT2, + .buffer_index = Instance2D.buffer_indices.instance, + }; + + attrs[Instance2D.shader.ATTR_vs_instance_origin] = .{ + .format = .FLOAT2, + .buffer_index = Instance2D.buffer_indices.instance, + }; + + attrs[Instance2D.shader.ATTR_vs_instance_tint] = .{ + .format = .UBYTE4N, + .buffer_index = Instance2D.buffer_indices.instance, + }; + + attrs[Instance2D.shader.ATTR_vs_instance_depth] = .{ + .format = .FLOAT, + .buffer_index = Instance2D.buffer_indices.instance, + }; + + attrs[Instance2D.shader.ATTR_vs_instance_rect] = .{ + .format = .FLOAT4, + .buffer_index = Instance2D.buffer_indices.instance, + }; + + break: get attrs; + }, + + .buffers = get: { + var buffers = [_]sokol.gfx.VertexBufferLayoutState{.{}} ** 8; + + buffers[Instance2D.buffer_indices.instance].step_func = .PER_INSTANCE; + + break: get buffers; + }, + }, + + .shader = sokol.gfx.makeShader(Instance2D.shader.draw2dShaderDesc(sokol.gfx.queryBackend())), + .index_type = .UINT16, + }), + + .instance_2d_buffers = .{.allocator = coral.heap.allocator}, + .objects = .{.allocator = coral.heap.allocator}, + }; + } + + fn flush_instance_2ds(self: *Rendering, frame: *Frame) void { + const unflushed_count = frame.unflushed_instance_2d_count(); + + if (unflushed_count == 0) { + return; + } + + sokol.gfx.applyPipeline(self.instance_2d_pipeline); + + sokol.gfx.applyUniforms(.VS, Instance2D.shader.SLOT_Screen, sokol.gfx.asRange(&Instance2D.shader.Screen{ + .screen_size = .{@floatFromInt(frame.width), @floatFromInt(frame.height)}, + })); + + const mesh_2d = self.objects.values[frame.mesh_2d.index().?].mesh_2d; + const texture = self.objects.values[frame.texture.index().?].texture; + + var bindings = sokol.gfx.Bindings{ + .vertex_buffers = get: { + var buffers = [_]sokol.gfx.Buffer{.{}} ** 8; + + buffers[Instance2D.buffer_indices.mesh] = mesh_2d.vertex_buffer; + + break: get buffers; + }, + + .index_buffer = mesh_2d.index_buffer, + + .fs = .{ + .images = get: { + var images = [_]sokol.gfx.Image{.{}} ** 12; + + images[0] = texture.image; + + break: get images; + }, + + .samplers = get: { + var samplers = [_]sokol.gfx.Sampler{.{}} ** 8; + + samplers[0] = texture.sampler; + + break: get samplers; + }, + }, + }; + + while (frame.flushed_instance_2d_count < frame.pushed_instance_2d_count) { + const buffer_index = frame.flushed_instance_2d_count / Instance2D.instances_per_buffer; + const buffer_offset = frame.flushed_instance_2d_count % Instance2D.instances_per_buffer; + const instances_to_flush = @min(Instance2D.instances_per_buffer - buffer_offset, unflushed_count); + + bindings.vertex_buffers[Instance2D.buffer_indices.instance] = self.instance_2d_buffers.values[buffer_index]; + bindings.vertex_buffer_offsets[Instance2D.buffer_indices.instance] = @intCast(buffer_offset); + + sokol.gfx.applyBindings(bindings); + sokol.gfx.draw(0, mesh_2d.index_count, @intCast(instances_to_flush)); + + frame.flushed_instance_2d_count += instances_to_flush; + } + } + + fn insert_object(self: *Rendering, index: usize, object: Object) !void { + const resource_count = self.objects.len(); + + if (index < resource_count) { + const empty_object = &self.objects.values[index]; + + if (empty_object.* != .empty) { + return error.InvalidHandle; + } + + empty_object.* = object; + } else { + if (index != resource_count) { + return error.InvalidIndex; + } + + try self.objects.push_grow(object); + } + } + + fn push_instance_2d(self: *Rendering, frame: *Frame, command: RenderCommand.Instance) std.mem.Allocator.Error!void { + if (command.mesh_2d != frame.mesh_2d or command.texture != frame.texture) { + self.flush_instance_2ds(frame); + } + + frame.mesh_2d = command.mesh_2d; + frame.texture = command.texture; + + const has_filled_buffer = (frame.pushed_instance_2d_count % Instance2D.instances_per_buffer) == 0; + const pushed_buffer_count = frame.pushed_instance_2d_count / Instance2D.instances_per_buffer; + + if (has_filled_buffer and pushed_buffer_count == self.instance_2d_buffers.len()) { + const instance_buffer = sokol.gfx.makeBuffer(.{ + .size = @sizeOf(Instance2D) * Instance2D.instances_per_buffer, + .usage = .STREAM, + .label = "2D drawing instance buffer", + }); + + errdefer sokol.gfx.destroyBuffer(instance_buffer); + + try self.instance_2d_buffers.push_grow(instance_buffer); + } + + _ = sokol.gfx.appendBuffer(self.instance_2d_buffers.get().?, sokol.gfx.asRange(&Instance2D{ + .transform = command.transform, + })); + + frame.pushed_instance_2d_count += 1; + } + + fn remove_object(self: *Rendering, index: usize) ?Object { + const object = self.objects.values[index]; + + if (object != .empty) { + self.objects.values[index] = .empty; + + return object; + } + + return null; + } +}; + +pub const RenderCommand = union (enum) { + instance_2d: Instance, + post_process: PostProcess, + + pub const Instance = struct { + texture: gfx.Handle, + mesh_2d: gfx.Handle, + transform: gfx.Transform2D, + }; + + pub const PostProcess = struct { + + }; + + fn clone(self: RenderCommand, arena: *std.heap.ArenaAllocator) std.mem.Allocator.Error!RenderCommand { + _ = arena; + + return switch (self) { + .instance_2d => |instance_2d| .{.instance_2d = instance_2d}, + .post_process => |post_process| .{.post_process = post_process}, + }; + } +}; + +pub const RenderChain = commands.Chain(RenderCommand, RenderCommand.clone); + +pub const RenderList = commands.List(RenderCommand, RenderCommand.clone); diff --git a/src/ona/gfx/formats.zig b/src/ona/gfx/formats.zig new file mode 100644 index 0000000..0bb116d --- /dev/null +++ b/src/ona/gfx/formats.zig @@ -0,0 +1,9 @@ +const gfx = @import("../gfx.zig"); + +const std = @import("std"); + +pub fn open_bmp(path: []const u8) std.mem.Allocator.Error!gfx.Handle { + // TODO: Implement. + _ = path; + unreachable; +} diff --git a/src/ona/msg.zig b/src/ona/msg.zig index 6c59377..54d2f1b 100644 --- a/src/ona/msg.zig +++ b/src/ona/msg.zig @@ -7,7 +7,7 @@ const std = @import("std"); fn Channel(comptime Message: type) type { return struct { buffers: [2]coral.stack.Sequential(Message), - swapped: bool = false, + swap_index: u1 = 0, ticks: u1 = 0, const Self = @This(); @@ -28,9 +28,9 @@ fn Channel(comptime Message: type) type { channel.res.ticks = coral.scalars.add(channel.res.ticks, 1) orelse 0; if (channel.res.ticks == 0) { - channel.res.swapped = !channel.res.swapped; + channel.res.swap_index ^= 1; - channel.res.buffers[@intFromBool(channel.res.swapped)].clear(); + channel.res.buffers[channel.res.swap_index].clear(); } } @@ -44,11 +44,11 @@ fn Channel(comptime Message: type) type { } fn messages(self: Self) []const Message { - return self.buffers[@intFromBool(!self.swapped)].values; + return self.buffers[self.swap_index ^ 1].values; } fn push(self: *Self, message: Message) std.mem.Allocator.Error!void { - try self.buffers[@intFromBool(self.swapped)].push(message); + try self.buffers[self.swap_index].push_grow(message); } }; } From ee88f58e53442407fdbc3a6c88a649e82161b636 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 23 Jun 2024 02:16:04 +0100 Subject: [PATCH 2/6] Implement bitmap loading --- src/coral/coral.zig | 2 + src/coral/files.zig | 124 ++++++++++++++++++++++++++++++++ src/coral/io.zig | 96 +++++-------------------- src/main.zig | 6 +- src/ona/gfx.zig | 154 +++++++++++++++++++++++----------------- src/ona/gfx/device.zig | 30 ++++---- src/ona/gfx/formats.zig | 68 ++++++++++++++++-- 7 files changed, 317 insertions(+), 163 deletions(-) create mode 100644 src/coral/files.zig diff --git a/src/coral/coral.zig b/src/coral/coral.zig index 24c5946..d760db7 100644 --- a/src/coral/coral.zig +++ b/src/coral/coral.zig @@ -2,6 +2,8 @@ pub const ascii = @import("./ascii.zig"); pub const dag = @import("./dag.zig"); +pub const files = @import("./files.zig"); + pub const hashes = @import("./hashes.zig"); pub const heap = @import("./heap.zig"); diff --git a/src/coral/files.zig b/src/coral/files.zig new file mode 100644 index 0000000..273202f --- /dev/null +++ b/src/coral/files.zig @@ -0,0 +1,124 @@ +const builtin = @import("builtin"); + +const io = @import("./io.zig"); + +const std = @import("std"); + +pub const Error = error { + FileNotFound, + FileInaccessible, +}; + +pub const Stat = struct { + size: u64, +}; + +pub const Storage = struct { + userdata: *anyopaque, + vtable: *const VTable, + + pub const VTable = struct { + stat: *const fn (*anyopaque, []const u8) Error!Stat, + read: *const fn (*anyopaque, []const u8, usize, []io.Byte) Error!usize, + }; + + pub fn read_bytes(self: Storage, path: []const u8, offset: usize, output: []io.Byte) Error!usize { + return self.vtable.read(self.userdata, path, offset, output); + } + + pub fn read_foreign(self: Storage, path: []const u8, offset: usize, comptime Type: type) Error!?Type { + const decoded = (try self.read_native(path, offset, Type)) orelse { + return null; + }; + + return switch (@typeInfo(Type)) { + .Struct => std.mem.byteSwapAllFields(Type, &decoded), + else => @byteSwap(decoded), + }; + } + + pub fn read_native(self: Storage, path: []const u8, offset: usize, comptime Type: type) Error!?Type { + var buffer = @as([@sizeOf(Type)]io.Byte, undefined); + + if (try self.vtable.read(self.userdata, path, offset, &buffer) != buffer.len) { + return null; + } + + return @as(*align(1) const Type, @ptrCast(&buffer)).*; + } + + pub const read_little = switch (native_endian) { + .little => read_native, + .big => read_foreign, + }; + + pub const read_big = switch (native_endian) { + .little => read_foreign, + .big => read_native, + }; +}; + +pub const bundle = init: { + const Bundle = struct { + fn full_path(path: []const u8) Error![4095:0]u8 { + var buffer = [_:0]u8{0} ** 4095; + + _ = std.fs.cwd().realpath(path, &buffer) catch { + return error.FileInaccessible; + }; + + return buffer; + } + + fn read(_: *anyopaque, path: []const u8, offset: usize, output: []io.Byte) Error!usize { + var file = std.fs.openFileAbsoluteZ(&(try full_path(path)), .{.mode = .read_only}) catch |open_error| { + return switch (open_error) { + error.FileNotFound => error.FileNotFound, + else => error.FileInaccessible, + }; + }; + + defer file.close(); + + if (offset != 0) { + file.seekTo(offset) catch { + return error.FileInaccessible; + }; + } + + return file.read(output) catch error.FileInaccessible; + } + + fn stat(_: *anyopaque, path: []const u8) Error!Stat { + const file_stat = get: { + var file = std.fs.openFileAbsoluteZ(&(try full_path(path)), .{.mode = .read_only}) catch |open_error| { + return switch (open_error) { + error.FileNotFound => error.FileNotFound, + else => error.FileInaccessible, + }; + }; + + defer file.close(); + + break: get file.stat() catch { + return error.FileInaccessible; + }; + }; + + return .{ + .size = file_stat.size, + }; + } + }; + + break: init Storage{ + .userdata = undefined, + + .vtable = &.{ + .stat = Bundle.stat, + .read = Bundle.read, + }, + }; +}; + +const native_endian = builtin.cpu.arch.endian(); diff --git a/src/coral/io.zig b/src/coral/io.zig index 8177525..214ce8e 100644 --- a/src/coral/io.zig +++ b/src/coral/io.zig @@ -6,23 +6,30 @@ const slices = @import("./slices.zig"); const std = @import("std"); -pub const Byte = u8; +pub const Writable = struct { + data: []Byte, -pub const Decoder = coral.io.Functor(coral.io.Error!void, &.{[]coral.Byte}); + pub fn writer(self: *Writable) Writer { + return Writer.bind(Writable, self, write); + } + + fn write(self: *Writable, buffer: []const u8) !usize { + const range = @min(buffer.len, self.data.len); + + @memcpy(self.data[0 .. range], buffer[0 .. range]); + + self.data = self.data[range ..]; + + return buffer.len; + } +}; + +pub const Byte = u8; pub const Error = error { UnavailableResource, }; -pub fn FixedBuffer(comptime len: usize, comptime default_value: anytype) type { - const Value = @TypeOf(default_value); - - return struct { - filled: usize = 0, - values: [len]Value = [_]Value{default_value} ** len, - }; -} - pub fn Functor(comptime Output: type, comptime input_types: []const type) type { const InputTuple = std.meta.Tuple(input_types); @@ -119,20 +126,6 @@ pub fn Generator(comptime Output: type, comptime input_types: []const type) type }; } -pub const NullWritable = struct { - written: usize = 0, - - pub fn writer(self: *NullWritable) Writer { - return Writer.bind(NullWritable, self, write); - } - - pub fn write(self: *NullWritable, buffer: []const u8) !usize { - self.written += buffer.len; - - return buffer.len; - } -}; - pub const PrintError = Error || error { IncompleteWrite, }; @@ -141,8 +134,6 @@ pub const Reader = Generator(Error!usize, &.{[]coral.Byte}); pub const Writer = Generator(Error!usize, &.{[]const coral.Byte}); -const native_endian = builtin.cpu.arch.endian(); - pub fn alloc_read(input: coral.io.Reader, allocator: std.mem.Allocator) []coral.Byte { const buffer = coral.Stack(coral.Byte){.allocator = allocator}; @@ -153,20 +144,6 @@ pub fn alloc_read(input: coral.io.Reader, allocator: std.mem.Allocator) []coral. return buffer.to_allocation(streamed); } -pub fn are_equal(a: []const Byte, b: []const Byte) bool { - if (a.len != b.len) { - return false; - } - - for (0 .. a.len) |i| { - if (a[i] != b[i]) { - return false; - } - } - - return true; -} - pub const bits_per_byte = 8; pub fn bytes_of(value: anytype) []const Byte { @@ -179,14 +156,6 @@ pub fn bytes_of(value: anytype) []const Byte { }; } -pub fn ends_with(haystack: []const Byte, needle: []const Byte) bool { - if (needle.len > haystack.len) { - return false; - } - - return are_equal(haystack[haystack.len - needle.len ..], needle); -} - pub fn print(writer: Writer, utf8: []const u8) PrintError!void { if (try writer.yield(.{utf8}) != utf8.len) { return error.IncompleteWrite; @@ -208,35 +177,6 @@ pub fn skip_n(input: Reader, distance: u64) Error!void { } } -pub fn read_foreign(input: Reader, comptime Type: type) Error!Type { - const decoded = try read_native(input, Type); - - return switch (@typeInfo(input)) { - .Struct => std.mem.byteSwapAllFields(Type, &decoded), - else => @byteSwap(decoded), - }; -} - -pub fn read_native(input: Reader, comptime Type: type) Error!Type { - var buffer = @as([@sizeOf(Type)]coral.Byte, undefined); - - if (try input.yield(.{&buffer}) != buffer.len) { - return error.UnavailableResource; - } - - return @as(*align(1) const Type, @ptrCast(&buffer)).*; -} - -pub const read_little = switch (native_endian) { - .little => read_native, - .big => read_foreign, -}; - -pub const read_big = switch (native_endian) { - .little => read_foreign, - .big => read_native, -}; - pub fn slice_sentineled(comptime sen: anytype, ptr: [*:sen]const @TypeOf(sen)) [:sen]const @TypeOf(sen) { var len = @as(usize, 0); diff --git a/src/main.zig b/src/main.zig index c5d050d..c8ebec9 100644 --- a/src/main.zig +++ b/src/main.zig @@ -6,6 +6,7 @@ const ona = @import("ona"); const Actors = struct { instances: coral.stack.Sequential(ona.gfx.Point2D) = .{.allocator = coral.heap.allocator}, + quad_mesh_2d: ona.gfx.Handle = .none, body_texture: ona.gfx.Handle = .none, }; @@ -24,6 +25,7 @@ pub fn main() !void { fn load(display: coral.Write(ona.gfx.Display), actors: coral.Write(Actors), assets: coral.Write(ona.gfx.Assets)) !void { display.res.width, display.res.height = .{1280, 720}; actors.res.body_texture = try assets.res.open_file("actor.bmp"); + actors.res.quad_mesh_2d = try assets.res.open_quad_mesh_2d(@splat(1)); try actors.res.instances.push_grow(.{0, 0}); } @@ -32,11 +34,11 @@ fn exit(actors: coral.Write(Actors)) void { actors.res.instances.deinit(); } -fn render(queue: ona.gfx.Queue, actors: coral.Write(Actors), assets: coral.Read(ona.gfx.Assets)) !void { +fn render(queue: ona.gfx.Queue, actors: coral.Write(Actors)) !void { for (actors.res.instances.values) |instance| { try queue.commands.append(.{ .instance_2d = .{ - .mesh_2d = assets.res.primitives.quad_mesh, + .mesh_2d = actors.res.quad_mesh_2d, .texture = actors.res.body_texture, .transform = .{ diff --git a/src/ona/gfx.zig b/src/ona/gfx.zig index 074485a..72aa211 100644 --- a/src/ona/gfx.zig +++ b/src/ona/gfx.zig @@ -14,41 +14,99 @@ const std = @import("std"); pub const Assets = struct { context: device.Context, - primitives: Primitives, formats: coral.stack.Sequential(Format), + staging_arena: std.heap.ArenaAllocator, pub const Format = struct { extension: []const u8, - open: *const fn ([]const u8) std.mem.Allocator.Error!Handle, + open_file: *const fn (*std.heap.ArenaAllocator, []const u8) Error!Desc, + + pub const Error = std.mem.Allocator.Error || coral.files.Error || error { + Unsupported, + }; }; - const Primitives = struct { - quad_mesh: Handle, - }; + pub fn open_file(self: *Assets, path: []const u8) (std.mem.Allocator.Error || Format.Error)!Handle { + defer { + const max_cache_size = 536870912; - pub fn close(self: *Assets, handle: Handle) void { - return self.context.close(handle); - } + if (!self.staging_arena.reset(.{.retain_with_limit = max_cache_size})) { + std.log.warn("failed to retain staging arena size of {} bytes", .{max_cache_size}); + } + } - pub fn open_file(self: *Assets, path: []const u8) std.mem.Allocator.Error!Handle { for (self.formats.values) |format| { if (!std.mem.endsWith(u8, path, format.extension)) { continue; } - return format.open(path); + return self.context.open(try format.open_file(&self.staging_arena, path)); } return .none; } - pub fn open_mesh_2d(self: *Assets, mesh_2d: Mesh2D) std.mem.Allocator.Error!Handle { - return self.context.open(.{.mesh_2d = mesh_2d}); + pub fn open_quad_mesh_2d(self: *Assets, extents: Point2D) std.mem.Allocator.Error!Handle { + const width, const height = extents / @as(Point2D, @splat(2)); + + return self.context.open(.{ + .mesh_2d = .{ + .indices = &.{0, 1, 2, 0, 2, 3}, + + .vertices = &.{ + .{.xy = .{-width, height}, .uv = .{0, 1}}, + .{.xy = .{width, height}, .uv = .{1, 1}}, + .{.xy = .{width, -height}, .uv = .{1, 0}}, + .{.xy = .{-width, -height}, .uv = .{0, 0}}, + }, + }, + }); } }; pub const Color = @Vector(4, f32); +pub const Desc = union (enum) { + texture: Texture, + mesh_2d: Mesh2D, + + pub const Mesh2D = struct { + vertices: []const Vertex, + indices: []const u16, + + pub const Vertex = struct { + xy: Point2D, + uv: Point2D, + }; + }; + + pub const Texture = struct { + data: []const coral.io.Byte, + width: u16, + format: Format, + access: Access, + + pub const Access = enum { + static, + }; + + pub const Format = enum { + rgba8888, + bgra8888, + argb8888, + rgb888, + bgr888, + + pub fn byte_size(self: Format) usize { + return switch (self) { + .rgba8888, .bgra8888, .argb8888 => 4, + .rgb888, .bgr888 => 3, + }; + } + }; + }; +}; + pub const Display = struct { width: u16 = 1280, height: u16 = 720, @@ -85,16 +143,6 @@ pub const Input = union (enum) { pub const Point2D = @Vector(2, f32); -pub const Mesh2D = struct { - vertices: []const Vertex, - indices: []const u16, - - pub const Vertex = struct { - xy: Point2D, - uv: Point2D, - }; -}; - pub const Queue = struct { commands: *device.RenderList, @@ -104,6 +152,10 @@ pub const Queue = struct { pub fn bind(_: coral.system.BindContext) std.mem.Allocator.Error!State { // TODO: Review how good of an idea this global state is, even if bind is guaranteed to always be ran on main. + if (renders.is_empty()) { + renders = .{.allocator = coral.heap.allocator}; + } + const command_index = renders.len(); try renders.push_grow(device.RenderChain.init(coral.heap.allocator)); @@ -121,39 +173,21 @@ pub const Queue = struct { pub fn unbind(state: *State) void { std.debug.assert(!renders.is_empty()); - std.mem.swap(device.RenderChain, &renders.values[state.command_index], renders.get_ptr().?); + + const render = &renders.values[state.command_index]; + + render.deinit(); + std.mem.swap(device.RenderChain, render, renders.get_ptr().?); std.debug.assert(renders.pop()); + + if (renders.is_empty()) { + Queue.renders.deinit(); + } } var renders = coral.stack.Sequential(device.RenderChain){.allocator = coral.heap.allocator}; }; -pub const Texture = struct { - data: []const coral.io.Byte, - width: u16, - format: Format, - access: Access, - - pub const Access = enum { - static, - }; - - pub const Format = enum { - rgba8888, - bgra8888, - argb8888, - rgb888, - bgr888, - - pub fn byte_size(self: Format) usize { - return switch (self) { - .rgba8888, .bgra8888, .argb8888 => 4, - .rgb888, .bgr888 => 3, - }; - } - }; -}; - pub const Transform2D = extern struct { xbasis: Point2D = .{1, 0}, ybasis: Point2D = .{0, 1}, @@ -163,7 +197,7 @@ pub const Transform2D = extern struct { const builtin_formats = [_]Assets.Format{ .{ .extension = "bmp", - .open = formats.open_bmp, + .open_file = formats.load_bmp, }, }; @@ -210,24 +244,8 @@ pub fn setup(world: *coral.World, events: App.Events) (error {Unsupported} || st try registered_formats.grow(builtin_formats.len); std.debug.assert(registered_formats.push_all(&builtin_formats)); - const half_extent = 0.5; - try world.set_resource(.none, Assets{ - .primitives = .{ - .quad_mesh = try context.open(.{ - .mesh_2d = .{ - .indices = &.{0, 1, 2, 0, 2, 3}, - - .vertices = &.{ - .{.xy = .{-half_extent, half_extent}, .uv = .{0, 1}}, - .{.xy = .{half_extent, half_extent}, .uv = .{1, 1}}, - .{.xy = .{half_extent, -half_extent}, .uv = .{1, 0}}, - .{.xy = .{-half_extent, -half_extent}, .uv = .{0, 0}}, - }, - }, - }), - }, - + .staging_arena = std.heap.ArenaAllocator.init(coral.heap.allocator), .formats = registered_formats, .context = context, }); @@ -239,6 +257,8 @@ pub fn setup(world: *coral.World, events: App.Events) (error {Unsupported} || st } pub fn stop(assets: coral.Write(Assets)) void { + assets.res.staging_arena.deinit(); + assets.res.formats.deinit(); assets.res.context.deinit(); } diff --git a/src/ona/gfx/device.zig b/src/ona/gfx/device.zig index f1284f3..d0d05cc 100644 --- a/src/ona/gfx/device.zig +++ b/src/ona/gfx/device.zig @@ -10,11 +10,6 @@ const sokol = @import("sokol"); const std = @import("std"); -pub const Asset = union (enum) { - texture: gfx.Texture, - mesh_2d: gfx.Mesh2D, -}; - pub const Context = struct { window: *ext.SDL_Window, thread: std.Thread, @@ -42,6 +37,7 @@ pub const Context = struct { self.loop.is_running.store(false, .monotonic); self.loop.ready.post(); self.thread.join(); + self.loop.deinit(); coral.heap.allocator.destroy(self.loop); ext.SDL_DestroyWindow(self.window); @@ -84,16 +80,20 @@ pub const Context = struct { }; } - pub fn open(self: *Context, asset: Asset) std.mem.Allocator.Error!gfx.Handle { + pub fn open(self: *Context, desc: gfx.Desc) std.mem.Allocator.Error!gfx.Handle { const open_commands = self.loop.opens.pending(); const index = self.loop.closed_indices.get() orelse open_commands.stack.len(); try open_commands.append(.{ .index = index, - .payload = asset, + .desc = desc, }); - try self.loop.closes.pending().stack.grow(1); + const pending_closes = self.loop.closes.pending(); + + if (pending_closes.stack.len() == pending_closes.stack.cap) { + try pending_closes.stack.grow(1); + } _ = self.loop.closed_indices.pop(); @@ -145,13 +145,13 @@ const Loop = struct { const OpenCommand = struct { index: usize, - payload: Asset, + desc: gfx.Desc, fn clone(command: OpenCommand, arena: *std.heap.ArenaAllocator) std.mem.Allocator.Error!OpenCommand { const allocator = arena.allocator(); return .{ - .payload = switch (command.payload) { + .desc = switch (command.desc) { .texture => |texture| .{ .texture = .{ .data = try allocator.dupe(coral.io.Byte, texture.data), @@ -163,7 +163,7 @@ const Loop = struct { .mesh_2d => |mesh_2d| .{ .mesh_2d = .{ - .vertices = try allocator.dupe(gfx.Mesh2D.Vertex, mesh_2d.vertices), + .vertices = try allocator.dupe(gfx.Desc.Mesh2D.Vertex, mesh_2d.vertices), .indices = try allocator.dupe(u16, mesh_2d.indices), }, }, @@ -178,6 +178,12 @@ const Loop = struct { const OpenChain = commands.Chain(OpenCommand, OpenCommand.clone); + fn deinit(self: *Loop) void { + self.closes.deinit(); + self.opens.deinit(); + self.closed_indices.deinit(); + } + fn run(self: *Loop, window: *ext.SDL_Window) !void { const context = configure_and_create: { var result = @as(c_int, 0); @@ -230,7 +236,7 @@ const Loop = struct { defer open_commands.clear(); for (open_commands.stack.values) |command| { - switch (command.payload) { + switch (command.desc) { .texture => |texture| { const stride = texture.width * texture.format.byte_size(); diff --git a/src/ona/gfx/formats.zig b/src/ona/gfx/formats.zig index 0bb116d..481e25b 100644 --- a/src/ona/gfx/formats.zig +++ b/src/ona/gfx/formats.zig @@ -1,9 +1,69 @@ +const coral = @import("coral"); + const gfx = @import("../gfx.zig"); const std = @import("std"); -pub fn open_bmp(path: []const u8) std.mem.Allocator.Error!gfx.Handle { - // TODO: Implement. - _ = path; - unreachable; +pub fn load_bmp(arena: *std.heap.ArenaAllocator, path: []const u8) gfx.Assets.Format.Error!gfx.Desc { + const header = try coral.files.bundle.read_little(path, 0, extern struct { + type: [2]u8 align (1), + file_size: u32 align (1), + reserved: [2]u16 align (1), + image_offset: u32 align (1), + header_size: u32 align (1), + pixel_width: i32 align (1), + pixel_height: i32 align (1), + color_planes: u16 align (1), + bits_per_pixel: u16 align (1), + compression_method: u32 align (1), + image_size: u32 align(1), + pixels_per_meter_x: i32 align (1), + pixels_per_meter_y: i32 align (1), + palette_colors_used: u32 align (1), + important_colors_used: u32 align (1), + }) orelse { + return error.Unsupported; + }; + + if (!std.mem.eql(u8, &header.type, "BM")) { + return error.Unsupported; + } + + const pixel_width = std.math.cast(u16, header.pixel_width) orelse { + return error.Unsupported; + }; + + const pixels = try arena.allocator().alloc(coral.io.Byte, header.image_size); + const bytes_per_pixel = header.bits_per_pixel / coral.io.bits_per_byte; + const alignment = 4; + const byte_stride = pixel_width * bytes_per_pixel; + const padded_byte_stride = alignment * @divTrunc((byte_stride + alignment - 1), alignment); + const byte_padding = coral.scalars.sub(padded_byte_stride, byte_stride) orelse 0; + var buffer_offset: usize = 0; + var file_offset = @as(usize, header.image_offset); + + while (buffer_offset < pixels.len) { + const line = pixels[buffer_offset .. buffer_offset + byte_stride]; + + if (try coral.files.bundle.read_bytes(path, file_offset, line) != byte_stride) { + return error.Unsupported; + } + + file_offset = line.len + byte_padding; + buffer_offset += padded_byte_stride; + } + + return .{ + .texture = .{ + .format = switch (header.bits_per_pixel) { + 24 => .bgr888, + 32 => .bgra8888, + else => return error.Unsupported, + }, + + .width = pixel_width, + .data = pixels, + .access = .static, + } + }; } From 4125aa0ddba91fe44742aa0fdd5d1b6a69035647 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 23 Jun 2024 02:51:00 +0100 Subject: [PATCH 3/6] Tidy and fix up --- src/ona/gfx.zig | 22 ++++++--------- src/ona/gfx/device.zig | 5 ++++ src/ona/gfx/formats.zig | 42 +++++++++++++++++----------- src/ona/gfx/shaders/instance_2d.glsl | 4 +++ 4 files changed, 43 insertions(+), 30 deletions(-) diff --git a/src/ona/gfx.zig b/src/ona/gfx.zig index 72aa211..b13f4b8 100644 --- a/src/ona/gfx.zig +++ b/src/ona/gfx.zig @@ -2,12 +2,12 @@ const App = @import("./App.zig"); const coral = @import("coral"); -const formats = @import("./gfx/formats.zig"); - const device = @import("./gfx/device.zig"); const ext = @import("./ext.zig"); +const formats = @import("./gfx/formats.zig"); + const msg = @import("./msg.zig"); const std = @import("std"); @@ -19,10 +19,10 @@ pub const Assets = struct { pub const Format = struct { extension: []const u8, - open_file: *const fn (*std.heap.ArenaAllocator, []const u8) Error!Desc, + file_desc: *const fn (*std.heap.ArenaAllocator, []const u8) Error!Desc, pub const Error = std.mem.Allocator.Error || coral.files.Error || error { - Unsupported, + FormatUnsupported, }; }; @@ -40,7 +40,7 @@ pub const Assets = struct { continue; } - return self.context.open(try format.open_file(&self.staging_arena, path)); + return self.context.open(try format.file_desc(&self.staging_arena, path)); } return .none; @@ -91,16 +91,12 @@ pub const Desc = union (enum) { }; pub const Format = enum { - rgba8888, - bgra8888, - argb8888, - rgb888, - bgr888, + rgba8, + bgra8, pub fn byte_size(self: Format) usize { return switch (self) { - .rgba8888, .bgra8888, .argb8888 => 4, - .rgb888, .bgr888 => 3, + .rgba8, .bgra8 => 4, }; } }; @@ -197,7 +193,7 @@ pub const Transform2D = extern struct { const builtin_formats = [_]Assets.Format{ .{ .extension = "bmp", - .open_file = formats.load_bmp, + .file_desc = formats.bmp_file_desc, }, }; diff --git a/src/ona/gfx/device.zig b/src/ona/gfx/device.zig index d0d05cc..2edb578 100644 --- a/src/ona/gfx/device.zig +++ b/src/ona/gfx/device.zig @@ -253,6 +253,11 @@ const Loop = struct { break: get subimage; }, }, + + .pixel_format = switch (texture.format) { + .rgba8 => .RGBA8, + .bgra8 => .BGRA8, + }, }); errdefer sokol.gfx.destroyImage(image); diff --git a/src/ona/gfx/formats.zig b/src/ona/gfx/formats.zig index 481e25b..c64c02d 100644 --- a/src/ona/gfx/formats.zig +++ b/src/ona/gfx/formats.zig @@ -4,7 +4,7 @@ const gfx = @import("../gfx.zig"); const std = @import("std"); -pub fn load_bmp(arena: *std.heap.ArenaAllocator, path: []const u8) gfx.Assets.Format.Error!gfx.Desc { +pub fn bmp_file_desc(arena: *std.heap.ArenaAllocator, path: []const u8) gfx.Assets.Format.Error!gfx.Desc { const header = try coral.files.bundle.read_little(path, 0, extern struct { type: [2]u8 align (1), file_size: u32 align (1), @@ -22,15 +22,15 @@ pub fn load_bmp(arena: *std.heap.ArenaAllocator, path: []const u8) gfx.Assets.Fo palette_colors_used: u32 align (1), important_colors_used: u32 align (1), }) orelse { - return error.Unsupported; + return error.FormatUnsupported; }; if (!std.mem.eql(u8, &header.type, "BM")) { - return error.Unsupported; + return error.FormatUnsupported; } const pixel_width = std.math.cast(u16, header.pixel_width) orelse { - return error.Unsupported; + return error.FormatUnsupported; }; const pixels = try arena.allocator().alloc(coral.io.Byte, header.image_size); @@ -42,27 +42,35 @@ pub fn load_bmp(arena: *std.heap.ArenaAllocator, path: []const u8) gfx.Assets.Fo var buffer_offset: usize = 0; var file_offset = @as(usize, header.image_offset); - while (buffer_offset < pixels.len) { - const line = pixels[buffer_offset .. buffer_offset + byte_stride]; + switch (header.bits_per_pixel) { + 32 => { + while (buffer_offset < pixels.len) { + const line = pixels[buffer_offset .. buffer_offset + byte_stride]; - if (try coral.files.bundle.read_bytes(path, file_offset, line) != byte_stride) { - return error.Unsupported; - } + if (try coral.files.bundle.read_bytes(path, file_offset, line) != byte_stride) { + return error.FormatUnsupported; + } - file_offset = line.len + byte_padding; - buffer_offset += padded_byte_stride; + for (0 .. pixel_width) |i| { + const line_offset = i * 4; + const pixel = line[line_offset .. line_offset + 4]; + + std.mem.swap(u8, &pixel[0], &pixel[2]); + } + + file_offset += line.len + byte_padding; + buffer_offset += padded_byte_stride; + } + }, + + else => return error.FormatUnsupported, } return .{ .texture = .{ - .format = switch (header.bits_per_pixel) { - 24 => .bgr888, - 32 => .bgra8888, - else => return error.Unsupported, - }, - .width = pixel_width, .data = pixels, + .format = .rgba8, .access = .static, } }; diff --git a/src/ona/gfx/shaders/instance_2d.glsl b/src/ona/gfx/shaders/instance_2d.glsl index 2ffff17..160c15c 100644 --- a/src/ona/gfx/shaders/instance_2d.glsl +++ b/src/ona/gfx/shaders/instance_2d.glsl @@ -51,6 +51,10 @@ out vec4 texel; void main() { texel = texture(sampler2D(tex, smp), uv) * color; + + if (texel.a == 0) { + discard; + } } @end From 9cfabae9315643ff2f697eec2dbf8a414e7ffc68 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 23 Jun 2024 03:03:41 +0100 Subject: [PATCH 4/6] Update to Zig 0.13.0 --- .gitignore | 2 +- build.zig.zon | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 5af52ca..705556a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ # Generated assets -/zig-cache +/.zig-cache /zig-out *.glsl.zig diff --git a/build.zig.zon b/build.zig.zon index a3445f4..3fcc20d 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -10,8 +10,8 @@ }, .dependencies = .{ .sokol = .{ - .url = "git+https://github.com/floooh/sokol-zig.git#796a3d3d54c22d20da9e91a9a9120d5423d1e700", - .hash = "12209a187e071d76af00c02865677d170f844376866d062b1b5f82e4ecbd750c4e18", + .url = "git+https://github.com/floooh/sokol-zig.git#7c25767e51aa06dd5fb0684e4a8f2200d182ef27", + .hash = "1220fa7f47fbaf2f1ed8c17fab2d23b6a85bcbbc4aa0b3802c90a3e8bf6fca1f8569", }, }, } From bfa3efbb51d2fb9aca75938fe64a93766f8bd701 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 23 Jun 2024 03:31:51 +0100 Subject: [PATCH 5/6] PR tidy up --- src/coral/io.zig | 18 ------------------ src/coral/resource.zig | 2 +- src/ona/gfx.zig | 12 ++++++++---- src/ona/gfx/device.zig | 2 +- 4 files changed, 10 insertions(+), 24 deletions(-) diff --git a/src/coral/io.zig b/src/coral/io.zig index 214ce8e..383825e 100644 --- a/src/coral/io.zig +++ b/src/coral/io.zig @@ -6,24 +6,6 @@ const slices = @import("./slices.zig"); const std = @import("std"); -pub const Writable = struct { - data: []Byte, - - pub fn writer(self: *Writable) Writer { - return Writer.bind(Writable, self, write); - } - - fn write(self: *Writable, buffer: []const u8) !usize { - const range = @min(buffer.len, self.data.len); - - @memcpy(self.data[0 .. range], buffer[0 .. range]); - - self.data = self.data[range ..]; - - return buffer.len; - } -}; - pub const Byte = u8; pub const Error = error { diff --git a/src/coral/resource.zig b/src/coral/resource.zig index d1640f7..617c38b 100644 --- a/src/coral/resource.zig +++ b/src/coral/resource.zig @@ -53,7 +53,7 @@ pub const Table = struct { errdefer resource_allocator.destroy(allocated_resource); std.debug.assert(try self.table.emplace(value_id, .{ - .ptr = @ptrCast(allocated_resource), + .ptr = allocated_resource, })); allocated_resource.* = value; diff --git a/src/ona/gfx.zig b/src/ona/gfx.zig index b13f4b8..d998ee2 100644 --- a/src/ona/gfx.zig +++ b/src/ona/gfx.zig @@ -19,14 +19,14 @@ pub const Assets = struct { pub const Format = struct { extension: []const u8, - file_desc: *const fn (*std.heap.ArenaAllocator, []const u8) Error!Desc, + file_desc: *const fn (*std.heap.ArenaAllocator, coral.files.Storage, []const u8) Error!Desc, pub const Error = std.mem.Allocator.Error || coral.files.Error || error { FormatUnsupported, }; }; - pub fn open_file(self: *Assets, path: []const u8) (std.mem.Allocator.Error || Format.Error)!Handle { + pub fn open_file(self: *Assets, storage: coral.files.Storage, path: []const u8) (OpenError || Format.Error)!Handle { defer { const max_cache_size = 536870912; @@ -40,13 +40,13 @@ pub const Assets = struct { continue; } - return self.context.open(try format.file_desc(&self.staging_arena, path)); + return self.context.open(try format.file_desc(&self.staging_arena, storage, path)); } return .none; } - pub fn open_quad_mesh_2d(self: *Assets, extents: Point2D) std.mem.Allocator.Error!Handle { + pub fn open_quad_mesh_2d(self: *Assets, extents: Point2D) OpenError!Handle { const width, const height = extents / @as(Point2D, @splat(2)); return self.context.open(.{ @@ -137,6 +137,10 @@ pub const Input = union (enum) { }; }; +pub const OpenError = std.mem.Allocator.Error || error { + +}; + pub const Point2D = @Vector(2, f32); pub const Queue = struct { diff --git a/src/ona/gfx/device.zig b/src/ona/gfx/device.zig index 2edb578..eb2f203 100644 --- a/src/ona/gfx/device.zig +++ b/src/ona/gfx/device.zig @@ -80,7 +80,7 @@ pub const Context = struct { }; } - pub fn open(self: *Context, desc: gfx.Desc) std.mem.Allocator.Error!gfx.Handle { + pub fn open(self: *Context, desc: gfx.Desc) gfx.OpenError!gfx.Handle { const open_commands = self.loop.opens.pending(); const index = self.loop.closed_indices.get() orelse open_commands.stack.len(); From f0cc66edfa307ea8fbf3a0b594ca13f7e7a109fc Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 23 Jun 2024 03:35:16 +0100 Subject: [PATCH 6/6] Fix compilation errors --- src/main.zig | 2 +- src/ona/gfx/formats.zig | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main.zig b/src/main.zig index c8ebec9..93e26de 100644 --- a/src/main.zig +++ b/src/main.zig @@ -24,7 +24,7 @@ pub fn main() !void { fn load(display: coral.Write(ona.gfx.Display), actors: coral.Write(Actors), assets: coral.Write(ona.gfx.Assets)) !void { display.res.width, display.res.height = .{1280, 720}; - actors.res.body_texture = try assets.res.open_file("actor.bmp"); + actors.res.body_texture = try assets.res.open_file(coral.files.bundle, "actor.bmp"); actors.res.quad_mesh_2d = try assets.res.open_quad_mesh_2d(@splat(1)); try actors.res.instances.push_grow(.{0, 0}); diff --git a/src/ona/gfx/formats.zig b/src/ona/gfx/formats.zig index c64c02d..a717cd4 100644 --- a/src/ona/gfx/formats.zig +++ b/src/ona/gfx/formats.zig @@ -4,8 +4,12 @@ const gfx = @import("../gfx.zig"); const std = @import("std"); -pub fn bmp_file_desc(arena: *std.heap.ArenaAllocator, path: []const u8) gfx.Assets.Format.Error!gfx.Desc { - const header = try coral.files.bundle.read_little(path, 0, extern struct { +pub fn bmp_file_desc( + arena: *std.heap.ArenaAllocator, + storage: coral.files.Storage, + path: []const u8, +) gfx.Assets.Format.Error!gfx.Desc { + const header = try storage.read_little(path, 0, extern struct { type: [2]u8 align (1), file_size: u32 align (1), reserved: [2]u16 align (1), @@ -47,7 +51,7 @@ pub fn bmp_file_desc(arena: *std.heap.ArenaAllocator, path: []const u8) gfx.Asse while (buffer_offset < pixels.len) { const line = pixels[buffer_offset .. buffer_offset + byte_stride]; - if (try coral.files.bundle.read_bytes(path, file_offset, line) != byte_stride) { + if (try storage.read_bytes(path, file_offset, line) != byte_stride) { return error.FormatUnsupported; }