From f100aa0d4649a1386c5cba798349b0fc2711baa5 Mon Sep 17 00:00:00 2001 From: kayomn Date: Thu, 27 Jun 2024 20:01:06 +0100 Subject: [PATCH 01/30] Add render textures --- src/main.zig | 70 +- src/ona/gfx.zig | 53 +- src/ona/gfx/device.zig | 880 ++++++++++-------- src/ona/gfx/formats.zig | 10 +- .../{instance_2d.glsl => default_2d.glsl} | 15 +- 5 files changed, 605 insertions(+), 423 deletions(-) rename src/ona/gfx/shaders/{instance_2d.glsl => default_2d.glsl} (72%) diff --git a/src/main.zig b/src/main.zig index 93e26de..6a79895 100644 --- a/src/main.zig +++ b/src/main.zig @@ -6,8 +6,9 @@ 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, + quad_mesh_2d: ?*ona.gfx.Handle = null, + body_texture: ?*ona.gfx.Handle = null, + render_texture: ?*ona.gfx.Handle = null, }; const Player = struct { @@ -24,8 +25,21 @@ 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(coral.files.bundle, "actor.bmp"); - actors.res.quad_mesh_2d = try assets.res.open_quad_mesh_2d(@splat(1)); + actors.res.body_texture = try assets.res.create_from_file(coral.files.bundle, "actor.bmp"); + actors.res.quad_mesh_2d = try assets.res.create_quad_mesh_2d(@splat(1)); + + actors.res.render_texture = try assets.res.context.create(.{ + .texture = .{ + .format = .rgba8, + + .access = .{ + .render = .{ + .width = display.res.width, + .height = display.res.height, + }, + }, + }, + }); try actors.res.instances.push_grow(.{0, 0}); } @@ -34,27 +48,47 @@ fn exit(actors: coral.Write(Actors)) void { actors.res.instances.deinit(); } -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 = actors.res.quad_mesh_2d, - .texture = actors.res.body_texture, +fn render(queue: ona.gfx.Queue, actors: coral.Write(Actors), display: coral.Read(ona.gfx.Display)) !void { + try queue.commands.append(.{.target = .{ + .texture = actors.res.render_texture.?, + .clear_color = .{0, 0, 0, 0}, + .clear_depth = 0, + }}); - .transform = .{ - .origin = instance, - .xbasis = .{64, 0}, - .ybasis = .{0, 64}, - }, + for (actors.res.instances.values) |instance| { + try queue.commands.append(.{.instance_2d = .{ + .mesh_2d = actors.res.quad_mesh_2d.?, + .texture = actors.res.body_texture.?, + + .transform = .{ + .origin = instance, + .xbasis = .{64, 0}, + .ybasis = .{0, 64}, }, - }); + }}); } + + try queue.commands.append(.{.target = .{ + .clear_color = null, + .clear_depth = null, + }}); + + try queue.commands.append(.{.instance_2d = .{ + .mesh_2d = actors.res.quad_mesh_2d.?, + .texture = actors.res.render_texture.?, + + .transform = .{ + .origin = .{@floatFromInt(display.res.width / 2), @floatFromInt(display.res.height / 2)}, + .xbasis = .{@floatFromInt(display.res.width), 0}, + .ybasis = .{0, @floatFromInt(display.res.height)}, + }, + }}); } fn update(player: coral.Read(Player), actors: coral.Write(Actors), mapping: coral.Read(ona.act.Mapping)) !void { actors.res.instances.values[0] += .{ - mapping.res.axis_strength(player.res.move_x), - mapping.res.axis_strength(player.res.move_y), + mapping.res.axis_strength(player.res.move_x) * 10, + mapping.res.axis_strength(player.res.move_y) * 10, }; } diff --git a/src/ona/gfx.zig b/src/ona/gfx.zig index d998ee2..50c973b 100644 --- a/src/ona/gfx.zig +++ b/src/ona/gfx.zig @@ -26,7 +26,7 @@ pub const Assets = struct { }; }; - pub fn open_file(self: *Assets, storage: coral.files.Storage, path: []const u8) (OpenError || Format.Error)!Handle { + pub fn create_from_file(self: *Assets, storage: coral.files.Storage, path: []const u8) (OpenError || Format.Error)!*Handle { defer { const max_cache_size = 536870912; @@ -40,16 +40,16 @@ pub const Assets = struct { continue; } - return self.context.open(try format.file_desc(&self.staging_arena, storage, path)); + return self.context.create(try format.file_desc(&self.staging_arena, storage, path)); } - return .none; + return error.FormatUnsupported; } - pub fn open_quad_mesh_2d(self: *Assets, extents: Point2D) OpenError!Handle { + pub fn create_quad_mesh_2d(self: *Assets, extents: Point2D) OpenError!*Handle { const width, const height = extents / @as(Point2D, @splat(2)); - return self.context.open(.{ + return self.context.create(.{ .mesh_2d = .{ .indices = &.{0, 1, 2, 0, 2, 3}, @@ -66,6 +66,23 @@ pub const Assets = struct { pub const Color = @Vector(4, f32); +pub const Command = union (enum) { + instance_2d: Instance2D, + target: Target, + + pub const Instance2D = struct { + texture: *Handle, + mesh_2d: *Handle, + transform: Transform2D, + }; + + pub const Target = struct { + texture: ?*Handle = null, + clear_color: ?Color, + clear_depth: ?f32, + }; +}; + pub const Desc = union (enum) { texture: Texture, mesh_2d: Mesh2D, @@ -81,13 +98,19 @@ pub const Desc = union (enum) { }; pub const Texture = struct { - data: []const coral.io.Byte, - width: u16, format: Format, access: Access, - pub const Access = enum { - static, + pub const Access = union (enum) { + static: struct { + width: u16, + data: []const coral.io.Byte, + }, + + render: struct { + width: u16, + height: u16, + }, }; pub const Format = enum { @@ -109,17 +132,7 @@ pub const Display = struct { clear_color: Color = colors.black, }; -pub const Handle = enum (usize) { - none, - _, - - pub fn index(self: Handle) ?usize { - return switch (self) { - .none => null, - _ => @intFromEnum(self) - 1, - }; - } -}; +pub const Handle = opaque {}; pub const Input = union (enum) { key_up: Key, diff --git a/src/ona/gfx/device.zig b/src/ona/gfx/device.zig index eb2f203..679475e 100644 --- a/src/ona/gfx/device.zig +++ b/src/ona/gfx/device.zig @@ -2,6 +2,8 @@ const commands = @import("./commands.zig"); const coral = @import("coral"); +const default_2d = @import("./shaders/default_2d.glsl.zig"); + const ext = @import("../ext.zig"); const gfx = @import("../gfx.zig"); @@ -22,15 +24,30 @@ pub const Context = struct { renders: []RenderChain, }; - pub fn close(self: *Context, handle: gfx.Handle) void { - const handle_index = handle.index() orelse { - return; - }; - + pub fn close(self: *Context, handle: *gfx.Handle) void { 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; + close_commands.append(handle) catch unreachable; + } + + pub fn create(self: *Context, desc: gfx.Desc) gfx.OpenError!*gfx.Handle { + const resource = try coral.heap.allocator.create(Resource); + + errdefer coral.heap.allocator.destroy(resource); + + try self.loop.creations.pending().append(.{ + .resource = resource, + .desc = desc, + }); + + const pending_destroys = self.loop.destroys.pending(); + + if (pending_destroys.stack.len() == pending_destroys.stack.cap) { + try pending_destroys.stack.grow(1); + } + + return @ptrCast(resource); } pub fn deinit(self: *Context) void { @@ -80,26 +97,6 @@ pub const Context = struct { }; } - 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(); - - try open_commands.append(.{ - .index = index, - .desc = desc, - }); - - 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(); - - return @enumFromInt(index + 1); - } - pub fn submit(self: *Context, submission: Submission) void { self.loop.finished.wait(); @@ -109,8 +106,8 @@ pub const Context = struct { render.swap(); } - self.loop.opens.swap(); - self.loop.closes.swap(); + self.loop.creations.swap(); + self.loop.destroys.swap(); var last_width, var last_height = [_]c_int{0, 0}; @@ -127,37 +124,57 @@ pub const Context = struct { } }; +const Frame = struct { + projection: default_2d.Mat4 = .{@splat(0), @splat(0), @splat(0), @splat(0)}, + flushed_batch_count: usize = 0, + pushed_batch_count: usize = 0, + render_passes: usize = 0, + mesh: ?*gfx.Handle = null, + texture: ?*gfx.Handle = null, + material: ?*gfx.Handle = null, +}; + 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}, + destroys: DestroyChain = DestroyChain.init(coral.heap.allocator), + creations: CreateChain = CreateChain.init(coral.heap.allocator), + has_resource_head: ?*Resource = null, + has_resource_tail: ?*Resource = null, const AtomicBool = std.atomic.Value(bool); - const CloseCommand = struct { - index: usize, - }; - - const OpenCommand = struct { - index: usize, + const CreateCommand = struct { + resource: *Resource, desc: gfx.Desc, - fn clone(command: OpenCommand, arena: *std.heap.ArenaAllocator) std.mem.Allocator.Error!OpenCommand { + fn clone(command: CreateCommand, arena: *std.heap.ArenaAllocator) std.mem.Allocator.Error!CreateCommand { const allocator = arena.allocator(); return .{ .desc = switch (command.desc) { .texture => |texture| .{ .texture = .{ - .data = try allocator.dupe(coral.io.Byte, texture.data), - .width = texture.width, + .access = switch (texture.access) { + .static => |static| .{ + .static = .{ + .data = try allocator.dupe(coral.io.Byte, static.data), + .width = static.width, + }, + }, + + .render => |render| .{ + .render = .{ + .width = render.width, + .height = render.height, + }, + }, + }, + .format = texture.format, - .access = texture.access, }, }, @@ -169,19 +186,162 @@ const Loop = struct { }, }, - .index = command.index, + .resource = command.resource, }; } }; - const CloseChain = commands.Chain(CloseCommand, null); + const CreateChain = commands.Chain(CreateCommand, CreateCommand.clone); - const OpenChain = commands.Chain(OpenCommand, OpenCommand.clone); + const DestroyChain = commands.Chain(*Resource, null); + + fn consume_creations(self: *Loop) std.mem.Allocator.Error!void { + const create_commands = self.creations.submitted(); + + defer create_commands.clear(); + + for (create_commands.stack.values) |command| { + errdefer coral.heap.allocator.destroy(command.resource); + + command.resource.* = .{ + .payload = switch (command.desc) { + .texture => |texture| init: { + const pixel_format = switch (texture.format) { + .rgba8 => sokol.gfx.PixelFormat.RGBA8, + .bgra8 => sokol.gfx.PixelFormat.BGRA8, + }; + + switch (texture.access) { + .static => |static| { + break: init .{ + .texture = .{ + .image = sokol.gfx.makeImage(.{ + .width = static.width, + .height = @intCast(static.data.len / (static.width * texture.format.byte_size())), + .pixel_format = pixel_format, + + .data = .{ + .subimage = get: { + var subimage = [_][16]sokol.gfx.Range{.{.{}} ** 16} ** 6; + + subimage[0][0] = sokol.gfx.asRange(static.data); + + break: get subimage; + }, + }, + }), + + .sampler = sokol.gfx.makeSampler(.{}), + }, + }; + }, + + .render => |render| { + const color_image = sokol.gfx.makeImage(.{ + .width = render.width, + .height = render.height, + .render_target = true, + .pixel_format = pixel_format, + }); + + const depth_image = sokol.gfx.makeImage(.{ + .width = render.width, + .height = render.height, + .render_target = true, + .pixel_format = .DEPTH_STENCIL, + }); + + break: init .{ + .render_target = .{ + .attachments = sokol.gfx.makeAttachments(.{ + .colors = get: { + var attachments = [_]sokol.gfx.AttachmentDesc{.{}} ** 4; + + attachments[0] = .{ + .image = color_image, + }; + + break: get attachments; + }, + + .depth_stencil = .{ + .image = depth_image, + }, + }), + + .sampler = sokol.gfx.makeSampler(.{}), + .color_image = color_image, + .depth_image = depth_image, + .width = render.width, + .height = render.height, + }, + }; + }, + } + }, + + .mesh_2d => |mesh_2d| init: { + if (mesh_2d.indices.len > std.math.maxInt(u32)) { + return error.OutOfMemory; + } + + break: init .{ + .mesh_2d = .{ + .index_buffer = sokol.gfx.makeBuffer(.{ + .data = sokol.gfx.asRange(mesh_2d.indices), + .type = .INDEXBUFFER, + }), + + .vertex_buffer = sokol.gfx.makeBuffer(.{ + .data = sokol.gfx.asRange(mesh_2d.vertices), + .type = .VERTEXBUFFER, + }), + + .index_count = @intCast(mesh_2d.indices.len), + }, + }; + }, + }, + + .has_prev = self.has_resource_tail, + .has_next = null, + }; + + if (self.has_resource_tail) |resource_tail| { + resource_tail.has_next = command.resource; + } else { + std.debug.assert(self.has_resource_head == null); + + self.has_resource_head = command.resource; + } + + self.has_resource_tail = command.resource; + } + } + + fn consume_destroys(self: *Loop) std.mem.Allocator.Error!void { + const destroy_commands = self.destroys.submitted(); + + defer destroy_commands.clear(); + + for (destroy_commands.stack.values) |resource| { + defer coral.heap.allocator.destroy(resource); + + resource.destroy(); + + if (resource.has_prev) |resource_prev| { + resource_prev.has_next = resource.has_next; + } + + if (resource.has_next) |resource_next| { + resource_next.has_prev = resource.has_prev; + } + } + } fn deinit(self: *Loop) void { - self.closes.deinit(); - self.opens.deinit(); - self.closed_indices.deinit(); + self.destroys.deinit(); + self.creations.deinit(); } fn run(self: *Loop, window: *ext.SDL_Window) !void { @@ -220,9 +380,9 @@ const Loop = struct { ext.SDL_GL_DeleteContext(context); } - var rendering = Rendering.init(); + var rendering_2d = Rendering2D.init(); - defer rendering.deinit(); + defer rendering_2d.deinit(); self.finished.post(); @@ -231,230 +391,219 @@ const Loop = struct { defer self.finished.post(); - const open_commands = self.opens.submitted(); + try self.consume_creations(); - defer open_commands.clear(); + var frame = Frame{}; - for (open_commands.stack.values) |command| { - switch (command.desc) { - .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; - }, - }, - - .pixel_format = switch (texture.format) { - .rgba8 => .RGBA8, - .bgra8 => .BGRA8, - }, - }); - - 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; - }, - }, + rendering_2d.set_target(&frame, window, .{ + .clear_color = self.clear_color, + .clear_depth = 0, }); - for (self.renders) |*render| { - const render_commands = render.submitted(); + if (self.renders.len != 0) { + const renders_tail_index = self.renders.len - 1; + + for (self.renders[0 .. renders_tail_index]) |*render| { + const render_commands = render.submitted(); + + defer render_commands.clear(); + + for (render_commands.stack.values) |command| { + try switch (command) { + .instance_2d => |instance_2d| rendering_2d.batch(&frame, instance_2d), + .target => |target| rendering_2d.set_target(&frame, window, target), + }; + } + + rendering_2d.set_target(&frame, window, .{ + .clear_color = null, + .clear_depth = null, + }); + } + + const render_commands = self.renders[renders_tail_index].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; - }, - } + try switch (command) { + .instance_2d => |instance_2d| rendering_2d.batch(&frame, instance_2d), + .target => |target| rendering_2d.set_target(&frame, window, target), + }; } + + rendering_2d.flush(&frame); } - rendering.flush_instance_2ds(&frame); sokol.gfx.endPass(); sokol.gfx.commit(); ext.SDL_GL_SwapWindow(window); - const close_commands = self.closes.submitted(); + try self.consume_destroys(); + } - defer close_commands.clear(); + var has_next_resource = self.has_resource_head; - for (close_commands.stack.values) |command| { - const object = &rendering.objects.values[command.index]; + while (has_next_resource) |resource| { + std.log.info("destroying remaining gfx device resource {x}", .{@intFromPtr(resource)}); + resource.destroy(); - switch (object.*) { - .empty => {}, // TODO: Handle double-closes. + has_next_resource = resource.has_next; - .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; - } + coral.heap.allocator.destroy(resource); } } }; -const Rendering = struct { - objects: coral.stack.Sequential(Object), - instance_2d_pipeline: sokol.gfx.Pipeline, - instance_2d_buffers: coral.stack.Sequential(sokol.gfx.Buffer), +pub const RenderChain = commands.Chain(gfx.Command, clone_command); - const Instance2D = extern struct { +pub const RenderList = commands.List(gfx.Command, clone_command); + +const Rendering2D = struct { + batching_pipeline: sokol.gfx.Pipeline, + batching_buffers: coral.stack.Sequential(sokol.gfx.Buffer), + + const Instance = 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, + const buffer_indices = .{ + .mesh = 0, + .instance = 1, + }; - fn unflushed_instance_2d_count(self: Frame) usize { - return self.pushed_instance_2d_count - self.flushed_instance_2d_count; + fn batch(self: *Rendering2D, frame: *Frame, instance: gfx.Command.Instance2D) std.mem.Allocator.Error!void { + if (instance.mesh_2d != frame.mesh or instance.texture != frame.texture) { + self.flush(frame); } - }; - const Object = union (enum) { - empty, + frame.mesh = instance.mesh_2d; + frame.texture = instance.texture; - mesh_2d: struct { - index_count: u32, - vertex_buffer: sokol.gfx.Buffer, - index_buffer: sokol.gfx.Buffer, - }, + const has_filled_buffer = (frame.pushed_batch_count % instances_per_buffer) == 0; + const pushed_buffer_count = frame.pushed_batch_count / instances_per_buffer; - texture: struct { - image: sokol.gfx.Image, - sampler: sokol.gfx.Sampler, - }, - }; + if (has_filled_buffer and pushed_buffer_count == self.batching_buffers.len()) { + var name_buffer = [_:0]u8{0} ** 64; - fn deinit(self: *Rendering) void { - for (self.instance_2d_buffers.values) |buffer| { + const name_view = std.fmt.bufPrint(&name_buffer, "instance 2d buffer #{}", .{self.batching_buffers.len()}) catch { + unreachable; + }; + + const instance_buffer = sokol.gfx.makeBuffer(.{ + .label = name_view.ptr, + .size = @sizeOf(Instance) * instances_per_buffer, + .usage = .STREAM, + }); + + errdefer sokol.gfx.destroyBuffer(instance_buffer); + + try self.batching_buffers.push_grow(instance_buffer); + } + + _ = sokol.gfx.appendBuffer(self.batching_buffers.get().?, sokol.gfx.asRange(&Instance{ + .transform = instance.transform, + })); + + frame.pushed_batch_count += 1; + } + + fn deinit(self: *Rendering2D) void { + for (self.batching_buffers.values) |buffer| { sokol.gfx.destroyBuffer(buffer); } - self.instance_2d_buffers.deinit(); - sokol.gfx.destroyPipeline(self.instance_2d_pipeline); - self.objects.deinit(); + self.batching_buffers.deinit(); + sokol.gfx.destroyPipeline(self.batching_pipeline); } - fn init() Rendering { + fn flush(self: *Rendering2D, frame: *Frame) void { + const unflushed_count = frame.pushed_batch_count - frame.flushed_batch_count; + + if (unflushed_count == 0) { + return; + } + + sokol.gfx.applyPipeline(self.batching_pipeline); + + sokol.gfx.applyUniforms(.VS, default_2d.SLOT_Projection, sokol.gfx.asRange(&default_2d.Projection{ + .projection = frame.projection, + })); + + const mesh_2d = resource_cast(frame.mesh.?).payload.mesh_2d; + + var bindings = sokol.gfx.Bindings{ + .vertex_buffers = get: { + var buffers = [_]sokol.gfx.Buffer{.{}} ** 8; + + buffers[buffer_indices.mesh] = mesh_2d.vertex_buffer; + + break: get buffers; + }, + + .index_buffer = mesh_2d.index_buffer, + + .fs = switch (resource_cast(frame.texture.?).payload) { + .texture => |texture| .{ + .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; + }, + }, + + .render_target => |render_target| .{ + .images = get: { + var images = [_]sokol.gfx.Image{.{}} ** 12; + + images[0] = render_target.color_image; + + break: get images; + }, + + .samplers = get: { + var samplers = [_]sokol.gfx.Sampler{.{}} ** 8; + + samplers[0] = render_target.sampler; + + break: get samplers; + }, + }, + + else => unreachable, + }, + }; + + while (frame.flushed_batch_count < frame.pushed_batch_count) { + const buffer_index = frame.flushed_batch_count / instances_per_buffer; + const buffer_offset = frame.flushed_batch_count % instances_per_buffer; + const instances_to_flush = @min(instances_per_buffer - buffer_offset, unflushed_count); + + bindings.vertex_buffers[buffer_indices.instance] = self.batching_buffers.values[buffer_index]; + bindings.vertex_buffer_offsets[buffer_indices.instance] = @intCast(@sizeOf(Instance) * buffer_offset); + + sokol.gfx.applyBindings(bindings); + sokol.gfx.draw(0, mesh_2d.index_count, @intCast(instances_to_flush)); + + frame.flushed_batch_count += instances_to_flush; + } + } + + fn init() Rendering2D { sokol.gfx.setup(.{ .environment = .{ .defaults = .{ @@ -470,51 +619,51 @@ const Rendering = struct { }); return .{ - .instance_2d_pipeline = sokol.gfx.makePipeline(.{ + .batching_pipeline = sokol.gfx.makePipeline(.{ .label = "2D drawing pipeline", .layout = .{ .attrs = get: { var attrs = [_]sokol.gfx.VertexAttrState{.{}} ** 16; - attrs[Instance2D.shader.ATTR_vs_mesh_xy] = .{ + attrs[default_2d.ATTR_vs_mesh_xy] = .{ .format = .FLOAT2, - .buffer_index = Instance2D.buffer_indices.mesh, + .buffer_index = buffer_indices.mesh, }; - attrs[Instance2D.shader.ATTR_vs_mesh_uv] = .{ + attrs[default_2d.ATTR_vs_mesh_uv] = .{ .format = .FLOAT2, - .buffer_index = Instance2D.buffer_indices.mesh, + .buffer_index = buffer_indices.mesh, }; - attrs[Instance2D.shader.ATTR_vs_instance_xbasis] = .{ + attrs[default_2d.ATTR_vs_instance_xbasis] = .{ .format = .FLOAT2, - .buffer_index = Instance2D.buffer_indices.instance, + .buffer_index = buffer_indices.instance, }; - attrs[Instance2D.shader.ATTR_vs_instance_ybasis] = .{ + attrs[default_2d.ATTR_vs_instance_ybasis] = .{ .format = .FLOAT2, - .buffer_index = Instance2D.buffer_indices.instance, + .buffer_index = buffer_indices.instance, }; - attrs[Instance2D.shader.ATTR_vs_instance_origin] = .{ + attrs[default_2d.ATTR_vs_instance_origin] = .{ .format = .FLOAT2, - .buffer_index = Instance2D.buffer_indices.instance, + .buffer_index = buffer_indices.instance, }; - attrs[Instance2D.shader.ATTR_vs_instance_tint] = .{ + attrs[default_2d.ATTR_vs_instance_tint] = .{ .format = .UBYTE4N, - .buffer_index = Instance2D.buffer_indices.instance, + .buffer_index = buffer_indices.instance, }; - attrs[Instance2D.shader.ATTR_vs_instance_depth] = .{ + attrs[default_2d.ATTR_vs_instance_depth] = .{ .format = .FLOAT, - .buffer_index = Instance2D.buffer_indices.instance, + .buffer_index = buffer_indices.instance, }; - attrs[Instance2D.shader.ATTR_vs_instance_rect] = .{ + attrs[default_2d.ATTR_vs_instance_rect] = .{ .format = .FLOAT4, - .buffer_index = Instance2D.buffer_indices.instance, + .buffer_index = buffer_indices.instance, }; break: get attrs; @@ -523,169 +672,152 @@ const Rendering = struct { .buffers = get: { var buffers = [_]sokol.gfx.VertexBufferLayoutState{.{}} ** 8; - buffers[Instance2D.buffer_indices.instance].step_func = .PER_INSTANCE; + buffers[buffer_indices.instance].step_func = .PER_INSTANCE; break: get buffers; }, }, - .shader = sokol.gfx.makeShader(Instance2D.shader.draw2dShaderDesc(sokol.gfx.queryBackend())), + .shader = sokol.gfx.makeShader(default_2d.draw2dShaderDesc(sokol.gfx.queryBackend())), .index_type = .UINT16, }), - .instance_2d_buffers = .{.allocator = coral.heap.allocator}, - .objects = .{.allocator = coral.heap.allocator}, + .batching_buffers = .{.allocator = coral.heap.allocator}, }; } - fn flush_instance_2ds(self: *Rendering, frame: *Frame) void { - const unflushed_count = frame.unflushed_instance_2d_count(); + const instances_per_buffer = 512; - if (unflushed_count == 0) { - return; + fn set_target(self: *Rendering2D, frame: *Frame, window: *ext.SDL_Window, target: gfx.Command.Target) void { + defer frame.render_passes += 1; + + if (frame.render_passes != 0) { + self.flush(frame); + sokol.gfx.endPass(); } - 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; - }, + var pass = sokol.gfx.Pass{ + .action = .{ + // TODO: Review if stencil buffer is needed. + .stencil = .{.load_action = .CLEAR}, }, }; - 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; + if (target.clear_color) |color| { + pass.action.colors[0] = .{ + .load_action = .CLEAR, + .clear_value = @bitCast(color), + }; } 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); + pass.action.colors[0] = .{.load_action = .LOAD}; } - 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); + if (target.clear_depth) |depth| { + pass.action.depth = .{ + .load_action = .CLEAR, + .clear_value = depth, + }; + } else { + pass.action.depth = .{.load_action = .LOAD}; } - _ = sokol.gfx.appendBuffer(self.instance_2d_buffers.get().?, sokol.gfx.asRange(&Instance2D{ - .transform = command.transform, - })); + if (target.texture) |render_texture| { + const render_target = resource_cast(render_texture).payload.render_target; - frame.pushed_instance_2d_count += 1; - } + pass.attachments = render_target.attachments; + frame.projection = ortho_projection(0.0, @floatFromInt(render_target.width), 0.0, @floatFromInt(render_target.height), -1.0, 1.0); + } else { + var target_width, var target_height = [_]c_int{0, 0}; - fn remove_object(self: *Rendering, index: usize) ?Object { - const object = self.objects.values[index]; + ext.SDL_GL_GetDrawableSize(window, &target_width, &target_height); + std.debug.assert(target_width > 0 and target_height > 0); - if (object != .empty) { - self.objects.values[index] = .empty; + pass.swapchain = .{ + .width = target_width, + .height = target_height, + .sample_count = 1, + .color_format = .RGBA8, + .depth_format = .DEPTH_STENCIL, + .gl = .{.framebuffer = 0}, + }; - return object; + frame.projection = ortho_projection(0.0, @floatFromInt(target_width), @floatFromInt(target_height), 0.0, -1.0, 1.0); } - return null; + sokol.gfx.beginPass(pass); } }; -pub const RenderCommand = union (enum) { - instance_2d: Instance, - post_process: PostProcess, +const Resource = struct { + has_prev: ?*Resource, + has_next: ?*Resource, - pub const Instance = struct { - texture: gfx.Handle, - mesh_2d: gfx.Handle, - transform: gfx.Transform2D, - }; + payload: union (enum) { + mesh_2d: struct { + index_count: u32, + vertex_buffer: sokol.gfx.Buffer, + index_buffer: sokol.gfx.Buffer, + }, - pub const PostProcess = struct { + texture: struct { + image: sokol.gfx.Image, + sampler: sokol.gfx.Sampler, + }, - }; + render_target: struct { + sampler: sokol.gfx.Sampler, + color_image: sokol.gfx.Image, + depth_image: sokol.gfx.Image, + attachments: sokol.gfx.Attachments, + width: u16, + height: u16, + }, + }, - fn clone(self: RenderCommand, arena: *std.heap.ArenaAllocator) std.mem.Allocator.Error!RenderCommand { - _ = arena; + fn destroy(self: Resource) void { + switch (self.payload) { + .mesh_2d => |mesh_2d| { + sokol.gfx.destroyBuffer(mesh_2d.vertex_buffer); + sokol.gfx.destroyBuffer(mesh_2d.index_buffer); + }, - return switch (self) { - .instance_2d => |instance_2d| .{.instance_2d = instance_2d}, - .post_process => |post_process| .{.post_process = post_process}, - }; + .texture => |texture| { + sokol.gfx.destroyImage(texture.image); + sokol.gfx.destroySampler(texture.sampler); + }, + + .render_target => |render_target| { + sokol.gfx.destroyImage(render_target.color_image); + sokol.gfx.destroyImage(render_target.depth_image); + sokol.gfx.destroySampler(render_target.sampler); + sokol.gfx.destroyAttachments(render_target.attachments); + }, + } } }; -pub const RenderChain = commands.Chain(RenderCommand, RenderCommand.clone); +fn clone_command(self: gfx.Command, arena: *std.heap.ArenaAllocator) std.mem.Allocator.Error!gfx.Command { + _ = arena; -pub const RenderList = commands.List(RenderCommand, RenderCommand.clone); + return switch (self) { + .instance_2d => |instance_2d| .{.instance_2d = instance_2d}, + .target => |target| .{.target = target}, + }; +} + +fn resource_cast(handle: *gfx.Handle) *Resource { + return @ptrCast(@alignCast(handle)); +} + +fn ortho_projection(left: f32, right: f32, bottom: f32, top: f32, zNear: f32, zFar: f32) default_2d.Mat4 { + var result = default_2d.Mat4{.{1, 0, 0, 0}, .{0, 1, 0, 0}, .{0, 0, 1, 0}, .{0, 0, 0, 1}}; + + result[0][0] = 2 / (right - left); + result[1][1] = 2 / (top - bottom); + result[2][2] = - 2 / (zFar - zNear); + result[3][0] = - (right + left) / (right - left); + result[3][1] = - (top + bottom) / (top - bottom); + result[3][2] = - (zFar + zNear) / (zFar - zNear); + + return result; +} diff --git a/src/ona/gfx/formats.zig b/src/ona/gfx/formats.zig index a717cd4..a767d7f 100644 --- a/src/ona/gfx/formats.zig +++ b/src/ona/gfx/formats.zig @@ -72,10 +72,14 @@ pub fn bmp_file_desc( return .{ .texture = .{ - .width = pixel_width, - .data = pixels, .format = .rgba8, - .access = .static, + + .access = .{ + .static = .{ + .width = pixel_width, + .data = pixels, + }, + }, } }; } diff --git a/src/ona/gfx/shaders/instance_2d.glsl b/src/ona/gfx/shaders/default_2d.glsl similarity index 72% rename from src/ona/gfx/shaders/instance_2d.glsl rename to src/ona/gfx/shaders/default_2d.glsl index 160c15c..111abec 100644 --- a/src/ona/gfx/shaders/instance_2d.glsl +++ b/src/ona/gfx/shaders/default_2d.glsl @@ -1,5 +1,7 @@ -@header const Vec2 = @Vector(2, f32) +@header pub const Vec2 = @Vector(2, f32) +@header pub const Mat4 = [4]@Vector(4, f32) @ctype vec2 Vec2 +@ctype mat4 Mat4 @vs vs in vec2 mesh_xy; @@ -12,8 +14,8 @@ in vec4 instance_tint; in float instance_depth; in vec4 instance_rect; -uniform Screen { - vec2 screen_size; +uniform Projection { + mat4 projection; }; out vec4 color; @@ -22,13 +24,10 @@ out vec2 uv; void main() { // Calculate the world position of the vertex const vec2 world_position = instance_origin + mesh_xy.x * instance_xbasis + mesh_xy.y * instance_ybasis; - - // Convert world position to normalized device coordinates (NDC) - // Assuming the screen coordinates range from (0, 0) to (screen_size.x, screen_size.y) - const vec2 ndc_position = (vec2(world_position.x, -world_position.y) / screen_size) * 2.0 - vec2(1.0, -1.0); + const vec2 projected_position = (projection * vec4(world_position, 0, 1)).xy; // Set the position of the vertex in clip space - gl_Position = vec4(ndc_position, instance_depth, 1.0); + gl_Position = vec4(projected_position, instance_depth, 1.0); color = instance_tint; // Calculate the width and height from left, top, right, bottom configuration From 40067c067b913a513a6ae59bc4b40506f307e57b Mon Sep 17 00:00:00 2001 From: kayomn Date: Tue, 2 Jul 2024 23:08:25 +0100 Subject: [PATCH 02/30] Initial replacement of Sokol shdc with a runtime compilation system --- .gitattributes | 2 - .gitignore | 4 +- .gitmodules | 3 + .vscode/settings.json | 14 +- b64_dump.py | 29 + build.zig | 166 ++-- build.zig.zon | 24 +- ext/spirv-cross | 1 + src/coral/coral.zig | 290 ------ src/coral/script/Chunk.zig | 998 -------------------- src/coral/script/Table.zig | 135 --- src/coral/script/tokens.zig | 535 ----------- src/coral/script/tree.zig | 268 ------ src/coral/script/tree/Expr.zig | 906 ------------------ src/coral/script/tree/Stmt.zig | 242 ----- src/coral/slots.zig | 23 - src/{coral => flow}/World.zig | 39 +- src/{coral => flow}/dag.zig | 10 +- src/flow/flow.zig | 273 ++++++ src/{coral/resource.zig => flow/states.zig} | 25 +- src/{coral => flow}/system.zig | 82 +- src/main.zig | 22 +- src/ona/App.zig | 16 +- src/ona/act.zig | 10 +- src/ona/gfx.zig | 58 +- src/ona/gfx/device.zig | 194 +++- src/ona/gfx/formats.zig | 2 +- src/ona/gfx/shaders/default_2d.glsl | 60 -- src/ona/gfx/shaders/render_2d.frag | 17 + src/ona/gfx/shaders/render_2d.vert | 36 + src/ona/gfx/spirv.zig | 130 +++ src/ona/msg.zig | 30 +- src/ona/ona.zig | 18 +- tools/sokol-shdc | 3 - tools/sokol-shdc.exe | 3 - 35 files changed, 915 insertions(+), 3753 deletions(-) create mode 100644 .gitmodules create mode 100755 b64_dump.py create mode 160000 ext/spirv-cross delete mode 100644 src/coral/script/Chunk.zig delete mode 100644 src/coral/script/Table.zig delete mode 100644 src/coral/script/tokens.zig delete mode 100644 src/coral/script/tree.zig delete mode 100644 src/coral/script/tree/Expr.zig delete mode 100644 src/coral/script/tree/Stmt.zig delete mode 100644 src/coral/slots.zig rename src/{coral => flow}/World.zig (54%) rename src/{coral => flow}/dag.zig (93%) create mode 100644 src/flow/flow.zig rename src/{coral/resource.zig => flow/states.zig} (76%) rename src/{coral => flow}/system.zig (83%) delete mode 100644 src/ona/gfx/shaders/default_2d.glsl create mode 100644 src/ona/gfx/shaders/render_2d.frag create mode 100644 src/ona/gfx/shaders/render_2d.vert create mode 100644 src/ona/gfx/spirv.zig delete mode 100755 tools/sokol-shdc delete mode 100644 tools/sokol-shdc.exe diff --git a/.gitattributes b/.gitattributes index 663ee24..95c1536 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1 @@ -tools/sokol-shdc filter=lfs diff=lfs merge=lfs -text -tools/sokol-shdc.exe filter=lfs diff=lfs merge=lfs -text *.bmp filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore index 705556a..f190a33 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ # Generated assets -/.zig-cache +.zig-cache /zig-out -*.glsl.zig +*.spv diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..0e455b8 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "ext/spirv-cross"] + path = ext/spirv-cross + url = https://github.com/KhronosGroup/SPIRV-Cross/ diff --git a/.vscode/settings.json b/.vscode/settings.json index 682ea0f..995d05f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,5 +10,17 @@ "editor.detectIndentation": false, "editor.insertSpaces": false, "editor.rulers": [120], - } + }, + + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.hg": true, + "**/CVS": true, + "**/.DS_Store": true, + "**/Thumbs.db": true, + "**/.zig-cache": true, + "zig-out": true, + ".drone.yml": true, + }, } diff --git a/b64_dump.py b/b64_dump.py new file mode 100755 index 0000000..720bac9 --- /dev/null +++ b/b64_dump.py @@ -0,0 +1,29 @@ +#!/bin/python + +import sys +import base64 +import struct + +def format_base64_to_u32(base64_string): + # Decode the Base64 string + decoded_bytes = base64.b64decode(base64_string) + + # Interpret the bytes as a sequence of u32 values + u32_values = struct.unpack('I' * (len(decoded_bytes) // 4), decoded_bytes) + + # Format the u32 values as C-style hex values + formatted_u32_values = ', '.join(f'0x{value:08x}' for value in u32_values) + + # Split the formatted string into lines of 8 values each + lines = [', '.join(formatted_u32_values.split(', ')[i:i+8]) for i in range(0, len(u32_values), 8)] + + # Print the formatted values + print(',\n'.join(lines)) + +if __name__ == "__main__": + if len(sys.argv) != 2: + print(f"Usage: python {sys.argv[0]} ") + sys.exit(1) + + base64_string = sys.argv[1] + format_base64_to_u32(base64_string) diff --git a/build.zig b/build.zig index 234db97..e7da8c8 100644 --- a/build.zig +++ b/build.zig @@ -6,34 +6,49 @@ pub fn build(b: *std.Build) !void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); + const sokol_dependency = b.dependency("sokol", .{ + .target = target, + .optimize = optimize, + }); + const coral_module = b.createModule(.{ .root_source_file = b.path("src/coral/coral.zig"), }); - const ona_module = add: { - const sokol_dependency = b.dependency("sokol", .{ - .target = target, - .optimize = optimize, - }); + const flow_module = b.createModule(.{ + .root_source_file = b.path("src/flow/flow.zig"), - const module = b.addModule("ona", .{ - .root_source_file = b.path("src/ona/ona.zig"), - - .imports = &.{ - .{ - .name = "sokol", - .module = sokol_dependency.module("sokol"), - }, - - .{ - .name = "coral", - .module = coral_module, - }, + .imports = &.{ + .{ + .name = "coral", + .module = coral_module, }, - }); + } + }); - break: add module; - }; + const ona_module = b.createModule(.{ + .root_source_file = b.path("src/ona/ona.zig"), + + .imports = &.{ + .{ + .name = "sokol", + .module = sokol_dependency.module("sokol"), + }, + + .{ + .name = "coral", + .module = coral_module, + }, + + .{ + .name = "flow", + .module = flow_module, + }, + }, + }); + + ona_module.addIncludePath(b.path("ext/")); + ona_module.linkLibrary(build_spirvcross(b, target, optimize)); b.step("test", "Run unit tests").dependOn(create: { const tests = b.addTest(.{ @@ -46,92 +61,59 @@ pub fn build(b: *std.Build) !void { }); b.installArtifact(add: { - const compile_step = b.addExecutable(.{ + const exe = b.addExecutable(.{ .name = "main", .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, }); - compile_step.root_module.addImport("ona", ona_module); - compile_step.root_module.addImport("coral", coral_module); - compile_step.linkLibC(); - compile_step.linkSystemLibrary("SDL2"); + exe.root_module.addImport("ona", ona_module); + exe.root_module.addImport("coral", coral_module); + exe.linkLibC(); + exe.linkSystemLibrary("SDL2"); - try depend_on_shaders(b, target, "src/ona/gfx/shaders/", &compile_step.step); - - break: add compile_step; + break: add exe; }); } -fn depend_on_shaders( +fn build_spirvcross( b: *std.Build, target: std.Build.ResolvedTarget, - shader_dir_path: []const u8, - step: *std.Build.Step, -) !void { - var dir = try std.fs.cwd().openDir(shader_dir_path, .{ .iterate = true }); + mode: std.builtin.OptimizeMode, +) *std.Build.Step.Compile { + const dir = "ext/spirv-cross/"; - defer dir.close(); - - var walker = try dir.walk(b.allocator); - - defer walker.deinit(); - - const shdc_path = switch (builtin.os.tag) { - .windows => "./tools/sokol-shdc.exe", - .linux => "./tools/sokol-shdc", - else => @compileError("cannot compile sokol shaders on this platform"), + const sources = [_][]const u8{ + "spirv_cross.cpp", + "spirv_parser.cpp", + "spirv_cross_parsed_ir.cpp", + "spirv_cfg.cpp", + "spirv_glsl.cpp", + "spirv_msl.cpp", + "spirv_hlsl.cpp", + "spirv_reflect.cpp", + "spirv_cross_util.cpp", + "spirv_cross_c.cpp", }; - const path_buffer_max = 255; - var input_path_buffer = [_]u8{undefined} ** path_buffer_max; - var output_path_buffer = [_]u8{undefined} ** path_buffer_max; + const lib = b.addStaticLibrary(.{ + .name = "spirvcross", + .target = target, + .optimize = mode, + }); - const glsl = if (target.result.isDarwin()) "glsl410" else "glsl430"; - const slang = glsl ++ ":metal_macos:hlsl5:glsl300es:wgsl"; - - while (try walker.next()) |entry| { - if (entry.kind != .file or !std.mem.endsWith(u8, entry.path, ".glsl")) { - continue; - } - - const input_path = try std.fmt.bufPrint(&input_path_buffer, "{s}{s}", .{shader_dir_path, entry.path}); - const output_path = try std.fmt.bufPrint(&output_path_buffer, "{s}.zig", .{input_path}); - const output = std.fs.path.basename(output_path); - - dir.access(output, .{.mode = .read_only}) catch { - const cmd = b.addSystemCommand(&.{ - shdc_path, - "-i", - input_path, - "-o", - output_path, - "-l", - slang, - "-f", - "sokol_zig", - }); - - step.dependOn(&cmd.step); - - continue; - }; - - if ((try dir.statFile(entry.path)).mtime > (try dir.statFile(output)).mtime) { - const cmd = b.addSystemCommand(&.{ - shdc_path, - "-i", - input_path, - "-o", - output_path, - "-l", - slang, - "-f", - "sokol_zig", - }); - - step.dependOn(&cmd.step); - } + switch (lib.rootModuleTarget().abi) { + .msvc => lib.linkLibC(), + else => lib.linkLibCpp(), } + + inline for (sources) |src| { + lib.addCSourceFile(.{ + .file = b.path(dir ++ src), + .flags = &.{"-fstrict-aliasing", "-DSPIRV_CROSS_C_API_GLSL", "-DSPIRV_CROSS_C_API_HLSL", "-DSPIRV_CROSS_C_API_MSL"}, + }); + } + + return lib; } diff --git a/build.zig.zon b/build.zig.zon index 3fcc20d..a155592 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,17 +1,17 @@ .{ - .name = "Ona", - .version = "0.0.1", - .paths = .{ - "src", - "build.zig", - "build.zig.zon", + .name = "Ona", + .version = "0.0.1", + .paths = .{ + "src", + "build.zig", + "build.zig.zon", "LICENSE", "README.md", - }, - .dependencies = .{ - .sokol = .{ - .url = "git+https://github.com/floooh/sokol-zig.git#7c25767e51aa06dd5fb0684e4a8f2200d182ef27", + }, + .dependencies = .{ + .sokol = .{ + .url = "git+https://github.com/floooh/sokol-zig.git#7c25767e51aa06dd5fb0684e4a8f2200d182ef27", .hash = "1220fa7f47fbaf2f1ed8c17fab2d23b6a85bcbbc4aa0b3802c90a3e8bf6fca1f8569", - }, - }, + }, + }, } diff --git a/ext/spirv-cross b/ext/spirv-cross new file mode 160000 index 0000000..6fd1f75 --- /dev/null +++ b/ext/spirv-cross @@ -0,0 +1 @@ +Subproject commit 6fd1f75636b1c424b809ad8a84804654cf5ae48b diff --git a/src/coral/coral.zig b/src/coral/coral.zig index d760db7..fae4ce8 100644 --- a/src/coral/coral.zig +++ b/src/coral/coral.zig @@ -1,7 +1,5 @@ pub const ascii = @import("./ascii.zig"); -pub const dag = @import("./dag.zig"); - pub const files = @import("./files.zig"); pub const hashes = @import("./hashes.zig"); @@ -16,296 +14,8 @@ pub const scalars = @import("./scalars.zig"); pub const slices = @import("./slices.zig"); -pub const slots = @import("./slots.zig"); - pub const stack = @import("./stack.zig"); -pub const system = @import("./system.zig"); - pub const utf8 = @import("./utf8.zig"); pub const vectors = @import("./vectors.zig"); - -pub const World = @import("./World.zig"); - -const std = @import("std"); - -pub const ResourceOptions = struct { - thread_restriction: World.ThreadRestriction, - read_only: bool = false, -}; - -pub fn Read(comptime Value: type) type { - return Resource(Value, .{ - .thread_restriction = .none, - .read_only = true, - }); -} - -pub fn ReadBlocking(comptime Value: type) type { - return Resource(Value, .{ - .thread_restriction = .main, - .read_only = true, - }); -} - -pub fn Resource(comptime Value: type, comptime options: ResourceOptions) type { - const value_info = @typeInfo(Value); - - const Qualified = switch (value_info) { - .Optional => @Type(.{ - .Optional = .{ - .child = .{ - .Pointer = .{ - .is_allowzero = false, - .sentinel = null, - .address_space = .generic, - .is_volatile = false, - .alignment = @alignOf(Value), - .size = .One, - .child = Value, - .is_const = options.read_only, - }, - }, - }, - }), - - else => @Type(.{ - .Pointer = .{ - .is_allowzero = false, - .sentinel = null, - .address_space = .generic, - .is_volatile = false, - .alignment = @alignOf(Value), - .size = .One, - .child = Value, - .is_const = options.read_only, - }, - }), - }; - - return struct { - res: Qualified, - - const Self = @This(); - - pub const State = struct { - res: Qualified, - }; - - pub fn bind(context: system.BindContext) std.mem.Allocator.Error!State { - const thread_restriction_name = switch (thread_restriction) { - .main => "main thread-restricted ", - .none => "" - }; - - const res = switch (options.read_only) { - true => (try context.register_read_only_resource_access(thread_restriction, Value)), - false => (try context.register_read_write_resource_access(thread_restriction, Value)), - }; - - return .{ - .res = switch (value_info) { - .Optional => res, - - else => res orelse { - @panic(std.fmt.comptimePrint("attempt to use {s}{s} {s} that has not yet been set", .{ - thread_restriction_name, - if (options.read_only) "read-only" else "read-write", - @typeName(Value), - })); - }, - }, - }; - } - - pub fn init(state: *State) Self { - return .{ - .res = state.res, - }; - } - - pub const thread_restriction = options.thread_restriction; - }; -} - -pub fn Write(comptime Value: type) type { - return Resource(Value, .{ - .thread_restriction = .none, - }); -} - -pub fn WriteBlocking(comptime Value: type) type { - return Resource(Value, .{ - .thread_restriction = .main, - }); -} - -fn parameter_type(comptime Value: type) *const system.Info.Parameter { - const has_state = @hasDecl(Value, "State"); - - 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); - - 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; - } - } - - fn init(argument: *anyopaque, state: ?*anyopaque) void { - const value_name = @typeName(Value); - - if (!@hasDecl(Value, "init")) { - @compileError("an `init` declaration on " ++ value_name ++ " is requied for parameter types"); - } - - const init_type = @typeInfo(@TypeOf(Value.init)); - - if (init_type != .Fn) { - @compileError("`init` declaration on " ++ value_name ++ " must be a fn"); - } - - if (init_type.Fn.return_type.? != Value) { - @compileError("`init` fn on " ++ value_name ++ " must return a " ++ value_name); - } - - 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(); - } - } - - fn unbind(allocator: std.mem.Allocator, state: ?*anyopaque) void { - if (@hasDecl(Value, "unbind")) { - if (has_state) { - const typed_state = @as(*Value.State, @ptrCast(@alignCast(state.?))); - - Value.unbind(typed_state); - allocator.destroy(typed_state); - } else { - Value.unbind(); - } - } - } - }; - - return comptime &.{ - .thread_restriction = if (@hasDecl(Value, "thread_restriction")) Value.thread_restriction else .none, - .init = parameters.init, - .bind = parameters.bind, - .unbind = parameters.unbind, - }; -} - -pub fn system_fn(comptime call: anytype) *const system.Info { - const Call = @TypeOf(call); - - const system_info = comptime generate: { - switch (@typeInfo(Call)) { - .Fn => |call_fn| { - if (call_fn.params.len > system.max_parameters) { - @compileError("number of parameters to `call` cannot be more than 16"); - } - - const systems = struct { - fn run(parameters: []const *const system.Info.Parameter, states: *const [system.max_parameters]?*anyopaque) anyerror!void { - var call_args = @as(std.meta.ArgsTuple(Call), undefined); - - inline for (parameters, &call_args, states[0 .. parameters.len]) |parameter, *call_arg, state| { - parameter.init(call_arg, state); - } - - switch (@typeInfo(call_fn.return_type.?)) { - .Void => @call(.auto, call, call_args), - .ErrorUnion => try @call(.auto, call, call_args), - else => @compileError("number of parameters to `call` must return void or !void"), - } - } - }; - - var parameters = @as([system.max_parameters]*const system.Info.Parameter, undefined); - var thread_restriction = World.ThreadRestriction.none; - - for (0 .. call_fn.params.len) |index| { - const CallParam = call_fn.params[index].type.?; - const parameter = parameter_type(CallParam); - - if (parameter.thread_restriction != .none) { - if (thread_restriction != .none and thread_restriction != parameter.thread_restriction) { - @compileError("a system may not have conflicting thread restrictions"); - } - - thread_restriction = parameter.thread_restriction; - } - - parameters[index] = parameter; - } - - break: generate &.{ - .parameters = parameters, - .parameter_count = call_fn.params.len, - .execute = systems.run, - .thread_restriction = thread_restriction, - }; - }, - - else => @compileError("parameter `call` must be a function"), - } - }; - - return system_info; -} diff --git a/src/coral/script/Chunk.zig b/src/coral/script/Chunk.zig deleted file mode 100644 index 9ca1028..0000000 --- a/src/coral/script/Chunk.zig +++ /dev/null @@ -1,998 +0,0 @@ -const coral = @import("coral"); - -const file = @import("../file.zig"); - -const script = @import("../script.zig"); - -const std = @import("std"); - -const tokens = @import("./tokens.zig"); - -const tree = @import("./tree.zig"); - -name: *script.Object, -arity: u8, -opcodes: coral.Stack(Opcode), -lines: coral.Stack(tokens.Line), -cursor: usize, -constants: coral.Stack(*script.Object), -bindings: []?*script.Object, -externals: *script.Object, - -const Compiler = struct { - chunk: *Self, - env: *script.Runtime, - - fn compile_argument(self: *const Compiler, environment: *const tree.Environment, initial_argument: ?*const tree.Expr) script.Error!u8 { - // TODO: Exceeding 255 arguments will make the VM crash. - var maybe_argument = initial_argument; - var argument_count = @as(u8, 0); - - while (maybe_argument) |argument| { - try self.compile_expression(environment, argument, null); - - maybe_argument = argument.next; - argument_count += 1; - } - - return argument_count; - } - - fn compile_expression(self: *const Compiler, environment: *const tree.Environment, expression: *const tree.Expr, name: ?[]const u8) script.Error!void { - const number_format = coral.utf8.DecimalFormat{ - .delimiter = "_", - .positive_prefix = .none, - }; - - switch (expression.kind) { - .nil_literal => try self.chunk.write(expression.line, .push_nil), - .true_literal => try self.chunk.write(expression.line, .push_true), - .false_literal => try self.chunk.write(expression.line, .push_false), - - .number_literal => |literal| { - for (literal) |codepoint| { - if (codepoint == '.') { - return self.chunk.write(expression.line, .{ - .push_const = try self.declare_float(number_format.parse(literal, script.Float) orelse unreachable), - }); - } - } - - try self.chunk.write(expression.line, .{ - .push_const = try self.declare_fixed(number_format.parse(literal, script.Fixed) orelse unreachable), - }); - }, - - .string_literal => |literal| { - try self.chunk.write(expression.line, .{.push_const = try self.declare_string(literal)}); - }, - - .vector2 => |vector2| { - try self.compile_expression(environment, vector2.x, null); - try self.compile_expression(environment, vector2.y, null); - try self.chunk.write(expression.line, .push_vector2); - }, - - .vector3 => |vector3| { - try self.compile_expression(environment, vector3.x, null); - try self.compile_expression(environment, vector3.y, null); - try self.compile_expression(environment, vector3.z, null); - try self.chunk.write(expression.line, .push_vector3); - }, - - .string_template => { - var current_expression = expression.next orelse { - return self.chunk.write(expression.line, .{.push_const = try self.declare_string("")}); - }; - - var component_count = @as(u8, 0); - - while (true) { - try self.compile_expression(environment, current_expression, null); - - component_count += 1; - - current_expression = current_expression.next orelse { - return self.chunk.write(expression.line, .{.push_concat = component_count}); - }; - } - }, - - .symbol_literal => |literal| { - try self.chunk.write(expression.line, .{.push_const = try self.declare_symbol(literal)}); - }, - - .table => |table| { - var entries = table.nodes(); - var num_entries = @as(u16, 0); - - while (entries.next()) |entry| { - try self.compile_expression(environment, entry.key, null); - try self.compile_expression(environment, entry.value, null); - - num_entries = coral.scalars.add(num_entries, 1) orelse { - return self.env.raise(error.OutOfMemory, "too many initializer values", .{}); - }; - } - - try self.chunk.write(expression.line, .{.push_table = num_entries}); - }, - - .lambda_construct => |lambda_construct| { - var chunk = try Self.init(self.env, name orelse "", lambda_construct.environment, &.{}); - - errdefer chunk.deinit(self.env); - - if (lambda_construct.environment.capture_count == 0) { - try self.chunk.write(expression.line, .{.push_const = try self.declare_chunk(chunk)}); - } else { - const lambda_captures = lambda_construct.environment.get_captures(); - var index = lambda_captures.len; - - while (index != 0) { - index -= 1; - - try self.chunk.write(expression.line, switch (lambda_captures[index]) { - .declaration_index => |declaration_index| .{.push_local = declaration_index}, - .capture_index => |capture_index| .{.push_binding = capture_index}, - }); - } - - try self.chunk.write(expression.line, .{.push_const = try self.declare_chunk(chunk)}); - try self.chunk.write(expression.line, .{.bind = lambda_construct.environment.capture_count}); - } - }, - - .binary_op => |binary_op| { - try self.compile_expression(environment, binary_op.lhs_operand, null); - try self.compile_expression(environment, binary_op.rhs_operand, null); - - try self.chunk.write(expression.line, switch (binary_op.operation) { - .addition => .add, - .subtraction => .sub, - .multiplication => .mul, - .divsion => .div, - .greater_equals_comparison => .cge, - .greater_than_comparison => .cgt, - .equals_comparison => .eql, - .less_than_comparison => .clt, - .less_equals_comparison => .cle, - }); - }, - - .unary_op => |unary_op| { - try self.compile_expression(environment, unary_op.operand, null); - - try self.chunk.write(expression.line, switch (unary_op.operation) { - .boolean_negation => .not, - .numeric_negation => .neg, - }); - }, - - .invoke => |invoke| { - const argument_count = try self.compile_argument(environment, invoke.argument); - - try self.compile_expression(environment, invoke.object, null); - try self.chunk.write(expression.line, .{.call = argument_count}); - }, - - .group => |group| try self.compile_expression(environment, group, null), - - .declaration_get => |declaration_get| { - if (get_local_index(environment, declaration_get.declaration)) |index| { - if (is_declaration_boxed(declaration_get.declaration)) { - try self.chunk.write(expression.line, .{.push_local = index}); - try self.chunk.write(expression.line, .get_box); - } else { - try self.chunk.write(expression.line, .{.push_local = index}); - } - - return; - } - - if (try get_binding_index(environment, declaration_get.declaration)) |index| { - try self.chunk.write(expression.line, .{.push_binding = index}); - - if (is_declaration_boxed(declaration_get.declaration)) { - try self.chunk.write(expression.line, .get_box); - } - - return; - } - - return self.env.raise(error.IllegalState, "local out of scope", .{}); - }, - - .declaration_set => |declaration_set| { - if (get_local_index(environment, declaration_set.declaration)) |index| { - if (is_declaration_boxed(declaration_set.declaration)) { - try self.chunk.write(expression.line, .{.push_local = index}); - try self.compile_expression(environment, declaration_set.assign, null); - try self.chunk.write(expression.line, .set_box); - } else { - try self.compile_expression(environment, declaration_set.assign, null); - try self.chunk.write(expression.line, .{.set_local = index}); - } - - return; - } - - if (try get_binding_index(environment, declaration_set.declaration)) |index| { - try self.compile_expression(environment, declaration_set.assign, null); - try self.chunk.write(expression.line, .{.push_binding = index}); - - if (is_declaration_boxed(declaration_set.declaration)) { - try self.chunk.write(expression.line, .set_box); - } - - return; - } - - return self.env.raise(error.IllegalState, "local out of scope", .{}); - }, - - .field_get => |field_get| { - try self.chunk.write(expression.line, .{.push_const = try self.declare_symbol(field_get.identifier)}); - try self.compile_expression(environment, field_get.object, null); - try self.chunk.write(expression.line, .get_dynamic); - }, - - .field_set => |field_set| { - try self.chunk.write(expression.line, .{.push_const = try self.declare_symbol(field_set.identifier)}); - try self.compile_expression(environment, field_set.assign, null); - try self.compile_expression(environment, field_set.object, null); - try self.chunk.write(expression.line, .set_dynamic); - }, - - .subscript_get => |subscript_get| { - try self.compile_expression(environment, subscript_get.index, null); - try self.compile_expression(environment, subscript_get.object, null); - try self.chunk.write(expression.line, .get_dynamic); - }, - - .subscript_set => |subscript_set| { - try self.compile_expression(environment, subscript_set.index, null); - try self.compile_expression(environment, subscript_set.assign, null); - try self.compile_expression(environment, subscript_set.object, null); - try self.chunk.write(expression.line, .set_dynamic); - }, - - .external_get => |external_get| { - try self.chunk.write(expression.line, .{.push_const = try self.declare_symbol(external_get.name)}); - try self.chunk.write(expression.line, .get_external); - }, - } - } - - pub fn compile_environment(self: *const Compiler, environment: *const tree.Environment) script.Error!void { - if (environment.statement) |statement| { - const last_statement = try self.compile_statement(environment, statement); - - if (last_statement.kind != .@"return") { - try self.chunk.write(last_statement.line, .push_nil); - } - } - } - - fn compile_statement(self: *const Compiler, environment: *const tree.Environment, initial_statement: *const tree.Stmt) script.Error!*const tree.Stmt { - var current_statement = initial_statement; - - while (true) { - switch (current_statement.kind) { - .@"return" => |@"return"| { - if (@"return".returned_expression) |expression| { - try self.compile_expression(environment, expression, null); - } else { - try self.chunk.write(current_statement.line, .push_nil); - } - - // TODO: Omit ret calls at ends of chunk. - try self.chunk.write(current_statement.line, .ret); - }, - - .@"while" => |@"while"| { - try self.compile_expression(environment, @"while".loop_expression, null); - try self.chunk.write(current_statement.line, .{.jf = 0}); - - const origin_index = @as(u16, @intCast(self.chunk.opcodes.values.len - 1)); - - _ = try self.compile_statement(environment, @"while".loop); - self.chunk.opcodes.values[origin_index].jf = @intCast(self.chunk.opcodes.values.len - 1); - - try self.compile_expression(environment, @"while".loop_expression, null); - try self.chunk.write(current_statement.line, .{.jt = origin_index}); - }, - - .@"if" => |@"if"| { - try self.compile_expression(environment, @"if".then_expression, null); - try self.chunk.write(current_statement.line, .{.jf = 0}); - - const origin_index = @as(u16, @intCast(self.chunk.opcodes.values.len - 1)); - - _ = try self.compile_statement(environment, @"if".@"then"); - self.chunk.opcodes.values[origin_index].jf = @intCast(self.chunk.opcodes.values.len - 1); - - if (@"if".@"else") |@"else"| { - _ = try self.compile_statement(environment, @"else"); - } - }, - - .declare => |declare| { - try self.compile_expression(environment, declare.initial_expression, declare.declaration.identifier); - - if (is_declaration_boxed(declare.declaration)) { - try self.chunk.write(current_statement.line, .push_boxed); - } - }, - - .top_expression => |top_expression| { - try self.compile_expression(environment, top_expression, null); - - if (top_expression.kind == .invoke) { - try self.chunk.write(current_statement.line, .pop); - } - }, - } - - current_statement = current_statement.next orelse return current_statement; - } - } - - const constants_max = @as(usize, std.math.maxInt(u16)); - - fn declare_chunk(self: *const Compiler, chunk: Self) script.Error!u16 { - if (self.chunk.constants.values.len == std.math.maxInt(u16)) { - return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{ - .max = @as(usize, std.math.maxInt(u16)), - }); - } - - const constant = (try self.env.new_dynamic(chunk)).pop().?; - - errdefer self.env.release(constant); - - try self.chunk.constants.push(constant); - - return @intCast(self.chunk.constants.values.len - 1); - } - - fn declare_fixed(self: *const Compiler, fixed: script.Fixed) script.Error!u16 { - if (self.chunk.constants.values.len == constants_max) { - return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{ - .max = constants_max, - }); - } - - const constant = (try self.env.new_fixed(fixed)).pop().?; - - errdefer self.env.release(constant); - - try self.chunk.constants.push(constant); - - return @intCast(self.chunk.constants.values.len - 1); - } - - fn declare_float(self: *const Compiler, float: script.Float) script.Error!u16 { - if (self.chunk.constants.values.len == constants_max) { - return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{ - .max = constants_max, - }); - } - - const constant = (try self.env.new_float(float)).pop().?; - - errdefer self.env.release(constant); - - try self.chunk.constants.push(constant); - - return @intCast(self.chunk.constants.values.len - 1); - } - - fn declare_string(self: *const Compiler, string: []const u8) script.Error!u16 { - if (self.chunk.constants.values.len == constants_max) { - return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{ - .max = constants_max, - }); - } - - const constant = (try self.env.new_string(string)).pop().?; - - errdefer self.env.release(constant); - - try self.chunk.constants.push(constant); - - return @intCast(self.chunk.constants.values.len - 1); - } - - fn declare_vector2(self: *const Compiler, vector: script.Vector2) script.Error!u16 { - if (self.chunk.constants.values.len == constants_max) { - return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{ - .max = constants_max, - }); - } - - const constant = (try self.env.new_vector2(vector)).pop().?; - - errdefer self.env.release(constant); - - try self.chunk.constants.push(constant); - - return @intCast(self.chunk.constants.values.len - 1); - } - - fn declare_vector3(self: *const Compiler, vector: script.Vector3) script.Error!u16 { - if (self.chunk.constants.values.len == constants_max) { - return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{ - .max = constants_max, - }); - } - - const constant = (try self.env.new_vector3(vector)).pop().?; - - errdefer self.env.release(constant); - - try self.chunk.constants.push(constant); - - return @intCast(self.chunk.constants.values.len - 1); - } - - fn declare_symbol(self: *const Compiler, symbol: []const u8) script.Error!u16 { - if (self.chunk.constants.values.len == constants_max) { - return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{ - .max = constants_max, - }); - } - - const constant = (try self.env.new_symbol(symbol)).pop().?; - - errdefer self.env.release(constant); - - try self.chunk.constants.push(constant); - - return @intCast(self.chunk.constants.values.len - 1); - } - - fn get_binding_index(environment: *const tree.Environment, declaration: *const tree.Declaration) script.Error!?u8 { - var binding_index = @as(u8, 0); - - while (binding_index < environment.capture_count) : (binding_index += 1) { - var capture = &environment.captures[binding_index]; - var target_environment = environment.enclosing orelse return null; - - while (capture.* == .capture_index) { - capture = &target_environment.captures[capture.capture_index]; - target_environment = target_environment.enclosing orelse return null; - } - - std.debug.assert(capture.* == .declaration_index); - - if (&target_environment.declarations[capture.declaration_index] == declaration) { - return binding_index; - } - } - - return null; - } - - fn get_local_index(environment: *const tree.Environment, declaration: *const tree.Declaration) ?u8 { - var remaining = environment.declaration_count; - - while (remaining != 0) { - remaining -= 1; - - if (&environment.declarations[remaining] == declaration) { - return remaining; - } - } - - return null; - } - - fn is_declaration_boxed(declaration: *const tree.Declaration) bool { - return declaration.is.captured and !declaration.is.readonly; - } -}; - -pub const Opcode = union (enum) { - ret, - pop, - push_nil, - push_true, - push_false, - push_const: u16, - push_local: u8, - push_top, - push_vector2, - push_vector3, - push_table: u16, - push_binding: u8, - push_concat: u8, - push_boxed, - set_local: u8, - get_dynamic, - set_dynamic, - get_external, - get_box, - set_box, - call: u8, - bind: u8, - - not, - neg, - - add, - sub, - mul, - div, - - eql, - cgt, - clt, - cge, - cle, - - jt: u16, - jf: u16, -}; - -pub const External = struct {[]const u8, *script.Object}; - -const Self = @This(); - -pub fn deinit(self: *Self, env: *script.Runtime) void { - while (self.constants.pop()) |constant| { - env.release(constant.*); - } - - self.constants.deinit(); - self.opcodes.deinit(); - self.lines.deinit(); - env.release(self.name); - env.release(self.externals); - - if (self.bindings.len != 0) { - for (self.bindings) |binding| { - if (binding) |value| { - env.release(value); - } - } - - env.allocator.free(self.bindings); - } - - self.bindings = &.{}; -} - -pub fn dump(chunk: Self, env: *script.Runtime) script.Error!*script.Object { - var opcode_cursor = @as(u32, 0); - var buffer = coral.list.ByteStack.init(env.allocator); - - defer buffer.deinit(); - - const writer = coral.list.stack_as_writer(&buffer); - - _ = coral.utf8.print_string(writer, "\n"); - - while (opcode_cursor < chunk.opcodes.values.len) : (opcode_cursor += 1) { - _ = coral.utf8.print_formatted(writer, "[{instruction}]: ", .{.instruction = opcode_cursor}); - - _ = switch (chunk.opcodes.values[opcode_cursor]) { - .ret => coral.utf8.print_string(writer, "ret\n"), - .pop => coral.utf8.print_string(writer, "pop\n"), - .push_nil => coral.utf8.print_string(writer, "push nil\n"), - .push_true => coral.utf8.print_string(writer, "push true\n"), - .push_false => coral.utf8.print_string(writer, "push false\n"), - - .push_const => |push_const| print: { - const string_ref = (try (try env.push(try chunk.get_constant(env, push_const))).to_string()).pop().?; - - defer env.release(string_ref); - - const string = string_ref.is_string(); - - break: print coral.utf8.print_formatted(writer, "push const ({value})\n", .{.value = string.?}); - }, - - .push_local => |push_local| coral.utf8.print_formatted(writer, "push local ({local})\n", .{ - .local = push_local, - }), - - .push_top => coral.utf8.print_string(writer, "push top\n"), - - .push_table => |push_table| coral.utf8.print_formatted(writer, "push table ({count})\n", .{ - .count = push_table, - }), - - .push_boxed => coral.utf8.print_string(writer, "push boxed\n"), - - .push_binding => |push_binding| coral.utf8.print_formatted(writer, "push binding ({binding})\n", .{ - .binding = push_binding, - }), - - .push_concat => |push_concat| coral.utf8.print_formatted(writer, "push concat ({count})\n", .{ - .count = push_concat, - }), - - .push_builtin => |push_builtin| coral.utf8.print_formatted(writer, "push builtin ({builtin})\n", .{ - .builtin = switch (push_builtin) { - .import => "import", - .print => "print", - .vec2 => "vec2", - .vec3 => "vec3", - }, - }), - - .bind => |bind| coral.utf8.print_formatted(writer, "bind ({count})\n", .{ - .count = bind, - }), - - .set_local => |local_set| coral.utf8.print_formatted(writer, "set local ({local})\n", .{ - .local = local_set, - }), - - .get_box => coral.utf8.print_string(writer, "get box\n"), - .set_box => coral.utf8.print_string(writer, "set box\n"), - .get_dynamic => coral.utf8.print_string(writer, "get dynamic\n"), - .set_dynamic => coral.utf8.print_string(writer, "set dynamic\n"), - .call => |call| coral.utf8.print_formatted(writer, "call ({count})\n", .{.count = call}), - .not => coral.utf8.print_string(writer, "not\n"), - .neg => coral.utf8.print_string(writer, "neg\n"), - .add => coral.utf8.print_string(writer, "add\n"), - .sub => coral.utf8.print_string(writer, "sub\n"), - .mul => coral.utf8.print_string(writer, "mul\n"), - .div => coral.utf8.print_string(writer, "div\n"), - .eql => coral.utf8.print_string(writer, "eql\n"), - .cgt => coral.utf8.print_string(writer, "cgt\n"), - .clt => coral.utf8.print_string(writer, "clt\n"), - .cge => coral.utf8.print_string(writer, "cge\n"), - .cle => coral.utf8.print_string(writer, "cle\n"), - .jf => |jf| coral.utf8.print_formatted(writer, "jf ({instruction})\n", .{.instruction = jf}), - .jt => |jt| coral.utf8.print_formatted(writer, "jt ({instruction})\n", .{.instruction = jt}), - }; - } - - return (try env.new_string(buffer.values)).pop().?; -} - -pub fn execute(self: *Self, env: *script.Runtime) script.Error!?*script.Object { - self.cursor = 0; - - while (self.cursor < self.opcodes.values.len) : (self.cursor += 1) { - switch (self.opcodes.values[self.cursor]) { - .ret => break, - .pop => env.discard(), - .push_nil => _ = try env.push(null), - .push_true => _ = try env.new_boolean(true), - .push_false => _ = try env.new_boolean(false), - .push_const => |push_const| _ = try env.push(try self.get_constant(env, push_const)), - .push_local => |push_local| _ = try env.local_get(push_local), - .push_top => _ = try env.local_top(), - - .push_vector2 => { - const y = try env.expect_float(try env.expect_object(env.pop())); - const x = try env.expect_float(try env.expect_object(env.pop())); - - _ = try env.new_vector2(.{@floatCast(x), @floatCast(y)}); - }, - - .push_vector3 => { - const z = try env.expect_float(try env.expect_object(env.pop())); - const y = try env.expect_float(try env.expect_object(env.pop())); - const x = try env.expect_float(try env.expect_object(env.pop())); - - _ = try env.new_vector3(.{@floatCast(x), @floatCast(y), @floatCast(z)}); - }, - - .push_table => |push_table| { - const table = (try env.new_table()).pop().?; - - defer env.release(table); - - var popped = @as(usize, 0); - - while (popped < push_table) : (popped += 1) { - if (env.pop()) |object| { - defer env.release(object); - - try env.index_set(table, object); - } else { - env.release(try env.expect_object(env.pop())); - } - } - - _ = try env.push(table); - }, - - .push_boxed => { - const value = env.pop(); - - defer { - if (value) |object| { - env.release(object); - } - } - - _ = try env.new_boxed(value); - }, - - .push_binding => |push_binding| _ = try env.push(try self.get_binding(env, push_binding)), - .push_concat => |push_concat| _ = try env.concat(push_concat), - - .bind => |bind| { - const callable = try env.expect_object(env.pop()); - - defer env.release(callable); - - const chunk = try env.expect_dynamic(callable, Self); - - if (chunk.bindings.len != 0) { - return env.raise(error.IllegalState, "cannot bind values to an already-bound chunk", .{}); - } - - chunk.bindings = try env.allocator.alloc(?*script.Object, bind); - - errdefer env.allocator.free(chunk.bindings); - - for (0 .. bind) |index| { - const value = env.pop(); - - errdefer { - if (value) |object| { - env.release(object); - } - } - - chunk.bindings[index] = value; - } - - _ = try env.push(callable); - }, - - .set_local => |local_set| _ = try env.local_set(local_set, env.pop()), - .get_box => _ = try env.boxed_get(), - - .set_box => { - const value = env.pop(); - - defer { - if (value) |object| { - env.release(object); - } - } - - try env.boxed_set(value); - }, - - .get_dynamic => { - const indexable = try env.expect_object(env.pop()); - - defer env.release(indexable); - - _ = try env.index_get(indexable); - }, - - .set_dynamic => { - const indexable = try env.expect_object(env.pop()); - - defer env.release(indexable); - - const value = env.pop(); - - defer { - if (value) |object| { - env.release(object); - } - } - - try env.index_set(indexable, value); - }, - - .get_external => _ = try env.index_get(self.externals), - .call => |call| _ = try env.call(call), - - .not => { - const object = try env.expect_object(env.pop()); - - defer env.release(object); - - _ = try env.new_boolean(object.is_false()); - }, - - .neg => _ = try env.neg(), - - .add => { - const addable = try env.expect_object(env.pop()); - - defer env.release(addable); - - _ = switch (try env.expect_numeric(addable)) { - .fixed => |fixed| try env.fixed_add(fixed), - .float => |float| try env.float_add(float), - .vector2 => |vector2| try env.vector2_add(vector2), - .vector3 => |vector3| try env.vector3_add(vector3), - }; - }, - - .sub => { - const subtractable = try env.expect_object(env.pop()); - - defer env.release(subtractable); - - _ = switch (try env.expect_numeric(subtractable)) { - .fixed => |fixed| try env.fixed_subtract(fixed), - .float => |float| try env.float_subtract(float), - .vector2 => |vector2| try env.vector2_subtract(vector2), - .vector3 => |vector3| try env.vector3_subtract(vector3), - }; - }, - - .mul => { - const multiplicable = try env.expect_object(env.pop()); - - defer env.release(multiplicable); - - _ = switch (try env.expect_numeric(multiplicable)) { - .fixed => |fixed| try env.fixed_multiply(fixed), - .float => |float| try env.float_multiply(float), - .vector2 => |vector2| try env.vector2_multiply(vector2), - .vector3 => |vector3| try env.vector3_multiply(vector3), - }; - }, - - .div => { - const divisible = try env.expect_object(env.pop()); - - defer env.release(divisible); - - _ = switch (try env.expect_numeric(divisible)) { - .fixed => |fixed| try env.fixed_divide(fixed), - .float => |float| try env.float_divide(float), - .vector2 => |vector2| try env.vector2_divide(vector2), - .vector3 => |vector3| try env.vector3_divide(vector3), - }; - }, - - .eql => { - if (env.pop()) |equatable| { - defer env.release(equatable); - - _ = try env.equals_object(equatable); - } else { - _ = try env.equals_nil(); - } - }, - - .cgt => { - const comparable = try env.expect_object(env.pop()); - - defer env.release(comparable); - - _ = try env.compare_greater(comparable); - }, - - .clt => { - const comparable = try env.expect_object(env.pop()); - - defer env.release(comparable); - - _ = try env.compare_less(comparable); - }, - - .cge => { - const comparable = try env.expect_object(env.pop()); - - defer env.release(comparable); - - _ = try env.compare_greater_equals(comparable); - }, - - .cle => { - const comparable = try env.expect_object(env.pop()); - - defer env.release(comparable); - - _ = try env.compare_less_equals(comparable); - }, - - .jf => |jf| { - if (env.pop()) |condition| { - defer env.release(condition); - - if (condition.is_false()) { - self.cursor = jf; - } - } else { - self.cursor = jf; - } - }, - - .jt => |jt| { - if (env.pop()) |condition| { - defer env.release(condition); - - if (condition.is_true()) { - self.cursor = jt; - } - } - }, - } - } - - return env.pop(); -} - -fn get_binding(self: *Self, env: *script.Runtime, index: usize) script.Error!?*script.Object { - if (index >= self.bindings.len) { - return env.raise(error.IllegalState, "invalid binding", .{}); - } - - return self.bindings[index]; -} - -fn get_constant(self: *const Self, env: *script.Runtime, index: usize) script.Error!*script.Object { - if (index >= self.constants.values.len) { - return env.raise(error.IllegalState, "invalid constant", .{}); - } - - return self.constants.values[index]; -} - -pub fn init(env: *script.Runtime, name: []const u8, environment: *const tree.Environment, externals: []const External) script.Error!Self { - const name_symbol = (try env.new_symbol(name)).pop().?; - - errdefer env.release(name_symbol); - - const externals_table = (try env.new_table()).pop().?; - - errdefer env.release(externals_table); - - for (0 .. externals.len) |i| { - const external_name, const external_object = externals[i]; - - try (try env.new_symbol(external_name)).index_set(externals_table, external_object); - } - - var chunk = Self{ - .externals = externals_table, - .name = name_symbol, - .opcodes = .{.allocator = env.allocator}, - .constants = .{.allocator = env.allocator}, - .lines = .{.allocator = env.allocator}, - .bindings = &.{}, - .arity = environment.argument_count, - .cursor = 0, - }; - - var compiler = Compiler{ - .chunk = &chunk, - .env = env, - }; - - try compiler.compile_environment(environment); - - return chunk; -} - -pub const typeinfo = script.Typeinfo{ - .name = "lambda", - .destruct = typeinfo_destruct, - .call = typeinfo_call, - .to_string = typeinfo_to_string, -}; - -fn typeinfo_call(context: script.Typeinfo.CallContext) script.Error!?*script.Object { - return @as(*Self, @ptrCast(@alignCast(context.userdata))).execute(context.env); -} - -fn typeinfo_destruct(context: script.Typeinfo.DestructContext) void { - @as(*Self, @ptrCast(@alignCast(context.userdata))).deinit(context.env); -} - -fn typeinfo_to_string(context: script.Typeinfo.ToStringContext) script.Error!*script.Object { - return (try (try context.env.push(@as(*Self, @ptrCast(@alignCast(context.userdata))).name)).to_string()).pop().?; -} - -pub fn write(self: *Self, line: tokens.Line, opcode: Opcode) std.mem.Allocator.Error!void { - try self.opcodes.push(opcode); - try self.lines.push(line); -} diff --git a/src/coral/script/Table.zig b/src/coral/script/Table.zig deleted file mode 100644 index a93a0a5..0000000 --- a/src/coral/script/Table.zig +++ /dev/null @@ -1,135 +0,0 @@ -const coral = @import("coral"); - -const script = @import("../script.zig"); - -associative: coral.map.Table(*script.Object, *script.Object, struct { - pub const hash = script.Object.hash; - - pub const equals = script.Object.equals; -}), - -contiguous: coral.Stack(?*script.Object), - -const Self = @This(); - -pub fn deinit(self: *Self, env: *script.Runtime) void { - { - var entries = self.associative.entries(); - - while (entries.next()) |entry| { - env.release(entry.key); - env.release(entry.value); - } - } - - self.associative.deinit(); - - while (self.contiguous.pop()) |value| { - if (value.*) |ref| { - env.release(ref); - } - } - - self.contiguous.deinit(); -} - -pub fn init(env: *script.Runtime) Self { - return .{ - .associative = .{ - .allocator = env.allocator, - .traits = .{}, - }, - - .contiguous = .{.allocator = env.allocator}, - }; -} - -pub const typeinfo = script.Typeinfo{ - .name = "table", - .destruct = typeinfo_destruct, - .get = typeinfo_get, - .set = typeinfo_set, - .count = typeinfo_count, -}; - -fn typeinfo_count(context: script.Typeinfo.CountContext) script.Error!script.Fixed { - const table = @as(*Self, @ptrCast(@alignCast(context.userdata))); - - return @intCast(table.associative.len + table.contiguous.values.len); -} - -fn typeinfo_destruct(context: script.Typeinfo.DestructContext) void { - @as(*Self, @ptrCast(@alignCast(context.userdata))).deinit(context.env); -} - -fn typeinfo_get(context: script.Typeinfo.GetContext) script.Error!?*script.Object { - const table = @as(*Self, @ptrCast(@alignCast(context.userdata))); - const index = (try context.push_index()).pop().?; - - defer context.env.release(index); - - if (index.is_fixed()) |fixed| { - if (fixed < 0) { - // TODO: Negative indexing. - unreachable; - } - - if (fixed < table.contiguous.values.len) { - return table.contiguous.values[@intCast(fixed)]; - } - } - - if (table.associative.lookup(index)) |value| { - return value; - } - - return null; -} - -fn typeinfo_set(context: script.Typeinfo.SetContext) script.Error!void { - const table = @as(*Self, @ptrCast(@alignCast(context.userdata))); - const index = (try context.push_index()).pop().?; - - errdefer context.env.release(index); - - if (index.is_fixed()) |fixed| { - if (fixed < 0) { - // TODO: Negative indexing. - unreachable; - } - - if (fixed < table.contiguous.values.len) { - const maybe_replacing = &table.contiguous.values[@intCast(fixed)]; - - if (maybe_replacing.*) |replacing| { - context.env.release(replacing); - } - - if ((try context.push_value()).pop()) |value| { - errdefer context.env.release(value); - - maybe_replacing.* = value; - } else { - maybe_replacing.* = null; - } - - return; - } - } - - const value = (try context.push_value()).pop() orelse { - if (table.associative.remove(index)) |removed| { - context.env.release(removed.key); - context.env.release(removed.value); - } - - return; - }; - - errdefer context.env.release(value); - - if (try table.associative.replace(index, value)) |replaced| { - context.env.release(replaced.key); - context.env.release(replaced.value); - } -} diff --git a/src/coral/script/tokens.zig b/src/coral/script/tokens.zig deleted file mode 100644 index b0ed5d4..0000000 --- a/src/coral/script/tokens.zig +++ /dev/null @@ -1,535 +0,0 @@ -const coral = @import("coral"); - -const std = @import("std"); - -pub const Line = struct { - number: u32, -}; - -pub const Token = union(enum) { - end, - unknown: u8, - newline, - identifier: []const u8, - builtin: []const u8, - - symbol_plus, - symbol_minus, - symbol_asterisk, - symbol_forward_slash, - symbol_paren_left, - symbol_paren_right, - symbol_bang, - symbol_comma, - symbol_at, - symbol_brace_left, - symbol_brace_right, - symbol_bracket_left, - symbol_bracket_right, - symbol_period, - symbol_colon, - symbol_less_than, - symbol_less_equals, - symbol_greater_than, - symbol_greater_equals, - symbol_equals, - symbol_double_equals, - - number: []const u8, - string: []const u8, - template_string: []const u8, - - keyword_nil, - keyword_false, - keyword_true, - keyword_return, - keyword_self, - keyword_const, - keyword_if, - keyword_do, - keyword_end, - keyword_while, - keyword_else, - keyword_elif, - keyword_var, - keyword_vec2, - keyword_vec3, - keyword_let, - keyword_lambda, - - pub fn text(self: Token) []const u8 { - return switch (self) { - .end => "end", - .unknown => |unknown| @as([*]const u8, @ptrCast(&unknown))[0 .. 1], - .newline => "newline", - - .identifier => |identifier| identifier, - .builtin => |identifier| identifier, - - .symbol_plus => "+", - .symbol_minus => "-", - .symbol_asterisk => "*", - .symbol_forward_slash => "/", - .symbol_paren_left => "(", - .symbol_paren_right => ")", - .symbol_bang => "!", - .symbol_comma => ",", - .symbol_at => "@", - .symbol_brace_left => "{", - .symbol_brace_right => "}", - .symbol_bracket_left => "[", - .symbol_bracket_right => "]", - .symbol_period => ".", - .symbol_colon => ":", - .symbol_less_than => "<", - .symbol_less_equals => "<=", - .symbol_greater_than => ">", - .symbol_greater_equals => ">=", - .symbol_equals => "=", - .symbol_double_equals => "==", - - .number => |literal| literal, - .string => |literal| literal, - .template_string => |literal| literal, - - .keyword_const => "const", - .keyword_nil => "nil", - .keyword_false => "false", - .keyword_true => "true", - .keyword_return => "return", - .keyword_self => "self", - .keyword_if => "if", - .keyword_do => "do", - .keyword_end => "end", - .keyword_while => "while", - .keyword_elif => "elif", - .keyword_else => "else", - .keyword_var => "var", - .keyword_vec2 => "vec2", - .keyword_vec3 => "vec3", - .keyword_let => "let", - .keyword_lambda => "lambda", - }; - } -}; - -pub const Stream = struct { - source: []const u8, - line: Line = .{.number = 1}, - token: Token = .newline, - - pub fn skip_newlines(self: *Stream) void { - self.step(); - - while (self.token == .newline) { - self.step(); - } - } - - pub fn step(self: *Stream) void { - var cursor = @as(usize, 0); - - defer self.source = self.source[cursor ..]; - - while (cursor < self.source.len) { - switch (self.source[cursor]) { - '#' => { - cursor += 1; - - while (cursor < self.source.len and self.source[cursor] != '\n') { - cursor += 1; - } - }, - - ' ', '\t' => cursor += 1, - - '\n' => { - cursor += 1; - self.token = .newline; - self.line.number += 1; - - return; - }, - - '0' ... '9' => { - const begin = cursor; - - cursor += 1; - - while (cursor < self.source.len) switch (self.source[cursor]) { - '0' ... '9' => cursor += 1, - - '.' => { - cursor += 1; - - while (cursor < self.source.len) switch (self.source[cursor]) { - '0' ... '9' => cursor += 1, - else => break, - }; - - self.token = .{.number = self.source[begin .. cursor]}; - - return; - }, - - else => break, - }; - - self.token = .{.number = self.source[begin .. cursor]}; - - return; - }, - - 'A' ... 'Z', 'a' ... 'z', '_' => { - const begin = cursor; - - cursor += 1; - - while (cursor < self.source.len) switch (self.source[cursor]) { - '0'...'9', 'A'...'Z', 'a'...'z', '_' => cursor += 1, - else => break, - }; - - const identifier = self.source[begin .. cursor]; - - std.debug.assert(identifier.len != 0); - - switch (identifier[0]) { - 'c' => { - if (coral.are_equal(identifier[1 ..], "onst")) { - self.token = .keyword_const; - - return; - } - }, - - 'd' => { - if (coral.are_equal(identifier[1 ..], "o")) { - self.token = .keyword_do; - - return; - } - }, - - 'e' => { - if (coral.are_equal(identifier[1 ..], "lse")) { - self.token = .keyword_else; - - return; - } - - if (coral.are_equal(identifier[1 ..], "lif")) { - self.token = .keyword_elif; - - return; - } - - if (coral.are_equal(identifier[1 ..], "nd")) { - self.token = .keyword_end; - - return; - } - }, - - 'f' => { - if (coral.are_equal(identifier[1 ..], "alse")) { - self.token = .keyword_false; - - return; - } - }, - - 'i' => { - if (coral.are_equal(identifier[1 ..], "f")) { - self.token = .keyword_if; - - return; - } - }, - - 'l' => { - if (coral.are_equal(identifier[1 ..], "ambda")) { - self.token = .keyword_lambda; - - return; - } - - if (coral.are_equal(identifier[1 ..], "et")) { - self.token = .keyword_let; - - return; - } - }, - - 'n' => { - if (coral.are_equal(identifier[1 ..], "il")) { - self.token = .keyword_nil; - - return; - } - }, - - 'r' => { - if (coral.are_equal(identifier[1 ..], "eturn")) { - self.token = .keyword_return; - - return; - } - }, - - 's' => { - if (coral.are_equal(identifier[1 ..], "elf")) { - self.token = .keyword_self; - - return; - } - }, - - 't' => { - if (coral.are_equal(identifier[1 ..], "rue")) { - self.token = .keyword_true; - - return; - } - }, - - 'v' => { - const rest = identifier[1 ..]; - - if (coral.are_equal(rest, "ar")) { - self.token = .keyword_var; - - return; - } - - if (coral.are_equal(rest, "vec2")) { - self.token = .keyword_vec2; - - return; - } - - if (coral.are_equal(rest, "vec3")) { - self.token = .keyword_vec3; - - return; - } - }, - - 'w' => { - if (coral.are_equal(identifier[1 ..], "hile")) { - self.token = .keyword_while; - - return; - } - }, - - else => {}, - } - - self.token = .{.identifier = identifier}; - - return; - }, - - '`' => { - cursor += 1; - - const begin = cursor; - - while (cursor < self.source.len) switch (self.source[cursor]) { - '`' => break, - else => cursor += 1, - }; - - self.token = .{.template_string = self.source[begin .. cursor]}; - cursor += 1; - - return; - }, - - '"' => { - cursor += 1; - - const begin = cursor; - - while (cursor < self.source.len) switch (self.source[cursor]) { - '"' => break, - else => cursor += 1, - }; - - self.token = .{.string = self.source[begin .. cursor]}; - cursor += 1; - - return; - }, - - '{' => { - self.token = .symbol_brace_left; - cursor += 1; - - return; - }, - - '}' => { - self.token = .symbol_brace_right; - cursor += 1; - - return; - }, - - '[' => { - self.token = .symbol_bracket_left; - cursor += 1; - - return; - }, - - ']' => { - self.token = .symbol_bracket_right; - cursor += 1; - - return; - }, - - ',' => { - self.token = .symbol_comma; - cursor += 1; - - return; - }, - - '!' => { - self.token = .symbol_bang; - cursor += 1; - - return; - }, - - ')' => { - self.token = .symbol_paren_right; - cursor += 1; - - return; - }, - - '(' => { - self.token = .symbol_paren_left; - cursor += 1; - - return; - }, - - '/' => { - self.token = .symbol_forward_slash; - cursor += 1; - - return; - }, - - '*' => { - self.token = .symbol_asterisk; - cursor += 1; - - return; - }, - - '-' => { - self.token = .symbol_minus; - cursor += 1; - - return; - }, - - '+' => { - self.token = .symbol_plus; - cursor += 1; - - return; - }, - - ':' => { - self.token = .symbol_colon; - cursor += 1; - - return; - }, - - '=' => { - cursor += 1; - - if (cursor < self.source.len) { - switch (self.source[cursor]) { - '=' => { - cursor += 1; - self.token = .symbol_double_equals; - - return; - }, - - else => {}, - } - } - - self.token = .symbol_equals; - - return; - }, - - '<' => { - cursor += 1; - - if (cursor < self.source.len and (self.source[cursor] == '=')) { - cursor += 1; - self.token = .symbol_less_equals; - - return; - } - - self.token = .symbol_less_than; - - return; - }, - - '>' => { - cursor += 1; - - if (cursor < self.source.len and (self.source[cursor] == '=')) { - cursor += 1; - self.token = .symbol_greater_equals; - - return; - } - - self.token = .symbol_greater_than; - - return; - }, - - '.' => { - self.token = .symbol_period; - cursor += 1; - - return; - }, - - '@' => { - self.token = .symbol_at; - cursor += 1; - - return; - }, - - else => { - self.token = .{.unknown = self.source[cursor]}; - cursor += 1; - - return; - }, - } - } - - self.token = .end; - - return; - } -}; diff --git a/src/coral/script/tree.zig b/src/coral/script/tree.zig deleted file mode 100644 index bc77118..0000000 --- a/src/coral/script/tree.zig +++ /dev/null @@ -1,268 +0,0 @@ -pub const Expr = @import("./tree/Expr.zig"); - -pub const Stmt = @import("./tree/Stmt.zig"); - -const coral = @import("coral"); - -const std = @import("std"); - -const script = @import("../script.zig"); - -const tokens = @import("./tokens.zig"); - -pub const Declaration = struct { - identifier: []const coral.Byte, - - is: packed struct { - readonly: bool = false, - captured: bool = false, - } = .{}, -}; - -pub const Environment = struct { - captures: [capture_max]Capture = [_]Capture{.{.declaration_index = 0}} ** capture_max, - capture_count: u8 = 0, - declarations: [declaration_max]Declaration = [_]Declaration{.{.identifier = ""}} ** declaration_max, - declaration_count: u8 = 0, - argument_count: u8 = 0, - statement: ?*const Stmt = null, - enclosing: ?*Environment = null, - - pub const Capture = union (enum) { - declaration_index: u8, - capture_index: u8, - }; - - pub const DeclareError = std.mem.Allocator.Error || error { - DeclarationExists, - }; - - const capture_max = std.math.maxInt(u8); - - const declaration_max = std.math.maxInt(u8); - - pub fn create_enclosed(self: *Environment, root: *Root) std.mem.Allocator.Error!*Environment { - const environment = try root.arena.allocator().create(Environment); - - environment.* = .{.enclosing = self}; - - return environment; - } - - fn declare(self: *Environment, declaration: Declaration) DeclareError!*const Declaration { - if (self.declaration_count == self.declarations.len) { - return error.OutOfMemory; - } - - { - var environment = self; - - while (true) { - var remaining_count = environment.declaration_count; - - while (remaining_count != 0) { - remaining_count -= 1; - - if (coral.are_equal(environment.declarations[remaining_count].identifier, declaration.identifier)) { - return error.DeclarationExists; - } - } - - environment = environment.enclosing orelse break; - } - } - - const declaration_slot = &self.declarations[self.declaration_count]; - - declaration_slot.* = declaration; - self.declaration_count += 1; - - return declaration_slot; - } - - pub fn declare_argument(self: *Environment, identifier: []const u8) DeclareError!*const Declaration { - std.debug.assert(self.declaration_count <= self.argument_count); - - defer self.argument_count += 1; - - return self.declare(.{ - .identifier = identifier, - .is = .{.readonly = true}, - }); - } - - pub fn declare_constant(self: *Environment, identifier: []const u8) DeclareError!*const Declaration { - return self.declare(.{ - .identifier = identifier, - .is = .{.readonly = true}, - }); - } - - pub fn declare_variable(self: *Environment, identifier: []const u8) DeclareError!*const Declaration { - return self.declare(.{.identifier = identifier}); - } - - pub fn resolve_declaration(self: *Environment, identifier: []const u8) std.mem.Allocator.Error!?*const Declaration { - var environment = self; - var ancestry = @as(u32, 0); - - while (true) : (ancestry += 1) { - var remaining_count = environment.declaration_count; - - while (remaining_count != 0) { - remaining_count -= 1; - - const declaration = &environment.declarations[remaining_count]; - - if (coral.are_equal(declaration.identifier, identifier)) { - if (ancestry != 0) { - declaration.is.captured = true; - environment = self; - ancestry -= 1; - - while (ancestry != 0) : (ancestry -= 1) { - if (environment.capture_count == environment.captures.len) { - return error.OutOfMemory; - } - - environment.captures[environment.capture_count] = .{ - .capture_index = environment.enclosing.?.capture_count - }; - - environment.capture_count += 1; - environment = environment.enclosing.?; - } - - environment.captures[environment.capture_count] = .{.declaration_index = remaining_count}; - environment.capture_count += 1; - } - - return declaration; - } - } - - environment = environment.enclosing orelse return null; - } - } - - pub fn get_captures(self: *const Environment) []const Capture { - return self.captures[0 .. self.capture_count]; - } - - pub fn get_declarations(self: *const Environment) []const Declaration { - return self.declarations[0 .. self.declaration_count]; - } -}; - -pub fn NodeChain(comptime Value: type) type { - return struct { - head: ?*Value = null, - tail: ?*Value = null, - - pub const Nodes = struct { - current: ?*const Value, - - pub fn next(self: *Nodes) ?*const Value { - const current = self.current orelse return null; - - defer self.current = current.next; - - return current; - } - }; - - const Self = @This(); - - pub fn append(self: *Self, value: *Value) void { - if (self.tail) |node| { - node.next = value; - self.tail = value; - } else { - self.tail = value; - self.head = value; - } - } - - pub fn nodes(self: *const Self) Nodes { - return .{.current = self.head}; - } - }; -} - -pub const ParseError = std.mem.Allocator.Error || error { - BadSyntax, -}; - -pub const Root = struct { - arena: std.heap.ArenaAllocator, - environment: Environment, - error_messages: MessageList, - - const MessageList = coral.Stack([]coral.Byte); - - pub fn report_error(self: *Root, line: tokens.Line, comptime format: []const u8, args: anytype) ParseError { - const allocator = self.arena.allocator(); - const message = try coral.utf8.alloc_formatted(allocator, format, args); - - defer allocator.free(message); - - try self.error_messages.push(try coral.utf8.alloc_formatted(allocator, "{line_number}: {message}", .{ - .message = message, - .line_number = line.number, - })); - - return error.BadSyntax; - } - - pub fn report_declare_error(self: *Root, line: tokens.Line, identifier: []const u8, @"error": Environment.DeclareError) ParseError { - return switch (@"error") { - error.OutOfMemory => error.OutOfMemory, - - error.DeclarationExists => self.report_error(line, "declaration `{identifier}` already exists", .{ - .identifier = identifier, - }), - }; - } - - pub fn create_node(self: *Root, node: anytype) std.mem.Allocator.Error!*@TypeOf(node) { - const copy = try self.arena.allocator().create(@TypeOf(node)); - - copy.* = node; - - return copy; - } - - pub fn create_string(self: *Root, comptime format: []const u8, args: anytype) std.mem.Allocator.Error![]const u8 { - return coral.utf8.alloc_formatted(self.arena.allocator(), format, args); - } - - pub fn deinit(self: *Root) void { - self.error_messages.deinit(); - self.arena.deinit(); - } - - pub fn init(allocator: std.mem.Allocator) std.mem.Allocator.Error!Root { - return .{ - .arena = std.heap.ArenaAllocator.init(allocator), - .error_messages = .{.allocator = allocator}, - .environment = .{}, - }; - } - - pub fn parse(self: *Root, stream: *tokens.Stream) ParseError!void { - stream.skip_newlines(); - - const first_statement = try Stmt.parse(self, stream, &self.environment); - var current_statement = first_statement; - - while (stream.token != .end) { - const next_statement = try Stmt.parse(self, stream, &self.environment); - - current_statement.next = next_statement; - current_statement = next_statement; - } - - self.environment.statement = first_statement; - } -}; - diff --git a/src/coral/script/tree/Expr.zig b/src/coral/script/tree/Expr.zig deleted file mode 100644 index 9f5fcb8..0000000 --- a/src/coral/script/tree/Expr.zig +++ /dev/null @@ -1,906 +0,0 @@ -const Stmt = @import("./Stmt.zig"); - -const coral = @import("coral"); - -const tokens = @import("../tokens.zig"); - -const tree = @import("../tree.zig"); - -const std = @import("std"); - -next: ?*const Self = null, -line: tokens.Line, -kind: Kind, - -pub const BinaryOp = struct { - rhs_operand: *Self, - lhs_operand: *Self, - operation: Operation, - - pub const Operation = enum { - addition, - subtraction, - multiplication, - divsion, - equals_comparison, - greater_than_comparison, - greater_equals_comparison, - less_than_comparison, - less_equals_comparison, - }; - - fn parser(comptime parse_next: Parser, comptime operations: []const BinaryOp.Operation) Parser { - const BinaryOpParser = struct { - fn parse(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Environment) tree.ParseError!*Self { - var expression = try parse_next(root, stream, environment); - - inline for (operations) |operation| { - const token = comptime @as(tokens.Token, switch (operation) { - .addition => .symbol_plus, - .subtraction => .symbol_minus, - .multiplication => .symbol_asterisk, - .divsion => .symbol_forward_slash, - .equals_comparison => .symbol_double_equals, - .greater_than_comparison => .symbol_greater_than, - .greater_equals_comparison => .symbol_greater_equals, - .less_than_comparison => .symbol_less_than, - .less_equals_comparison => .symbol_less_equals, - }); - - if (stream.token == std.meta.activeTag(token)) { - stream.step(); - - if (stream.token == .end) { - return root.report_error(stream.line, "expected other half of expression after `" ++ comptime token.text() ++ "`", .{}); - } - - // TODO: Remove once Zig has fixed struct self-reassignment. - const unnecessary_temp = expression; - - expression = try root.create_node(Self{ - .line = stream.line, - - .kind = .{ - .binary_op = .{ - .rhs_operand = try parse_next(root, stream, environment), - .operation = operation, - .lhs_operand = unnecessary_temp, - }, - }, - }); - } - } - - return expression; - } - }; - - return BinaryOpParser.parse; - } -}; - -pub const DeclarationGet = struct { - declaration: *const tree.Declaration, -}; - -pub const DeclarationSet = struct { - declaration: *const tree.Declaration, - assign: *const Self, -}; - -pub const FieldGet = struct { - identifier: []const coral.Byte, - object: *const Self, -}; - -pub const FieldSet = struct { - identifier: []const coral.Byte, - object: *const Self, - assign: *const Self, -}; - -pub const Invoke = struct { - argument: ?*const Self, - object: *const Self, -}; - -const Kind = union (enum) { - nil_literal, - true_literal, - false_literal, - number_literal: []const u8, - string_literal: []const u8, - string_template, - symbol_literal: []const u8, - vector2: Vector2, - vector3: Vector3, - table: tree.NodeChain(TableEntry), - group: *Self, - lambda_construct: LambdaConstruct, - declaration_get: DeclarationGet, - declaration_set: DeclarationSet, - field_get: FieldGet, - field_set: FieldSet, - external_get: ExternalGet, - subscript_get: SubscriptGet, - subscript_set: SubscriptSet, - binary_op: BinaryOp, - unary_op: UnaryOp, - invoke: Invoke, -}; - -pub const LambdaConstruct = struct { - environment: *const tree.Environment, -}; - -const Parser = fn (root: *tree.Root, stream: *tokens.Stream, environment: *tree.Environment) tree.ParseError!*Self; - -const ExternalGet = struct { - name: []const u8, -}; - -const Self = @This(); - -pub const SubscriptGet = struct { - index: *const Self, - object: *const Self, -}; - -pub const SubscriptSet = struct { - index: *const Self, - object: *const Self, - assign: *const Self, -}; - -pub const TableEntry = struct { - next: ?*const TableEntry = null, - key: *const Self, - value: *const Self, -}; - -const TemplateToken = union (enum) { - invalid: []const coral.Byte, - literal: []const coral.Byte, - expression: []const coral.Byte, - - fn extract(source: *[]const coral.Byte) ?TemplateToken { - var cursor = @as(usize, 0); - - defer source.* = source.*[cursor ..]; - - while (cursor < source.len) { - switch (source.*[cursor]) { - '{' => { - cursor += 1; - - while (true) : (cursor += 1) { - if (cursor == source.len) { - return .{.invalid = source.*[0 .. cursor]}; - } - - if (source.*[cursor] == '}') { - const token = TemplateToken{.expression = source.*[1 .. cursor]}; - - cursor += 1; - - return token; - } - } - }, - - else => { - cursor += 1; - - while (true) : (cursor += 1) { - if (cursor == source.len) { - return .{.literal = source.*[0 .. cursor]}; - } - - if (source.*[cursor] == '{') { - const cursor_next = cursor + 1; - - if (cursor_next == source.len) { - return .{.invalid = source.*[0 .. cursor]}; - } - - if (source.*[cursor_next] == '{') { - cursor = cursor_next; - - return .{.literal = source.*[0 .. cursor]}; - } - - return .{.literal = source.*[0 .. cursor]}; - } - } - } - } - } - - return null; - } -}; - -pub const UnaryOp = struct { - operand: *Self, - operation: Operation, - - pub const Operation = enum { - numeric_negation, - boolean_negation, - }; -}; - -pub const Vector2 = struct { - x: *const Self, - y: *const Self, -}; - -pub const Vector3 = struct { - x: *const Self, - y: *const Self, - z: *const Self, -}; - -pub fn parse(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Environment) tree.ParseError!*Self { - const expression = try parse_additive(root, stream, environment); - - if (stream.token == .symbol_equals) { - stream.skip_newlines(); - - if (stream.token == .end) { - return root.report_error(stream.line, "expected assignment after `=`", .{}); - } - - return root.create_node(Self{ - .line = stream.line, - - .kind = switch (expression.kind) { - .declaration_get => |declaration_get| convert: { - if (declaration_get.declaration.is.readonly) { - return root.report_error(stream.line, "readonly declarations cannot be re-assigned", .{}); - } - - break: convert .{ - .declaration_set = .{ - .assign = try parse(root, stream, environment), - .declaration = declaration_get.declaration, - }, - }; - }, - - .field_get => |field_get| .{ - .field_set = .{ - .assign = try parse(root, stream, environment), - .object = field_get.object, - .identifier = field_get.identifier, - }, - }, - - .subscript_get => |subscript_get| .{ - .subscript_set = .{ - .assign = try parse(root, stream, environment), - .object = subscript_get.object, - .index = subscript_get.index, - }, - }, - - else => return root.report_error(stream.line, "expected local or field on left-hand side of expression", .{}), - }, - }); - } - - return expression; -} - -const parse_additive = BinaryOp.parser(parse_equality, &.{ - .addition, - .subtraction, -}); - -const parse_comparison = BinaryOp.parser(parse_term, &.{ - .greater_than_comparison, - .greater_equals_comparison, - .less_than_comparison, - .less_equals_comparison -}); - -const parse_equality = BinaryOp.parser(parse_comparison, &.{ - .equals_comparison, -}); - -fn parse_factor(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Environment) tree.ParseError!*Self { - var expression = try parse_operand(root, stream, environment); - - while (true) { - switch (stream.token) { - .symbol_period => { - stream.skip_newlines(); - - // TODO: Remove when Zig fixes miscompilation with in-place struct re-assignment. - const unnecessary_temp = expression; - - expression = try root.create_node(Self{ - .line = stream.line, - - .kind = .{ - .field_get = .{ - .identifier = switch (stream.token) { - .identifier => |field_identifier| field_identifier, - else => return root.report_error(stream.line, "expected identifier after `.`", .{}), - }, - - .object = unnecessary_temp, - }, - }, - }); - - stream.skip_newlines(); - }, - - .symbol_bracket_left => { - stream.skip_newlines(); - - // TODO: Remove when Zig fixes miscompilation with in-place struct re-assignment. - const unnecessary_temp = expression; - - expression = try root.create_node(Self{ - .line = stream.line, - - .kind = .{ - .subscript_get = .{ - .index = try parse(root, stream, environment), - .object = unnecessary_temp, - }, - }, - }); - - if (stream.token != .symbol_bracket_right) { - return root.report_error(stream.line, "expected closing `]` on subscript", .{}); - } - - stream.skip_newlines(); - }, - - .symbol_paren_left => { - const lines_stepped = stream.line; - - stream.skip_newlines(); - - var first_argument = @as(?*Self, null); - - if (stream.token != .symbol_paren_right) { - var argument = try parse(root, stream, environment); - - first_argument = argument; - - while (true) { - switch (stream.token) { - .symbol_comma => stream.skip_newlines(), - .symbol_paren_right => break, - else => return root.report_error(stream.line, "expected `,` or `)` after lambda argument", .{}), - } - - const next_argument = try parse(root, stream, environment); - - argument.next = next_argument; - argument = next_argument; - } - } - - stream.skip_newlines(); - - // TODO: Remove when Zig fixes miscompilation with in-place struct re-assignment. - const unnecessary_temp = expression; - - expression = try root.create_node(Self{ - .line = lines_stepped, - - .kind = .{ - .invoke = .{ - .argument = first_argument, - .object = unnecessary_temp, - }, - }, - }); - }, - - else => break, - } - } - - return expression; -} - -fn parse_operand(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Environment) tree.ParseError!*Self { - switch (stream.token) { - .symbol_paren_left => { - stream.skip_newlines(); - - const expression = try parse(root, stream, environment); - - if (stream.token != .symbol_paren_right) { - return root.report_error(stream.line, "expected a closing `)` after expression", .{}); - } - - stream.skip_newlines(); - - return root.create_node(Self{ - .line = stream.line, - .kind = .{.group = expression}, - }); - }, - - .keyword_nil => { - stream.skip_newlines(); - - return root.create_node(Self{ - .line = stream.line, - .kind = .nil_literal, - }); - }, - - .keyword_true => { - stream.skip_newlines(); - - return root.create_node(Self{ - .line = stream.line, - .kind = .true_literal, - }); - }, - - .keyword_false => { - stream.skip_newlines(); - - return root.create_node(Self{ - .line = stream.line, - .kind = .false_literal, - }); - }, - - .keyword_vec2 => { - stream.skip_newlines(); - - if (stream.token != .symbol_paren_left) { - return root.report_error(stream.line, "expected an opening `(` after `vec2`", .{}); - } - - stream.skip_newlines(); - - const x_expression = try parse(root, stream, environment); - - switch (stream.token) { - .symbol_paren_right => { - stream.skip_newlines(); - - return root.create_node(Self{ - .line = stream.line, - - .kind = .{ - .vector2 = .{ - .x = x_expression, - .y = x_expression, - }, - }, - }); - }, - - .symbol_comma => { - stream.skip_newlines(); - - const y_expression = try parse(root, stream, environment); - - stream.skip_newlines(); - - if (stream.token != .symbol_paren_right) { - return root.report_error(stream.line, "expected a closing `)` after `vec3`", .{}); - } - - return root.create_node(Self{ - .line = stream.line, - - .kind = .{ - .vector2 = .{ - .x = x_expression, - .y = y_expression, - }, - }, - }); - }, - - else => return root.report_error(stream.line, "expected a closing `)` after `vec3`", .{}), - } - }, - - .keyword_vec3 => { - stream.skip_newlines(); - - if (stream.token != .symbol_paren_left) { - return root.report_error(stream.line, "expected an opening `(` after `vec2`", .{}); - } - - stream.skip_newlines(); - - const x_expression = try parse(root, stream, environment); - - switch (stream.token) { - .symbol_paren_right => { - stream.skip_newlines(); - - return root.create_node(Self{ - .line = stream.line, - - .kind = .{ - .vector3 = .{ - .x = x_expression, - .y = x_expression, - .z = x_expression, - }, - }, - }); - }, - - .symbol_comma => { - stream.skip_newlines(); - - const y_expression = try parse(root, stream, environment); - - stream.skip_newlines(); - - const z_expression = try parse(root, stream, environment); - - stream.skip_newlines(); - - if (stream.token != .symbol_paren_right) { - return root.report_error(stream.line, "expected a closing `)` after `vec3`", .{}); - } - - return root.create_node(Self{ - .line = stream.line, - - .kind = .{ - .vector3 = .{ - .x = x_expression, - .y = y_expression, - .z = z_expression, - }, - }, - }); - }, - - else => return root.report_error(stream.line, "expected a closing `)` after `vec3`", .{}), - } - }, - - .number => |value| { - stream.skip_newlines(); - - return root.create_node(Self{ - .line = stream.line, - .kind = .{.number_literal = value}, - }); - }, - - .string => |value| { - stream.skip_newlines(); - - return root.create_node(Self{ - .line = stream.line, - .kind = .{.string_literal = value}, - }); - }, - - .template_string => |value| { - const line = stream.line; - - stream.skip_newlines(); - - return parse_template(root, value, line, environment); - }, - - .symbol_at => { - stream.step(); - - const identifier = switch (stream.token) { - .identifier => |identifier| identifier, - else => return root.report_error(stream.line, "expected identifier after `@`", .{}), - }; - - stream.skip_newlines(); - - return root.create_node(Self{ - .line = stream.line, - .kind = .{.external_get = .{.name = identifier}}, - }); - }, - - .symbol_period => { - stream.step(); - - const identifier = switch (stream.token) { - .identifier => |identifier| identifier, - else => return root.report_error(stream.line, "expected identifier after `.`", .{}), - }; - - stream.skip_newlines(); - - return root.create_node(Self{ - .line = stream.line, - .kind = .{.symbol_literal = identifier}, - }); - }, - - .identifier => |identifier| { - stream.skip_newlines(); - - return root.create_node(Self{ - .line = stream.line, - - .kind = .{ - .declaration_get = .{ - .declaration = (try environment.resolve_declaration(identifier)) orelse { - return root.report_error(stream.line, "undefined identifier `{identifier}`", .{ - .identifier = identifier, - }); - } - }, - }, - }); - }, - - .keyword_lambda => { - stream.skip_newlines(); - - if (stream.token != .symbol_paren_left) { - return root.report_error(stream.line, "expected `(` after opening lambda block", .{}); - } - - stream.skip_newlines(); - - var lambda_environment = try environment.create_enclosed(root); - - while (stream.token != .symbol_paren_right) { - const identifier = switch (stream.token) { - .identifier => |identifier| identifier, - else => return root.report_error(stream.line, "expected identifier", .{}), - }; - - _ = lambda_environment.declare_argument(identifier) catch |declare_error| { - return root.report_declare_error(stream.line, identifier, declare_error); - }; - - stream.skip_newlines(); - - switch (stream.token) { - .symbol_comma => stream.skip_newlines(), - .symbol_paren_right => break, - else => return root.report_error(stream.line, "expected `,` or `)` after identifier", .{}), - } - } - - stream.skip_newlines(); - - if (stream.token != .symbol_colon) { - return root.report_error(stream.line, "expected `:` after closing `)` of lambda identifiers", .{}); - } - - stream.skip_newlines(); - - if (stream.token != .keyword_end) { - const first_statement = try Stmt.parse(root, stream, lambda_environment); - var current_statement = first_statement; - - while (stream.token != .keyword_end) { - const next_statement = try Stmt.parse(root, stream, lambda_environment); - - current_statement.next = next_statement; - current_statement = next_statement; - } - - lambda_environment.statement = first_statement; - } - - stream.skip_newlines(); - - return root.create_node(Self{ - .line = stream.line, - .kind = .{.lambda_construct = .{.environment = lambda_environment}}, - }); - }, - - .symbol_brace_left => { - stream.skip_newlines(); - - return parse_table(root, stream, environment); - }, - - .symbol_minus => { - stream.skip_newlines(); - - return root.create_node(Self{ - .line = stream.line, - - .kind = .{ - .unary_op = .{ - .operand = try parse_factor(root, stream, environment), - .operation = .numeric_negation, - }, - }, - }); - }, - - .symbol_bang => { - stream.skip_newlines(); - - return root.create_node(Self{ - .line = stream.line, - - .kind = .{ - .unary_op = .{ - .operand = try parse_factor(root, stream, environment), - .operation = .boolean_negation, - }, - }, - }); - }, - - else => return root.report_error(stream.line, "unexpected token in expression", .{}), - } -} - -fn parse_table(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Environment) tree.ParseError!*Self { - var entries = tree.NodeChain(TableEntry){}; - var sequential_index = @as(usize, 0); - - while (true) { - switch (stream.token) { - .symbol_brace_right => { - stream.skip_newlines(); - - return root.create_node(Self{ - .line = stream.line, - .kind = .{.table = entries}, - }); - }, - - .symbol_bracket_left => { - stream.skip_newlines(); - - const key = try parse(root, stream, environment); - - if (stream.token != .symbol_bracket_right) { - return root.report_error(stream.line, "expected `]` after subscript index expression", .{}); - } - - stream.skip_newlines(); - - if (stream.token != .symbol_equals) { - return root.report_error(stream.line, "expected `=` after table expression key", .{}); - } - - stream.skip_newlines(); - - entries.append(try root.create_node(TableEntry{ - .value = try parse(root, stream, environment), - .key = key, - })); - }, - - .symbol_period => { - stream.step(); - - const field = switch (stream.token) { - .identifier => |identifier| identifier, - else => return root.report_error(stream.line, "invalid symbol literal", .{}), - }; - - stream.skip_newlines(); - - switch (stream.token) { - .symbol_comma => { - stream.skip_newlines(); - - entries.append(try root.create_node(TableEntry{ - .key = try root.create_node(Self{ - .line = stream.line, - .kind = .{.number_literal = try root.create_string("{i}", .{.i = sequential_index})}, - }), - - .value = try root.create_node(Self{ - .line = stream.line, - .kind = .{.symbol_literal = field}, - }), - })); - - sequential_index += 1; - }, - - .symbol_equals => { - stream.skip_newlines(); - - entries.append(try root.create_node(TableEntry{ - .value = try parse(root, stream, environment), - - .key = try root.create_node(Self{ - .line = stream.line, - .kind = .{.symbol_literal = field}, - }), - })); - }, - - else => return root.report_error(stream.line, "expected `,` or `=` after symbol", .{}), - } - }, - - else => { - entries.append(try root.create_node(TableEntry{ - .value = try parse(root, stream, environment), - - .key = try root.create_node(Self{ - .line = stream.line, - .kind = .{.number_literal = try root.create_string("{i}", .{.i = sequential_index})}, - }), - })); - - sequential_index += 1; - }, - } - - switch (stream.token) { - .symbol_brace_right => { - stream.skip_newlines(); - - return root.create_node(Self{ - .line = stream.line, - .kind = .{.table = entries}, - }); - }, - - .symbol_comma => stream.skip_newlines(), - else => return root.report_error(stream.line, "expected `,` or '}' after table key value pair", .{}), - } - } -} - -fn parse_template(root: *tree.Root, template: []const coral.Byte, line: tokens.Line, environment: *tree.Environment) tree.ParseError!*Self { - const expression_head = try root.create_node(Self{ - .line = line, - .kind = .string_template, - }); - - var expression_tail = expression_head; - var source = template; - - while (TemplateToken.extract(&source)) |token| { - const expression = try switch (token) { - .invalid => |invalid| root.report_error(line, "invalid template format: `{invalid}`", .{ - .invalid = invalid, - }), - - .literal => |literal| root.create_node(Self{ - .line = line, - .kind = .{.string_literal = literal}, - }), - - .expression => |expression| create: { - var stream = tokens.Stream{ - .source = expression, - .line = line, - }; - - stream.step(); - - break: create try parse(root, &stream, environment); - }, - }; - - expression_tail.next = expression; - expression_tail = expression; - } - - return expression_head; -} - -const parse_term = BinaryOp.parser(parse_factor, &.{ - .multiplication, - .divsion, -}); diff --git a/src/coral/script/tree/Stmt.zig b/src/coral/script/tree/Stmt.zig deleted file mode 100644 index d507400..0000000 --- a/src/coral/script/tree/Stmt.zig +++ /dev/null @@ -1,242 +0,0 @@ -const Expr = @import("./Expr.zig"); - -const coral = @import("coral"); - -const tokens = @import("../tokens.zig"); - -const tree = @import("../tree.zig"); - -next: ?*const Self = null, -line: tokens.Line, - -kind: union (enum) { - top_expression: *const Expr, - @"return": Return, - declare: Declare, - @"if": If, - @"while": While, -}, - -pub const Declare = struct { - declaration: *const tree.Declaration, - initial_expression: *const Expr, -}; - -pub const If = struct { - then_expression: *const Expr, - @"then": *const Self, - @"else": ?*const Self, -}; - -pub const Return = struct { - returned_expression: ?*const Expr, -}; - -const Self = @This(); - -pub const While = struct { - loop_expression: *const Expr, - loop: *const Self, -}; - -pub fn parse(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Environment) tree.ParseError!*Self { - switch (stream.token) { - .keyword_return => { - stream.step(); - - if (stream.token != .end and stream.token != .newline) { - return root.create_node(Self{ - .line = stream.line, - .kind = .{.@"return" = .{.returned_expression = try Expr.parse(root, stream, environment)}}, - }); - } - - if (stream.token != .end and stream.token != .newline) { - return root.report_error(stream.line, "expected end or newline after return statement", .{}); - } - - return root.create_node(Self{ - .line = stream.line, - .kind = .{.@"return" = .{.returned_expression = null}}, - }); - }, - - .keyword_while => { - defer stream.skip_newlines(); - - stream.step(); - - const condition_expression = try Expr.parse(root, stream, environment); - - if (stream.token != .symbol_colon) { - return root.report_error(stream.line, "expected `:` after `while` statement", .{}); - } - - stream.skip_newlines(); - - const first_statement = try parse(root, stream, environment); - - { - var current_statement = first_statement; - - while (stream.token != .keyword_end) { - const next_statement = try parse(root, stream, environment); - - current_statement.next = next_statement; - current_statement = next_statement; - } - } - - return root.create_node(Self{ - .line = stream.line, - - .kind = .{ - .@"while" = .{ - .loop = first_statement, - .loop_expression = condition_expression, - }, - }, - }); - }, - - .keyword_var, .keyword_let => { - const is_constant = stream.token == .keyword_let; - - stream.skip_newlines(); - - const identifier = switch (stream.token) { - .identifier => |identifier| identifier, - else => return root.report_error(stream.line, "expected identifier after declaration", .{}), - }; - - stream.skip_newlines(); - - if (stream.token != .symbol_equals) { - return root.report_error(stream.line, "expected `=` after declaration `{identifier}`", .{ - .identifier = identifier, - }); - } - - stream.skip_newlines(); - - return root.create_node(Self{ - .line = stream.line, - - .kind = .{ - .declare = .{ - .initial_expression = try Expr.parse(root, stream, environment), - - .declaration = declare: { - if (is_constant) { - break: declare environment.declare_constant(identifier) catch |declaration_error| { - return root.report_declare_error(stream.line, identifier, declaration_error); - }; - } - - break: declare environment.declare_variable(identifier) catch |declaration_error| { - return root.report_declare_error(stream.line, identifier, declaration_error); - }; - }, - }, - }, - }); - }, - - .keyword_if => return parse_branch(root, stream, environment), - - else => return root.create_node(Self{ - .line = stream.line, - .kind = .{.top_expression = try Expr.parse(root, stream, environment)}, - }), - } -} - -fn parse_branch(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Environment) tree.ParseError!*Self { - stream.step(); - - const expression = try Expr.parse(root, stream, environment); - - if (stream.token != .symbol_colon) { - return root.report_error(stream.line, "expected `:` after `{token}`", .{.token = stream.token.text()}); - } - - stream.skip_newlines(); - - const first_then_statement = try parse(root, stream, environment); - var current_then_statement = first_then_statement; - - while (true) { - switch (stream.token) { - .keyword_end => { - stream.skip_newlines(); - - return root.create_node(Self{ - .line = stream.line, - - .kind = .{ - .@"if" = .{ - .then_expression = expression, - .@"then" = first_then_statement, - .@"else" = null, - }, - }, - }); - }, - - .keyword_else => { - stream.step(); - - if (stream.token != .symbol_colon) { - return root.report_error(stream.line, "expected `:` after `if` statement condition", .{}); - } - - stream.skip_newlines(); - - const first_else_statement = try parse(root, stream, environment); - var current_else_statement = first_else_statement; - - while (stream.token != .keyword_end) { - const next_statement = try parse(root, stream, environment); - - current_else_statement.next = next_statement; - current_else_statement = next_statement; - } - - stream.skip_newlines(); - - return root.create_node(Self{ - .line = stream.line, - - .kind = .{ - .@"if" = .{ - .@"else" = first_else_statement, - .@"then" = first_then_statement, - .then_expression = expression, - }, - } - }); - }, - - .keyword_elif => { - return root.create_node(Self{ - .line = stream.line, - - .kind = .{ - .@"if" = .{ - .@"else" = try parse_branch(root, stream, environment), - .@"then" = first_then_statement, - .then_expression = expression, - }, - }, - }); - }, - - else => { - const next_statement = try parse(root, stream, environment); - - current_then_statement.next = next_statement; - current_then_statement = next_statement; - }, - } - } -} diff --git a/src/coral/slots.zig b/src/coral/slots.zig deleted file mode 100644 index 43adc7d..0000000 --- a/src/coral/slots.zig +++ /dev/null @@ -1,23 +0,0 @@ -const slices = @import("./slices.zig"); - -const std = @import("std"); - -pub fn Parallel(comptime Value: type) type { - const Slices = slices.Parallel(Value); - const alignment = @alignOf(Value); - - return struct { - allocator: std.mem.Allocator, - slices: slices.Parallel(Value) = .{}, - - const Self = @This(); - - pub fn len(self: Self) usize { - return self.slices.len; - } - - pub fn values(self: *Self, comptime field: Slices.Field) []align (alignment) Slices.Element(field) { - return self.slices.slice(field); - } - }; -} diff --git a/src/coral/World.zig b/src/flow/World.zig similarity index 54% rename from src/coral/World.zig rename to src/flow/World.zig index e7329ac..e0d3650 100644 --- a/src/coral/World.zig +++ b/src/flow/World.zig @@ -1,30 +1,21 @@ const builtin = @import("builtin"); -const heap = @import("./heap.zig"); +const coral = @import("coral"); -const map = @import("./map.zig"); - -const resource = @import("./resource.zig"); +const states = @import("./states.zig"); const std = @import("std"); -const stack = @import("./stack.zig"); - const system = @import("./system.zig"); thread_pool: ?*std.Thread.Pool = null, -thread_restricted_resources: [std.enums.values(ThreadRestriction).len]resource.Table, -event_systems: stack.Sequential(system.Schedule), +thread_restricted_resources: [std.enums.values(states.ThreadRestriction).len]states.Table, +event_systems: coral.stack.Sequential(system.Schedule), pub const Event = enum (usize) { _ }; const Self = @This(); -pub const ThreadRestriction = enum { - none, - main, -}; - pub fn create_event(self: *Self, label: []const u8) std.mem.Allocator.Error!Event { var systems = try system.Schedule.init(label); @@ -48,7 +39,7 @@ pub fn deinit(self: *Self) void { if (self.thread_pool) |thread_pool| { thread_pool.deinit(); - heap.allocator.destroy(thread_pool); + coral.heap.allocator.destroy(thread_pool); } self.event_systems.deinit(); @@ -56,25 +47,25 @@ pub fn deinit(self: *Self) void { self.* = undefined; } -pub fn get_resource(self: Self, thread_restriction: ThreadRestriction, comptime Resource: type) ?*Resource { - return @ptrCast(@alignCast(self.thread_restricted_resources[@intFromEnum(thread_restriction)].get(Resource))); +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 set_get_resource(self: *Self, thread_restriction: ThreadRestriction, value: anytype) std.mem.Allocator.Error!*@TypeOf(value) { - return self.thread_restricted_resources[@intFromEnum(thread_restriction)].set_get(value); +pub fn set_get_resource(self: *Self, value: anytype) std.mem.Allocator.Error!*@TypeOf(value) { + return self.thread_restricted_resources[@intFromEnum(states.thread_restriction(@TypeOf(value)))].set_get(value); } pub fn init(thread_count: u32) std.Thread.SpawnError!Self { var world = Self{ - .thread_restricted_resources = .{resource.Table.init(), resource.Table.init()}, - .event_systems = .{.allocator = heap.allocator}, + .thread_restricted_resources = .{states.Table.init(), states.Table.init()}, + .event_systems = .{.allocator = coral.heap.allocator}, }; if (thread_count != 0 and !builtin.single_threaded) { - const thread_pool = try heap.allocator.create(std.Thread.Pool); + const thread_pool = try coral.heap.allocator.create(std.Thread.Pool); try thread_pool.init(.{ - .allocator = heap.allocator, + .allocator = coral.heap.allocator, .n_jobs = thread_count, }); @@ -92,6 +83,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, thread_restriction: ThreadRestriction, value: anytype) std.mem.Allocator.Error!void { - try self.thread_restricted_resources[@intFromEnum(thread_restriction)].set(value); +pub fn set_resource(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/coral/dag.zig b/src/flow/dag.zig similarity index 93% rename from src/coral/dag.zig rename to src/flow/dag.zig index 5aafa3c..ea35bc0 100644 --- a/src/coral/dag.zig +++ b/src/flow/dag.zig @@ -1,6 +1,4 @@ -const stack = @import("./stack.zig"); - -const slices = @import("./slices.zig"); +const coral = @import("coral"); const std = @import("std"); @@ -9,9 +7,9 @@ pub fn Graph(comptime Payload: type) type { node_count: usize = 0, table: NodeTables, - const NodeTables = stack.Parallel(struct { + const NodeTables = coral.stack.Parallel(struct { payload: Payload, - edges: stack.Sequential(Node), + edges: coral.stack.Sequential(Node), is_occupied: bool = true, is_visited: bool = false, }); @@ -82,7 +80,7 @@ pub fn Graph(comptime Payload: type) type { return false; }; - if (slices.index_of(edges.values, 0, edge_node) == null) { + if (coral.slices.index_of(edges.values, 0, edge_node) == null) { try edges.push_grow(edge_node); } diff --git a/src/flow/flow.zig b/src/flow/flow.zig new file mode 100644 index 0000000..34b84b9 --- /dev/null +++ b/src/flow/flow.zig @@ -0,0 +1,273 @@ +const std = @import("std"); + +pub const states = @import("./states.zig"); + +pub const system = @import("./system.zig"); + +pub const World = @import("./World.zig"); + +pub fn Read(comptime Value: type) type { + return Shared(Value, .{ + .thread_restriction = states.thread_restriction(Value), + .read_only = true, + }); +} + +pub const ShareInfo = struct { + thread_restriction: states.ThreadRestriction, + read_only: bool, +}; + +pub fn Shared(comptime Value: type, comptime info: ShareInfo) type { + const value_info = @typeInfo(Value); + + const Qualified = switch (value_info) { + .Optional => @Type(.{ + .Optional = .{ + .child = .{ + .Pointer = .{ + .is_allowzero = false, + .sentinel = null, + .address_space = .generic, + .is_volatile = false, + .alignment = @alignOf(Value), + .size = .One, + .child = Value, + .is_const = info.read_only, + }, + }, + }, + }), + + else => @Type(.{ + .Pointer = .{ + .is_allowzero = false, + .sentinel = null, + .address_space = .generic, + .is_volatile = false, + .alignment = @alignOf(Value), + .size = .One, + .child = Value, + .is_const = info.read_only, + }, + }), + }; + + return struct { + res: Qualified, + + const Self = @This(); + + pub const State = struct { + res: Qualified, + }; + + pub fn bind(context: system.BindContext) std.mem.Allocator.Error!State { + 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)), + }; + + return .{ + .res = switch (value_info) { + .Optional => res, + + else => res orelse { + @panic(std.fmt.comptimePrint("attempt to use {s}{s} {s} that has not yet been set", .{ + thread_restriction_name, + if (info.read_only) "read-only" else "read-write", + @typeName(Value), + })); + }, + }, + }; + } + + pub fn init(state: *State) Self { + return .{ + .res = state.res, + }; + } + }; +} + +pub fn Write(comptime Value: type) type { + return Shared(Value, .{ + .thread_restriction = states.thread_restriction(Value), + .read_only = false, + }); +} + +fn parameter_type(comptime Value: type) *const system.Info.Parameter { + const has_state = @hasDecl(Value, "State"); + + 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); + + 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; + } + } + + fn init(argument: *anyopaque, state: ?*anyopaque) void { + const value_name = @typeName(Value); + + if (!@hasDecl(Value, "init")) { + @compileError("an `init` declaration on " ++ value_name ++ " is requied for parameter types"); + } + + const init_type = @typeInfo(@TypeOf(Value.init)); + + if (init_type != .Fn) { + @compileError("`init` declaration on " ++ value_name ++ " must be a fn"); + } + + if (init_type.Fn.return_type.? != Value) { + @compileError("`init` fn on " ++ value_name ++ " must return a " ++ value_name); + } + + 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(); + } + } + + fn unbind(allocator: std.mem.Allocator, state: ?*anyopaque) void { + if (@hasDecl(Value, "unbind")) { + if (has_state) { + const typed_state = @as(*Value.State, @ptrCast(@alignCast(state.?))); + + Value.unbind(typed_state); + allocator.destroy(typed_state); + } else { + Value.unbind(); + } + } + } + }; + + return comptime &.{ + .thread_restriction = if (@hasDecl(Value, "thread_restriction")) Value.thread_restriction else .none, + .init = parameters.init, + .bind = parameters.bind, + .unbind = parameters.unbind, + }; +} + +pub fn system_fn(comptime call: anytype) *const system.Info { + const Call = @TypeOf(call); + + const system_info = comptime generate: { + switch (@typeInfo(Call)) { + .Fn => |call_fn| { + if (call_fn.params.len > system.max_parameters) { + @compileError("number of parameters to `call` cannot be more than 16"); + } + + const systems = struct { + 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| { + parameter.init(call_arg, state); + } + + switch (@typeInfo(call_fn.return_type.?)) { + .Void => @call(.auto, call, call_args), + .ErrorUnion => try @call(.auto, call, call_args), + else => @compileError("number of parameters to `call` must return void or !void"), + } + } + }; + + var parameters = @as([system.max_parameters]*const system.Info.Parameter, undefined); + var thread_restriction = states.ThreadRestriction.none; + + for (0 .. call_fn.params.len) |index| { + const CallParam = call_fn.params[index].type.?; + const parameter = parameter_type(CallParam); + + if (parameter.thread_restriction != .none) { + if (thread_restriction != .none and thread_restriction != parameter.thread_restriction) { + @compileError("a system may not have conflicting thread restrictions"); + } + + thread_restriction = parameter.thread_restriction; + } + + parameters[index] = parameter; + } + + break: generate &.{ + .parameters = parameters, + .parameter_count = call_fn.params.len, + .execute = systems.run, + .thread_restriction = thread_restriction, + }; + }, + + else => @compileError("parameter `call` must be a function"), + } + }; + + return system_info; +} diff --git a/src/coral/resource.zig b/src/flow/states.zig similarity index 76% rename from src/coral/resource.zig rename to src/flow/states.zig index 617c38b..d4a0e2e 100644 --- a/src/coral/resource.zig +++ b/src/flow/states.zig @@ -1,12 +1,10 @@ +const coral = @import("coral"); + const std = @import("std"); -const heap = @import("./heap.zig"); - -const map = @import("./map.zig"); - pub const Table = struct { arena: std.heap.ArenaAllocator, - table: map.Hashed(TypeID, Entry, map.enum_traits(TypeID)), + table: coral.map.Hashed(TypeID, Entry, coral.map.enum_traits(TypeID)), const Entry = struct { ptr: *anyopaque, @@ -29,8 +27,8 @@ pub const Table = struct { pub fn init() Table { return .{ - .arena = std.heap.ArenaAllocator.init(heap.allocator), - .table = .{.allocator = heap.allocator}, + .arena = std.heap.ArenaAllocator.init(coral.heap.allocator), + .table = .{.allocator = coral.heap.allocator}, }; } @@ -61,6 +59,11 @@ pub const Table = struct { } }; +pub const ThreadRestriction = enum { + none, + main, +}; + pub const TypeID = enum (usize) { _ }; pub fn type_id(comptime T: type) TypeID { @@ -74,3 +77,11 @@ pub fn type_id(comptime T: type) TypeID { return @enumFromInt(@intFromPtr(&TypeHandle.byte)); } + +pub fn thread_restriction(comptime State: type) ThreadRestriction { + if (@hasDecl(State, "thread_restriction")) { + return State.thread_restriction; + } + + return .none; +} diff --git a/src/coral/system.zig b/src/flow/system.zig similarity index 83% rename from src/coral/system.zig rename to src/flow/system.zig index 2224690..25dce4d 100644 --- a/src/coral/system.zig +++ b/src/flow/system.zig @@ -1,14 +1,8 @@ +const coral = @import("coral"); + const dag = @import("./dag.zig"); -const heap = @import("./heap.zig"); - -const map = @import("./map.zig"); - -const resource = @import("./resource.zig"); - -const slices = @import("./slices.zig"); - -const stack = @import("./stack.zig"); +const states = @import("./states.zig"); const std = @import("std"); @@ -21,7 +15,7 @@ pub const BindContext = struct { pub const ResourceAccess = std.meta.Tag(Schedule.ResourceAccess); - pub fn accesses_resource(self: BindContext, access: ResourceAccess, id: resource.TypeID) bool { + pub fn accesses_resource(self: BindContext, access: ResourceAccess, id: states.TypeID) bool { const resource_accesses = &self.systems.graph.get_ptr(self.node).?.resource_accesses; for (resource_accesses.values) |resource_access| { @@ -43,12 +37,12 @@ pub const BindContext = struct { return false; } - pub fn register_read_write_resource_access(self: BindContext, thread_restriction: World.ThreadRestriction, comptime Resource: type) std.mem.Allocator.Error!?*Resource { - const value = self.world.get_resource(thread_restriction, Resource) orelse { + pub fn register_writable_resource_access(self: BindContext, comptime Resource: type) std.mem.Allocator.Error!?*Resource { + const value = self.world.get_resource(Resource) orelse { return null; }; - const id = resource.type_id(Resource); + const id = states.type_id(Resource); if (!self.accesses_resource(.read_write, id)) { try self.systems.graph.get_ptr(self.node).?.resource_accesses.push_grow(.{.read_write = id}); @@ -57,26 +51,26 @@ pub const BindContext = struct { const read_write_resource_nodes = lazily_create: { break: lazily_create self.systems.read_write_resource_id_nodes.get_ptr(id) orelse insert: { std.debug.assert(try self.systems.read_write_resource_id_nodes.emplace(id, .{ - .allocator = heap.allocator, + .allocator = coral.heap.allocator, })); break: insert self.systems.read_write_resource_id_nodes.get_ptr(id).?; }; }; - if (slices.index_of(read_write_resource_nodes.values, 0, self.node) == null) { + if (coral.slices.index_of(read_write_resource_nodes.values, 0, self.node) == null) { try read_write_resource_nodes.push_grow(self.node); } return value; } - pub fn register_read_only_resource_access(self: BindContext, thread_restriction: World.ThreadRestriction, comptime Resource: type) std.mem.Allocator.Error!?*const Resource { - const value = self.world.get_resource(thread_restriction, Resource) orelse { + 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 { return null; }; - const id = resource.type_id(Resource); + const id = states.type_id(Resource); if (!self.accesses_resource(.read_only, id)) { try self.systems.graph.get_ptr(self.node).?.resource_accesses.push_grow(.{.read_only = id}); @@ -85,14 +79,14 @@ pub const BindContext = struct { const read_only_resource_nodes = lazily_create: { break: lazily_create self.systems.read_only_resource_id_nodes.get_ptr(id) orelse insert: { std.debug.assert(try self.systems.read_only_resource_id_nodes.emplace(id, .{ - .allocator = heap.allocator, + .allocator = coral.heap.allocator, })); break: insert self.systems.read_only_resource_id_nodes.get_ptr(id).?; }; }; - if (slices.index_of(read_only_resource_nodes.values, 0, self.node) == null) { + if (coral.slices.index_of(read_only_resource_nodes.values, 0, self.node) == null) { try read_only_resource_nodes.push_grow(self.node); } @@ -104,10 +98,10 @@ pub const Info = struct { execute: *const fn ([]const *const Parameter, *const [max_parameters]?*anyopaque) anyerror!void, parameters: [max_parameters]*const Parameter = undefined, parameter_count: u4 = 0, - thread_restriction: World.ThreadRestriction = .none, + thread_restriction: states.ThreadRestriction = .none, pub const Parameter = struct { - thread_restriction: World.ThreadRestriction, + 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, @@ -128,7 +122,7 @@ pub const Schedule = struct { label: [:0]const u8, graph: Graph, arena: std.heap.ArenaAllocator, - system_id_nodes: map.Hashed(usize, NodeBundle, map.usize_traits), + system_id_nodes: coral.map.Hashed(usize, NodeBundle, coral.map.usize_traits), read_write_resource_id_nodes: ResourceNodeBundle, read_only_resource_id_nodes: ResourceNodeBundle, parallel_work_bundles: ParallelNodeBundles, @@ -149,19 +143,19 @@ pub const Schedule = struct { label: [:0]u8, dependencies: []Dependency, parameter_states: [max_parameters]?*anyopaque = [_]?*anyopaque{null} ** max_parameters, - resource_accesses: stack.Sequential(ResourceAccess), + resource_accesses: coral.stack.Sequential(ResourceAccess), }); - const NodeBundle = stack.Sequential(dag.Node); + const NodeBundle = coral.stack.Sequential(dag.Node); - const ParallelNodeBundles = stack.Sequential(NodeBundle); + const ParallelNodeBundles = coral.stack.Sequential(NodeBundle); const ResourceAccess = union (enum) { - read_only: resource.TypeID, - read_write: resource.TypeID, + read_only: states.TypeID, + read_write: states.TypeID, }; - const ResourceNodeBundle = map.Hashed(resource.TypeID, NodeBundle, map.enum_traits(resource.TypeID)); + const ResourceNodeBundle = coral.map.Hashed(states.TypeID, NodeBundle, coral.map.enum_traits(states.TypeID)); pub fn deinit(self: *Schedule) void { { @@ -198,8 +192,8 @@ pub const Schedule = struct { } system.resource_accesses.deinit(); - heap.allocator.free(system.dependencies); - heap.allocator.free(system.label); + coral.heap.allocator.free(system.dependencies); + coral.heap.allocator.free(system.label); } for (self.parallel_work_bundles.values) |*bundle| { @@ -304,7 +298,7 @@ pub const Schedule = struct { continue; } - try schedule.parallel_work_bundles.push_grow(.{.allocator = heap.allocator}); + try schedule.parallel_work_bundles.push_grow(.{.allocator = coral.heap.allocator}); const bundle = schedule.parallel_work_bundles.get_ptr().?; @@ -385,21 +379,21 @@ pub const Schedule = struct { } pub fn init(label: []const u8) std.mem.Allocator.Error!Schedule { - var arena = std.heap.ArenaAllocator.init(heap.allocator); + var arena = std.heap.ArenaAllocator.init(coral.heap.allocator); errdefer arena.deinit(); const duped_label = try arena.allocator().dupeZ(u8, label); return .{ - .graph = Graph.init(heap.allocator), + .graph = Graph.init(coral.heap.allocator), .label = duped_label, .arena = arena, - .system_id_nodes = .{.allocator = heap.allocator}, - .read_write_resource_id_nodes = .{.allocator = heap.allocator}, - .read_only_resource_id_nodes = .{.allocator = heap.allocator}, - .parallel_work_bundles = .{.allocator = heap.allocator}, - .blocking_work = .{.allocator = heap.allocator}, + .system_id_nodes = .{.allocator = coral.heap.allocator}, + .read_write_resource_id_nodes = .{.allocator = coral.heap.allocator}, + .read_only_resource_id_nodes = .{.allocator = coral.heap.allocator}, + .parallel_work_bundles = .{.allocator = coral.heap.allocator}, + .blocking_work = .{.allocator = coral.heap.allocator}, }; } @@ -432,7 +426,7 @@ pub const Schedule = struct { const dependencies = init: { const total_run_orders = order.run_after.len + order.run_before.len; - const dependencies = try heap.allocator.alloc(Dependency, total_run_orders); + const dependencies = try coral.heap.allocator.alloc(Dependency, total_run_orders); var dependencies_written = @as(usize, 0); for (order.run_after) |after_system| { @@ -456,17 +450,17 @@ pub const Schedule = struct { break: init dependencies; }; - errdefer heap.allocator.free(dependencies); + errdefer coral.heap.allocator.free(dependencies); - const label = try heap.allocator.dupeZ(u8, if (order.label.len == 0) "anonymous system" else order.label); + const label = try coral.heap.allocator.dupeZ(u8, if (order.label.len == 0) "anonymous system" else order.label); - errdefer heap.allocator.free(label); + errdefer coral.heap.allocator.free(label); const node = try self.graph.append(.{ .info = info, .label = label, .dependencies = dependencies, - .resource_accesses = .{.allocator = heap.allocator}, + .resource_accesses = .{.allocator = coral.heap.allocator}, }); const system = self.graph.get_ptr(node).?; diff --git a/src/main.zig b/src/main.zig index 6a79895..b49021c 100644 --- a/src/main.zig +++ b/src/main.zig @@ -23,7 +23,7 @@ pub fn main() !void { }); } -fn load(display: coral.Write(ona.gfx.Display), actors: coral.Write(Actors), assets: coral.Write(ona.gfx.Assets)) !void { +fn load(display: ona.Write(ona.gfx.Display), actors: ona.Write(Actors), assets: ona.Write(ona.gfx.Assets)) !void { display.res.width, display.res.height = .{1280, 720}; actors.res.body_texture = try assets.res.create_from_file(coral.files.bundle, "actor.bmp"); actors.res.quad_mesh_2d = try assets.res.create_quad_mesh_2d(@splat(1)); @@ -44,11 +44,11 @@ fn load(display: coral.Write(ona.gfx.Display), actors: coral.Write(Actors), asse try actors.res.instances.push_grow(.{0, 0}); } -fn exit(actors: coral.Write(Actors)) void { +fn exit(actors: ona.Write(Actors)) void { actors.res.instances.deinit(); } -fn render(queue: ona.gfx.Queue, actors: coral.Write(Actors), display: coral.Read(ona.gfx.Display)) !void { +fn render(queue: ona.gfx.Queue, actors: ona.Write(Actors), display: ona.Read(ona.gfx.Display)) !void { try queue.commands.append(.{.target = .{ .texture = actors.res.render_texture.?, .clear_color = .{0, 0, 0, 0}, @@ -85,19 +85,19 @@ fn render(queue: ona.gfx.Queue, actors: coral.Write(Actors), display: coral.Read }}); } -fn update(player: coral.Read(Player), actors: coral.Write(Actors), mapping: coral.Read(ona.act.Mapping)) !void { +fn update(player: ona.Read(Player), actors: ona.Write(Actors), mapping: ona.Read(ona.act.Mapping)) !void { actors.res.instances.values[0] += .{ mapping.res.axis_strength(player.res.move_x) * 10, mapping.res.axis_strength(player.res.move_y) * 10, }; } -fn setup(world: *coral.World, events: ona.App.Events) !void { - try world.set_resource(.none, Actors{}); - try world.set_resource(.none, Player{}); +fn setup(world: *ona.World, events: ona.App.Events) !void { + try world.set_resource(Actors{}); + try world.set_resource(Player{}); - try world.on_event(events.load, coral.system_fn(load), .{.label = "load"}); - try world.on_event(events.update, coral.system_fn(update), .{.label = "update"}); - try world.on_event(events.exit, coral.system_fn(exit), .{.label = "exit"}); - try world.on_event(events.render, coral.system_fn(render), .{.label = "render actors"}); + try world.on_event(events.load, ona.system_fn(load), .{.label = "load"}); + try world.on_event(events.update, ona.system_fn(update), .{.label = "update"}); + try world.on_event(events.exit, ona.system_fn(exit), .{.label = "exit"}); + try world.on_event(events.render, ona.system_fn(render), .{.label = "render actors"}); } diff --git a/src/ona/App.zig b/src/ona/App.zig index e4452da..e985626 100644 --- a/src/ona/App.zig +++ b/src/ona/App.zig @@ -1,17 +1,19 @@ const coral = @import("coral"); +const flow = @import("flow"); + events: *const Events, target_frame_time: f64, is_running: bool, pub const Events = struct { - load: coral.World.Event, - pre_update: coral.World.Event, - update: coral.World.Event, - post_update: coral.World.Event, - render: coral.World.Event, - finish: coral.World.Event, - exit: coral.World.Event, + load: flow.World.Event, + pre_update: flow.World.Event, + update: flow.World.Event, + post_update: flow.World.Event, + render: flow.World.Event, + finish: flow.World.Event, + exit: flow.World.Event, }; const Self = @This(); diff --git a/src/ona/act.zig b/src/ona/act.zig index 293945f..eb0ab3d 100644 --- a/src/ona/act.zig +++ b/src/ona/act.zig @@ -2,6 +2,8 @@ const App = @import("./App.zig"); const coral = @import("coral"); +const flow = @import("flow"); + const gfx = @import("./gfx.zig"); const msg = @import("./msg.zig"); @@ -35,15 +37,15 @@ pub const Mapping = struct { } }; -pub fn setup(world: *coral.World, events: App.Events) std.mem.Allocator.Error!void { - try world.set_resource(.none, Mapping{}); +pub fn setup(world: *flow.World, events: App.Events) std.mem.Allocator.Error!void { + try world.set_resource(Mapping{}); - try world.on_event(events.pre_update, coral.system_fn(update), .{ + try world.on_event(events.pre_update, flow.system_fn(update), .{ .label = "update act", }); } -pub fn update(inputs: msg.Receive(gfx.Input), mapping: coral.Write(Mapping)) void { +pub fn update(inputs: msg.Receive(gfx.Input), mapping: flow.Write(Mapping)) void { mapping.res.keys_pressed = Mapping.ScancodeSet.initEmpty(); for (inputs.messages()) |message| { diff --git a/src/ona/gfx.zig b/src/ona/gfx.zig index 50c973b..8c314ee 100644 --- a/src/ona/gfx.zig +++ b/src/ona/gfx.zig @@ -6,27 +6,35 @@ const device = @import("./gfx/device.zig"); const ext = @import("./ext.zig"); +const flow = @import("flow"); + const formats = @import("./gfx/formats.zig"); const msg = @import("./msg.zig"); const std = @import("std"); -pub const Assets = struct { - context: device.Context, - formats: coral.stack.Sequential(Format), - staging_arena: std.heap.ArenaAllocator, +pub const AssetFormat = struct { + extension: []const u8, + file_desc: *const FileDesc, - pub const Format = struct { - extension: []const u8, - 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 const Error = std.mem.Allocator.Error || coral.files.Error || error { + FormatUnsupported, }; - pub fn create_from_file(self: *Assets, storage: coral.files.Storage, path: []const u8) (OpenError || Format.Error)!*Handle { + pub const FileDesc = fn (*std.heap.ArenaAllocator, coral.files.Storage, []const u8) Error!Desc; +}; + +pub const Assets = struct { + context: device.Context, + formats: coral.stack.Sequential(AssetFormat), + staging_arena: std.heap.ArenaAllocator, + + pub fn create_from_file( + self: *Assets, + storage: coral.files.Storage, + path: []const u8, + ) (OpenError || AssetFormat.Error)!*Handle { defer { const max_cache_size = 536870912; @@ -62,6 +70,8 @@ pub const Assets = struct { }, }); } + + pub const thread_restriction = .main; }; pub const Color = @Vector(4, f32); @@ -163,7 +173,7 @@ pub const Queue = struct { command_index: usize, }; - pub fn bind(_: coral.system.BindContext) std.mem.Allocator.Error!State { + pub fn bind(_: flow.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}; @@ -207,7 +217,7 @@ pub const Transform2D = extern struct { origin: Point2D = @splat(0), }; -const builtin_formats = [_]Assets.Format{ +const builtin_formats = [_]AssetFormat{ .{ .extension = "bmp", .file_desc = formats.bmp_file_desc, @@ -228,7 +238,7 @@ pub const colors = struct { } }; -pub fn poll(app: coral.Write(App), inputs: msg.Send(Input)) !void { +pub fn poll(app: flow.Write(App), inputs: msg.Send(Input)) !void { var event = @as(ext.SDL_Event, undefined); while (ext.SDL_PollEvent(&event) != 0) { @@ -241,7 +251,7 @@ pub fn poll(app: coral.Write(App), inputs: msg.Send(Input)) !void { } } -pub fn setup(world: *coral.World, events: App.Events) (error {Unsupported} || std.Thread.SpawnError || std.mem.Allocator.Error)!void { +pub fn setup(world: *flow.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.Unsupported; } @@ -250,32 +260,32 @@ pub fn setup(world: *coral.World, events: App.Events) (error {Unsupported} || st errdefer context.deinit(); - var registered_formats = coral.stack.Sequential(Assets.Format){.allocator = coral.heap.allocator}; + var registered_formats = coral.stack.Sequential(AssetFormat){.allocator = coral.heap.allocator}; errdefer registered_formats.deinit(); try registered_formats.grow(builtin_formats.len); std.debug.assert(registered_formats.push_all(&builtin_formats)); - try world.set_resource(.none, Assets{ + try world.set_resource(Assets{ .staging_arena = std.heap.ArenaAllocator.init(coral.heap.allocator), .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(synchronize), .{.label = "synchronize gfx"}); + try world.set_resource(Display{}); + 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"}); } -pub fn stop(assets: coral.Write(Assets)) void { +pub fn stop(assets: flow.Write(Assets)) void { assets.res.staging_arena.deinit(); assets.res.formats.deinit(); assets.res.context.deinit(); } -pub fn synchronize(assets: coral.Write(Assets), display: coral.Read(Display)) !void { +pub fn synchronize(assets: flow.Write(Assets), display: flow.Read(Display)) !void { assets.res.context.submit(.{ .width = display.res.width, .height = display.res.height, diff --git a/src/ona/gfx/device.zig b/src/ona/gfx/device.zig index 679475e..4cfb5dd 100644 --- a/src/ona/gfx/device.zig +++ b/src/ona/gfx/device.zig @@ -2,14 +2,14 @@ const commands = @import("./commands.zig"); const coral = @import("coral"); -const default_2d = @import("./shaders/default_2d.glsl.zig"); - const ext = @import("../ext.zig"); const gfx = @import("../gfx.zig"); const sokol = @import("sokol"); +const spirv = @import("./spirv.zig"); + const std = @import("std"); pub const Context = struct { @@ -125,7 +125,7 @@ pub const Context = struct { }; const Frame = struct { - projection: default_2d.Mat4 = .{@splat(0), @splat(0), @splat(0), @splat(0)}, + projection: Projection = .{@splat(0), @splat(0), @splat(0), @splat(0)}, flushed_batch_count: usize = 0, pushed_batch_count: usize = 0, render_passes: usize = 0, @@ -380,7 +380,7 @@ const Loop = struct { ext.SDL_GL_DeleteContext(context); } - var rendering_2d = Rendering2D.init(); + var rendering_2d = try Rendering2D.init(coral.heap.allocator); defer rendering_2d.deinit(); @@ -455,6 +455,8 @@ const Loop = struct { } }; +const Projection = [4]@Vector(4, f32); + pub const RenderChain = commands.Chain(gfx.Command, clone_command); pub const RenderList = commands.List(gfx.Command, clone_command); @@ -529,10 +531,7 @@ const Rendering2D = struct { } sokol.gfx.applyPipeline(self.batching_pipeline); - - sokol.gfx.applyUniforms(.VS, default_2d.SLOT_Projection, sokol.gfx.asRange(&default_2d.Projection{ - .projection = frame.projection, - })); + sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&frame.projection)); const mesh_2d = resource_cast(frame.mesh.?).payload.mesh_2d; @@ -603,7 +602,7 @@ const Rendering2D = struct { } } - fn init() Rendering2D { + fn init(allocator: std.mem.Allocator) spirv.CompileError!Rendering2D { sokol.gfx.setup(.{ .environment = .{ .defaults = .{ @@ -618,6 +617,18 @@ const Rendering2D = struct { }, }); + var arena = std.heap.ArenaAllocator.init(allocator); + + defer arena.deinit(); + + const shader_desc = try spirv.compile(&arena, .{ + .fragment_spirv = shader_sources.fragment_spirv[0 ..], + .vertex_spirv = shader_sources.vertex_spirv[0 ..], + }); + + std.log.info("{s}\n", .{shader_desc.fs.source}); + std.log.info("{s}", .{shader_desc.vs.source}); + return .{ .batching_pipeline = sokol.gfx.makePipeline(.{ .label = "2D drawing pipeline", @@ -626,42 +637,42 @@ const Rendering2D = struct { .attrs = get: { var attrs = [_]sokol.gfx.VertexAttrState{.{}} ** 16; - attrs[default_2d.ATTR_vs_mesh_xy] = .{ + attrs[0] = .{ .format = .FLOAT2, .buffer_index = buffer_indices.mesh, }; - attrs[default_2d.ATTR_vs_mesh_uv] = .{ + attrs[1] = .{ .format = .FLOAT2, .buffer_index = buffer_indices.mesh, }; - attrs[default_2d.ATTR_vs_instance_xbasis] = .{ + attrs[2] = .{ .format = .FLOAT2, .buffer_index = buffer_indices.instance, }; - attrs[default_2d.ATTR_vs_instance_ybasis] = .{ + attrs[3] = .{ .format = .FLOAT2, .buffer_index = buffer_indices.instance, }; - attrs[default_2d.ATTR_vs_instance_origin] = .{ + attrs[4] = .{ .format = .FLOAT2, .buffer_index = buffer_indices.instance, }; - attrs[default_2d.ATTR_vs_instance_tint] = .{ + attrs[5] = .{ .format = .UBYTE4N, .buffer_index = buffer_indices.instance, }; - attrs[default_2d.ATTR_vs_instance_depth] = .{ + attrs[6] = .{ .format = .FLOAT, .buffer_index = buffer_indices.instance, }; - attrs[default_2d.ATTR_vs_instance_rect] = .{ + attrs[7] = .{ .format = .FLOAT4, .buffer_index = buffer_indices.instance, }; @@ -678,7 +689,7 @@ const Rendering2D = struct { }, }, - .shader = sokol.gfx.makeShader(default_2d.draw2dShaderDesc(sokol.gfx.queryBackend())), + .shader = sokol.gfx.makeShader(shader_desc), .index_type = .UINT16, }), @@ -698,7 +709,6 @@ const Rendering2D = struct { var pass = sokol.gfx.Pass{ .action = .{ - // TODO: Review if stencil buffer is needed. .stencil = .{.load_action = .CLEAR}, }, }; @@ -725,7 +735,7 @@ const Rendering2D = struct { const render_target = resource_cast(render_texture).payload.render_target; pass.attachments = render_target.attachments; - frame.projection = ortho_projection(0.0, @floatFromInt(render_target.width), 0.0, @floatFromInt(render_target.height), -1.0, 1.0); + frame.projection = orthographic_projection(0.0, @floatFromInt(render_target.width), 0.0, @floatFromInt(render_target.height), -1.0, 1.0); } else { var target_width, var target_height = [_]c_int{0, 0}; @@ -741,11 +751,127 @@ const Rendering2D = struct { .gl = .{.framebuffer = 0}, }; - frame.projection = ortho_projection(0.0, @floatFromInt(target_width), @floatFromInt(target_height), 0.0, -1.0, 1.0); + frame.projection = orthographic_projection(0.0, @floatFromInt(target_width), @floatFromInt(target_height), 0.0, -1.0, 1.0); } sokol.gfx.beginPass(pass); } + + const shader_sources = .{ + .vertex_spirv = &[_]u32{ + 0x07230203, 0x00010000, 0x0008000b, 0x00000053, 0x00000000, 0x00020011, 0x00000001, 0x0006000b, + 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001, + 0x0010000f, 0x00000000, 0x00000004, 0x6e69616d, 0x00000000, 0x0000000b, 0x0000000d, 0x00000013, + 0x0000001a, 0x00000034, 0x00000036, 0x0000003d, 0x0000003f, 0x00000042, 0x0000004c, 0x0000004e, + 0x00030003, 0x00000002, 0x000001ae, 0x00040005, 0x00000004, 0x6e69616d, 0x00000000, 0x00060005, + 0x00000009, 0x6c726f77, 0x6f705f64, 0x69746973, 0x00006e6f, 0x00060005, 0x0000000b, 0x74736e69, + 0x65636e61, 0x69726f5f, 0x006e6967, 0x00040005, 0x0000000d, 0x6873656d, 0x0079785f, 0x00060005, + 0x00000013, 0x74736e69, 0x65636e61, 0x6162785f, 0x00736973, 0x00060005, 0x0000001a, 0x74736e69, + 0x65636e61, 0x6162795f, 0x00736973, 0x00070005, 0x0000001e, 0x6a6f7270, 0x65746365, 0x6f705f64, + 0x69746973, 0x00006e6f, 0x00050005, 0x00000021, 0x6a6f7250, 0x69746365, 0x00006e6f, 0x00080006, + 0x00000021, 0x00000000, 0x6a6f7270, 0x69746365, 0x6d5f6e6f, 0x69727461, 0x00000078, 0x00030005, + 0x00000023, 0x00000000, 0x00060005, 0x00000032, 0x505f6c67, 0x65567265, 0x78657472, 0x00000000, + 0x00060006, 0x00000032, 0x00000000, 0x505f6c67, 0x7469736f, 0x006e6f69, 0x00070006, 0x00000032, + 0x00000001, 0x505f6c67, 0x746e696f, 0x657a6953, 0x00000000, 0x00070006, 0x00000032, 0x00000002, + 0x435f6c67, 0x4470696c, 0x61747369, 0x0065636e, 0x00030005, 0x00000034, 0x00000000, 0x00060005, + 0x00000036, 0x74736e69, 0x65636e61, 0x7065645f, 0x00006874, 0x00040005, 0x0000003d, 0x6f6c6f63, + 0x00000072, 0x00060005, 0x0000003f, 0x74736e69, 0x65636e61, 0x6e69745f, 0x00000074, 0x00050005, + 0x00000041, 0x74636572, 0x736f705f, 0x00000000, 0x00060005, 0x00000042, 0x74736e69, 0x65636e61, + 0x6365725f, 0x00000074, 0x00050005, 0x00000045, 0x74636572, 0x7a69735f, 0x00000065, 0x00030005, + 0x0000004c, 0x00007675, 0x00040005, 0x0000004e, 0x6873656d, 0x0076755f, 0x00040047, 0x0000000b, + 0x0000001e, 0x00000004, 0x00040047, 0x0000000d, 0x0000001e, 0x00000000, 0x00040047, 0x00000013, + 0x0000001e, 0x00000002, 0x00040047, 0x0000001a, 0x0000001e, 0x00000003, 0x00040048, 0x00000021, + 0x00000000, 0x00000005, 0x00050048, 0x00000021, 0x00000000, 0x00000023, 0x00000000, 0x00050048, + 0x00000021, 0x00000000, 0x00000007, 0x00000010, 0x00030047, 0x00000021, 0x00000002, 0x00040047, + 0x00000023, 0x00000022, 0x00000000, 0x00040047, 0x00000023, 0x00000021, 0x00000000, 0x00050048, + 0x00000032, 0x00000000, 0x0000000b, 0x00000000, 0x00050048, 0x00000032, 0x00000001, 0x0000000b, + 0x00000001, 0x00050048, 0x00000032, 0x00000002, 0x0000000b, 0x00000003, 0x00030047, 0x00000032, + 0x00000002, 0x00040047, 0x00000036, 0x0000001e, 0x00000006, 0x00040047, 0x0000003d, 0x0000001e, + 0x00000000, 0x00040047, 0x0000003f, 0x0000001e, 0x00000005, 0x00040047, 0x00000042, 0x0000001e, + 0x00000007, 0x00040047, 0x0000004c, 0x0000001e, 0x00000001, 0x00040047, 0x0000004e, 0x0000001e, + 0x00000001, 0x00020013, 0x00000002, 0x00030021, 0x00000003, 0x00000002, 0x00030016, 0x00000006, + 0x00000020, 0x00040017, 0x00000007, 0x00000006, 0x00000002, 0x00040020, 0x00000008, 0x00000007, + 0x00000007, 0x00040020, 0x0000000a, 0x00000001, 0x00000007, 0x0004003b, 0x0000000a, 0x0000000b, + 0x00000001, 0x0004003b, 0x0000000a, 0x0000000d, 0x00000001, 0x00040015, 0x0000000e, 0x00000020, + 0x00000000, 0x0004002b, 0x0000000e, 0x0000000f, 0x00000000, 0x00040020, 0x00000010, 0x00000001, + 0x00000006, 0x0004003b, 0x0000000a, 0x00000013, 0x00000001, 0x0004002b, 0x0000000e, 0x00000017, + 0x00000001, 0x0004003b, 0x0000000a, 0x0000001a, 0x00000001, 0x00040017, 0x0000001f, 0x00000006, + 0x00000004, 0x00040018, 0x00000020, 0x0000001f, 0x00000004, 0x0003001e, 0x00000021, 0x00000020, + 0x00040020, 0x00000022, 0x00000002, 0x00000021, 0x0004003b, 0x00000022, 0x00000023, 0x00000002, + 0x00040015, 0x00000024, 0x00000020, 0x00000001, 0x0004002b, 0x00000024, 0x00000025, 0x00000000, + 0x00040020, 0x00000026, 0x00000002, 0x00000020, 0x0004002b, 0x00000006, 0x0000002a, 0x00000000, + 0x0004002b, 0x00000006, 0x0000002b, 0x3f800000, 0x0004001c, 0x00000031, 0x00000006, 0x00000017, + 0x0005001e, 0x00000032, 0x0000001f, 0x00000006, 0x00000031, 0x00040020, 0x00000033, 0x00000003, + 0x00000032, 0x0004003b, 0x00000033, 0x00000034, 0x00000003, 0x0004003b, 0x00000010, 0x00000036, + 0x00000001, 0x00040020, 0x0000003b, 0x00000003, 0x0000001f, 0x0004003b, 0x0000003b, 0x0000003d, + 0x00000003, 0x00040020, 0x0000003e, 0x00000001, 0x0000001f, 0x0004003b, 0x0000003e, 0x0000003f, + 0x00000001, 0x0004003b, 0x0000003e, 0x00000042, 0x00000001, 0x00040020, 0x0000004b, 0x00000003, + 0x00000007, 0x0004003b, 0x0000004b, 0x0000004c, 0x00000003, 0x0004003b, 0x0000000a, 0x0000004e, + 0x00000001, 0x00050036, 0x00000002, 0x00000004, 0x00000000, 0x00000003, 0x000200f8, 0x00000005, + 0x0004003b, 0x00000008, 0x00000009, 0x00000007, 0x0004003b, 0x00000008, 0x0000001e, 0x00000007, + 0x0004003b, 0x00000008, 0x00000041, 0x00000007, 0x0004003b, 0x00000008, 0x00000045, 0x00000007, + 0x0004003d, 0x00000007, 0x0000000c, 0x0000000b, 0x00050041, 0x00000010, 0x00000011, 0x0000000d, + 0x0000000f, 0x0004003d, 0x00000006, 0x00000012, 0x00000011, 0x0004003d, 0x00000007, 0x00000014, + 0x00000013, 0x0005008e, 0x00000007, 0x00000015, 0x00000014, 0x00000012, 0x00050081, 0x00000007, + 0x00000016, 0x0000000c, 0x00000015, 0x00050041, 0x00000010, 0x00000018, 0x0000000d, 0x00000017, + 0x0004003d, 0x00000006, 0x00000019, 0x00000018, 0x0004003d, 0x00000007, 0x0000001b, 0x0000001a, + 0x0005008e, 0x00000007, 0x0000001c, 0x0000001b, 0x00000019, 0x00050081, 0x00000007, 0x0000001d, + 0x00000016, 0x0000001c, 0x0003003e, 0x00000009, 0x0000001d, 0x00050041, 0x00000026, 0x00000027, + 0x00000023, 0x00000025, 0x0004003d, 0x00000020, 0x00000028, 0x00000027, 0x0004003d, 0x00000007, + 0x00000029, 0x00000009, 0x00050051, 0x00000006, 0x0000002c, 0x00000029, 0x00000000, 0x00050051, + 0x00000006, 0x0000002d, 0x00000029, 0x00000001, 0x00070050, 0x0000001f, 0x0000002e, 0x0000002c, + 0x0000002d, 0x0000002a, 0x0000002b, 0x00050091, 0x0000001f, 0x0000002f, 0x00000028, 0x0000002e, + 0x0007004f, 0x00000007, 0x00000030, 0x0000002f, 0x0000002f, 0x00000000, 0x00000001, 0x0003003e, + 0x0000001e, 0x00000030, 0x0004003d, 0x00000007, 0x00000035, 0x0000001e, 0x0004003d, 0x00000006, + 0x00000037, 0x00000036, 0x00050051, 0x00000006, 0x00000038, 0x00000035, 0x00000000, 0x00050051, + 0x00000006, 0x00000039, 0x00000035, 0x00000001, 0x00070050, 0x0000001f, 0x0000003a, 0x00000038, + 0x00000039, 0x00000037, 0x0000002b, 0x00050041, 0x0000003b, 0x0000003c, 0x00000034, 0x00000025, + 0x0003003e, 0x0000003c, 0x0000003a, 0x0004003d, 0x0000001f, 0x00000040, 0x0000003f, 0x0003003e, + 0x0000003d, 0x00000040, 0x0004003d, 0x0000001f, 0x00000043, 0x00000042, 0x0007004f, 0x00000007, + 0x00000044, 0x00000043, 0x00000043, 0x00000000, 0x00000001, 0x0003003e, 0x00000041, 0x00000044, + 0x0004003d, 0x0000001f, 0x00000046, 0x00000042, 0x0007004f, 0x00000007, 0x00000047, 0x00000046, + 0x00000046, 0x00000002, 0x00000003, 0x0004003d, 0x0000001f, 0x00000048, 0x00000042, 0x0007004f, + 0x00000007, 0x00000049, 0x00000048, 0x00000048, 0x00000000, 0x00000001, 0x00050083, 0x00000007, + 0x0000004a, 0x00000047, 0x00000049, 0x0003003e, 0x00000045, 0x0000004a, 0x0004003d, 0x00000007, + 0x0000004d, 0x00000041, 0x0004003d, 0x00000007, 0x0000004f, 0x0000004e, 0x0004003d, 0x00000007, + 0x00000050, 0x00000045, 0x00050085, 0x00000007, 0x00000051, 0x0000004f, 0x00000050, 0x00050081, + 0x00000007, 0x00000052, 0x0000004d, 0x00000051, 0x0003003e, 0x0000004c, 0x00000052, 0x000100fd, + 0x00010038, + }, + + .fragment_spirv = &[_]u32{ + 0x07230203, 0x00010000, 0x0008000b, 0x00000028, 0x00000000, 0x00020011, 0x00000001, 0x0006000b, + 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001, + 0x0008000f, 0x00000004, 0x00000004, 0x6e69616d, 0x00000000, 0x00000009, 0x00000016, 0x0000001a, + 0x00030010, 0x00000004, 0x00000007, 0x00030003, 0x00000002, 0x000001ae, 0x00040005, 0x00000004, + 0x6e69616d, 0x00000000, 0x00040005, 0x00000009, 0x65786574, 0x0000006c, 0x00030005, 0x0000000c, + 0x00786574, 0x00030005, 0x00000010, 0x00706d73, 0x00030005, 0x00000016, 0x00007675, 0x00040005, + 0x0000001a, 0x6f6c6f63, 0x00000072, 0x00040047, 0x00000009, 0x0000001e, 0x00000000, 0x00040047, + 0x0000000c, 0x00000022, 0x00000000, 0x00040047, 0x0000000c, 0x00000021, 0x00000000, 0x00040047, + 0x00000010, 0x00000022, 0x00000000, 0x00040047, 0x00000010, 0x00000021, 0x00000000, 0x00040047, + 0x00000016, 0x0000001e, 0x00000001, 0x00040047, 0x0000001a, 0x0000001e, 0x00000000, 0x00020013, + 0x00000002, 0x00030021, 0x00000003, 0x00000002, 0x00030016, 0x00000006, 0x00000020, 0x00040017, + 0x00000007, 0x00000006, 0x00000004, 0x00040020, 0x00000008, 0x00000003, 0x00000007, 0x0004003b, + 0x00000008, 0x00000009, 0x00000003, 0x00090019, 0x0000000a, 0x00000006, 0x00000001, 0x00000000, + 0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x00040020, 0x0000000b, 0x00000000, 0x0000000a, + 0x0004003b, 0x0000000b, 0x0000000c, 0x00000000, 0x0002001a, 0x0000000e, 0x00040020, 0x0000000f, + 0x00000000, 0x0000000e, 0x0004003b, 0x0000000f, 0x00000010, 0x00000000, 0x0003001b, 0x00000012, + 0x0000000a, 0x00040017, 0x00000014, 0x00000006, 0x00000002, 0x00040020, 0x00000015, 0x00000001, + 0x00000014, 0x0004003b, 0x00000015, 0x00000016, 0x00000001, 0x00040020, 0x00000019, 0x00000001, + 0x00000007, 0x0004003b, 0x00000019, 0x0000001a, 0x00000001, 0x00040015, 0x0000001d, 0x00000020, + 0x00000000, 0x0004002b, 0x0000001d, 0x0000001e, 0x00000003, 0x00040020, 0x0000001f, 0x00000003, + 0x00000006, 0x0004002b, 0x00000006, 0x00000022, 0x00000000, 0x00020014, 0x00000023, 0x00050036, + 0x00000002, 0x00000004, 0x00000000, 0x00000003, 0x000200f8, 0x00000005, 0x0004003d, 0x0000000a, + 0x0000000d, 0x0000000c, 0x0004003d, 0x0000000e, 0x00000011, 0x00000010, 0x00050056, 0x00000012, + 0x00000013, 0x0000000d, 0x00000011, 0x0004003d, 0x00000014, 0x00000017, 0x00000016, 0x00050057, + 0x00000007, 0x00000018, 0x00000013, 0x00000017, 0x0004003d, 0x00000007, 0x0000001b, 0x0000001a, + 0x00050085, 0x00000007, 0x0000001c, 0x00000018, 0x0000001b, 0x0003003e, 0x00000009, 0x0000001c, + 0x00050041, 0x0000001f, 0x00000020, 0x00000009, 0x0000001e, 0x0004003d, 0x00000006, 0x00000021, + 0x00000020, 0x000500b4, 0x00000023, 0x00000024, 0x00000021, 0x00000022, 0x000300f7, 0x00000026, + 0x00000000, 0x000400fa, 0x00000024, 0x00000025, 0x00000026, 0x000200f8, 0x00000025, 0x000100fc, + 0x000200f8, 0x00000026, 0x000100fd, 0x00010038, + }, + }; }; const Resource = struct { @@ -805,19 +931,19 @@ fn clone_command(self: gfx.Command, arena: *std.heap.ArenaAllocator) std.mem.All }; } +fn orthographic_projection(left: f32, right: f32, bottom: f32, top: f32, zNear: f32, zFar: f32) Projection { + const width = right - left; + const height = top - bottom; + const depth = zFar - zNear; + + return .{ + .{2 / width, 0, 0, 0}, + .{0, 2 / height, 0, 0}, + .{0, 0, -(2 / depth), 0}, + .{-((right + left) / width), -((top + bottom) / height), -((zFar + zNear) / depth), 1}, + }; +} + fn resource_cast(handle: *gfx.Handle) *Resource { return @ptrCast(@alignCast(handle)); } - -fn ortho_projection(left: f32, right: f32, bottom: f32, top: f32, zNear: f32, zFar: f32) default_2d.Mat4 { - var result = default_2d.Mat4{.{1, 0, 0, 0}, .{0, 1, 0, 0}, .{0, 0, 1, 0}, .{0, 0, 0, 1}}; - - result[0][0] = 2 / (right - left); - result[1][1] = 2 / (top - bottom); - result[2][2] = - 2 / (zFar - zNear); - result[3][0] = - (right + left) / (right - left); - result[3][1] = - (top + bottom) / (top - bottom); - result[3][2] = - (zFar + zNear) / (zFar - zNear); - - return result; -} diff --git a/src/ona/gfx/formats.zig b/src/ona/gfx/formats.zig index a767d7f..98d5952 100644 --- a/src/ona/gfx/formats.zig +++ b/src/ona/gfx/formats.zig @@ -8,7 +8,7 @@ pub fn bmp_file_desc( arena: *std.heap.ArenaAllocator, storage: coral.files.Storage, path: []const u8, -) gfx.Assets.Format.Error!gfx.Desc { +) gfx.AssetFormat.Error!gfx.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/shaders/default_2d.glsl b/src/ona/gfx/shaders/default_2d.glsl deleted file mode 100644 index 111abec..0000000 --- a/src/ona/gfx/shaders/default_2d.glsl +++ /dev/null @@ -1,60 +0,0 @@ -@header pub const Vec2 = @Vector(2, f32) -@header pub const Mat4 = [4]@Vector(4, f32) -@ctype vec2 Vec2 -@ctype mat4 Mat4 - -@vs vs -in vec2 mesh_xy; -in vec2 mesh_uv; - -in vec2 instance_xbasis; -in vec2 instance_ybasis; -in vec2 instance_origin; -in vec4 instance_tint; -in float instance_depth; -in vec4 instance_rect; - -uniform Projection { - mat4 projection; -}; - -out vec4 color; -out vec2 uv; - -void main() { - // Calculate the world position of the vertex - const vec2 world_position = instance_origin + mesh_xy.x * instance_xbasis + mesh_xy.y * instance_ybasis; - const vec2 projected_position = (projection * vec4(world_position, 0, 1)).xy; - - // Set the position of the vertex in clip space - gl_Position = vec4(projected_position, instance_depth, 1.0); - color = instance_tint; - - // Calculate the width and height from left, top, right, bottom configuration - const vec2 rect_pos = instance_rect.xy; // left, top - const vec2 rect_size = instance_rect.zw - instance_rect.xy; // right - left, bottom - top - - // Calculate the adjusted UV coordinates using the instance_rect - uv = rect_pos + (mesh_uv * rect_size); -} -@end - -@fs fs -uniform texture2D tex; -uniform sampler smp; - -in vec4 color; -in vec2 uv; - -out vec4 texel; - -void main() { - texel = texture(sampler2D(tex, smp), uv) * color; - - if (texel.a == 0) { - discard; - } -} -@end - -@program draw_2d vs fs diff --git a/src/ona/gfx/shaders/render_2d.frag b/src/ona/gfx/shaders/render_2d.frag new file mode 100644 index 0000000..a059a43 --- /dev/null +++ b/src/ona/gfx/shaders/render_2d.frag @@ -0,0 +1,17 @@ +#version 430 + +layout (binding = 0) uniform texture2D tex; +layout (binding = 0) uniform sampler smp; + +layout (location = 0) in vec4 color; +layout (location = 1) in vec2 uv; + +layout (location = 0) out vec4 texel; + +void main() { + texel = texture(sampler2D(tex, smp), uv) * color; + + if (texel.a == 0) { + discard; + } +} diff --git a/src/ona/gfx/shaders/render_2d.vert b/src/ona/gfx/shaders/render_2d.vert new file mode 100644 index 0000000..cea5831 --- /dev/null +++ b/src/ona/gfx/shaders/render_2d.vert @@ -0,0 +1,36 @@ +#version 430 + +layout (location = 0) in vec2 mesh_xy; +layout (location = 1) in vec2 mesh_uv; + +layout (location = 2) in vec2 instance_xbasis; +layout (location = 3) in vec2 instance_ybasis; +layout (location = 4) in vec2 instance_origin; +layout (location = 5) in vec4 instance_tint; +layout (location = 6) in float instance_depth; +layout (location = 7) in vec4 instance_rect; + +layout (binding = 0) uniform Projection { + mat4 projection_matrix; +}; + +layout (location = 0) out vec4 color; +layout (location = 1) out vec2 uv; + +void main() { + // Calculate the world position of the vertex + const vec2 world_position = instance_origin + mesh_xy.x * instance_xbasis + mesh_xy.y * instance_ybasis; + const vec2 projected_position = (projection_matrix * vec4(world_position, 0, 1)).xy; + + // Set the position of the vertex in clip space + gl_Position = vec4(projected_position, instance_depth, 1.0); + color = instance_tint; + + // Calculate the width and height from left, top, right, bottom configuration + const vec2 rect_pos = instance_rect.xy; // left, top + const vec2 rect_size = instance_rect.zw - instance_rect.xy; // right - left, bottom - top + + // Calculate the adjusted UV coordinates using the instance_rect + uv = rect_pos + (mesh_uv * rect_size); +} + diff --git a/src/ona/gfx/spirv.zig b/src/ona/gfx/spirv.zig new file mode 100644 index 0000000..857f4c2 --- /dev/null +++ b/src/ona/gfx/spirv.zig @@ -0,0 +1,130 @@ +const coral = @import("coral"); + +const ext = @cImport({ + @cInclude("spirv-cross/spirv_cross_c.h"); +}); + +const sokol = @import("sokol"); + +const std = @import("std"); + +pub const CompileError = std.mem.Allocator.Error || error { + UnsupportedTarget, + InvalidSpirV, + UnsupportedSpirv, + UnknownFailure, +}; + +pub const Sources = struct { + vertex_spirv: []const u32, + fragment_spirv: []const u32, +}; + +pub fn compile(arena: *std.heap.ArenaAllocator, sources: Sources) CompileError!sokol.gfx.ShaderDesc { + var context = @as(ext.spvc_context, null); + + try switch (ext.spvc_context_create(&context)) { + ext.SPVC_ERROR_OUT_OF_MEMORY => error.OutOfMemory, + ext.SPVC_SUCCESS => {}, + else => error.UnknownFailure, + }; + + ext.spvc_context_set_error_callback(context, log_errors, null); + + defer ext.spvc_context_destroy(context); + + const arena_allocator = arena.allocator(); + const shader_source_sentinel = @as(u8, 0); + + return .{ + .vs = .{ + .source = try arena_allocator.dupeZ(u8, coral.io.slice_sentineled( + shader_source_sentinel, + @ptrCast(try compile_shader(context, ext.SpvExecutionModelVertex, sources.vertex_spirv)))), + }, + + .fs = .{ + .source = try arena_allocator.dupeZ(u8, coral.io.slice_sentineled( + shader_source_sentinel, + @ptrCast(try compile_shader(context, ext.SpvExecutionModelFragment, sources.fragment_spirv)))), + }, + }; +} + +fn compile_shader(context: ext.spvc_context, model: ext.SpvExecutionModel, spirv: []const u32) CompileError![*]const u8 { + var parsed_ir = @as(ext.spvc_parsed_ir, null); + + try switch (ext.spvc_context_parse_spirv(context, spirv.ptr, spirv.len, &parsed_ir)) { + ext.SPVC_ERROR_OUT_OF_MEMORY => error.OutOfMemory, + ext.SPVC_ERROR_INVALID_SPIRV => error.InvalidSpirV, + ext.SPVC_SUCCESS => {}, + else => error.UnknownFailure, + }; + + var compiler = @as(ext.spvc_compiler, null); + + try switch (ext.spvc_context_create_compiler(context, ext.SPVC_BACKEND_GLSL, parsed_ir, ext.SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, &compiler)) { + ext.SPVC_ERROR_OUT_OF_MEMORY => error.OutOfMemory, + ext.SPVC_ERROR_INVALID_ARGUMENT => error.UnsupportedTarget, + ext.SPVC_SUCCESS => {}, + else => error.UnknownFailure, + }; + + try switch (ext.spvc_compiler_build_combined_image_samplers(compiler)) { + ext.SPVC_ERROR_UNSUPPORTED_SPIRV => error.UnsupportedSpirv, + ext.SPVC_SUCCESS => {}, + else => error.UnknownFailure, + }; + + try switch (ext.spvc_compiler_set_entry_point(compiler, "main", model)) { + ext.SPVC_SUCCESS => {}, + else => error.UnknownFailure, + }; + + var options = @as(ext.spvc_compiler_options, null); + + try switch (ext.spvc_compiler_create_compiler_options(compiler, &options)) { + ext.SPVC_ERROR_OUT_OF_MEMORY => error.OutOfMemory, + ext.SPVC_SUCCESS => {}, + else => error.UnknownFailure, + }; + + try switch (ext.spvc_compiler_options_set_uint(options, ext.SPVC_COMPILER_OPTION_GLSL_VERSION, 430)) { + ext.SPVC_ERROR_INVALID_ARGUMENT => error.InvalidSpirV, + ext.SPVC_SUCCESS => {}, + else => error.UnknownFailure, + }; + + try switch (ext.spvc_compiler_options_set_bool(options, ext.SPVC_COMPILER_OPTION_GLSL_ES, @intFromBool(false))) { + ext.SPVC_ERROR_INVALID_ARGUMENT => error.InvalidSpirV, + ext.SPVC_SUCCESS => {}, + else => error.UnknownFailure, + }; + + try switch (ext.spvc_compiler_options_set_bool(options, ext.SPVC_COMPILER_OPTION_GLSL_VULKAN_SEMANTICS, @intFromBool(false))) { + ext.SPVC_ERROR_INVALID_ARGUMENT => error.InvalidSpirV, + ext.SPVC_SUCCESS => {}, + else => error.UnknownFailure, + }; + + try switch (ext.spvc_compiler_install_compiler_options(compiler, options)) { + ext.SPVC_SUCCESS => {}, + else => error.UnknownFailure, + }; + + var source = @as([*]const u8, undefined); + + try switch (ext.spvc_compiler_compile(compiler, @ptrCast(&source))) { + ext.SPVC_ERROR_OUT_OF_MEMORY => error.OutOfMemory, + ext.SPVC_ERROR_UNSUPPORTED_SPIRV => error.UnsupportedSpirv, + ext.SPVC_SUCCESS => {}, + else => error.UnknownFailure, + }; + + return source; +} + +fn log_errors(userdata: ?*anyopaque, error_message: [*c]const u8) callconv(.C) void { + std.debug.assert(userdata == null); + std.log.err("{s}", .{error_message}); +} diff --git a/src/ona/msg.zig b/src/ona/msg.zig index 54d2f1b..f90b225 100644 --- a/src/ona/msg.zig +++ b/src/ona/msg.zig @@ -2,6 +2,8 @@ const App = @import("./App.zig"); const coral = @import("coral"); +const flow = @import("flow"); + const std = @import("std"); fn Channel(comptime Message: type) type { @@ -12,7 +14,7 @@ fn Channel(comptime Message: type) type { const Self = @This(); - fn cleanup(channel: coral.Write(Self)) void { + fn cleanup(channel: flow.Write(Self)) void { channel.res.deinit(); } @@ -24,7 +26,7 @@ fn Channel(comptime Message: type) type { self.* = undefined; } - fn swap(channel: coral.Write(Self)) void { + fn swap(channel: flow.Write(Self)) void { channel.res.ticks = coral.scalars.add(channel.res.ticks, 1) orelse 0; if (channel.res.ticks == 0) { @@ -65,12 +67,12 @@ pub fn Receive(comptime Message: type) type { channel: *const TypedChannel, }; - pub fn bind(context: coral.system.BindContext) std.mem.Allocator.Error!State { + pub fn bind(context: flow.system.BindContext) std.mem.Allocator.Error!State { return .{ - .channel = (try context.register_read_only_resource_access(thread_restriction, TypedChannel)) orelse set: { - try context.world.set_resource(thread_restriction, TypedChannel.init(coral.heap.allocator)); + .channel = (try context.register_readable_resource_access(TypedChannel)) orelse set: { + try context.world.set_resource(TypedChannel.init(coral.heap.allocator)); - break: set (try context.register_read_only_resource_access(thread_restriction, TypedChannel)).?; + break: set (try context.register_readable_resource_access(TypedChannel)).?; }, }; } @@ -99,22 +101,22 @@ pub fn Send(comptime Message: type) type { channel: *TypedChannel, }; - pub fn bind(context: coral.system.BindContext) std.mem.Allocator.Error!State { + pub fn bind(context: flow.system.BindContext) std.mem.Allocator.Error!State { return .{ - .channel = (try context.register_read_write_resource_access(thread_restriction, TypedChannel)) orelse set: { - try context.world.set_resource(thread_restriction, TypedChannel.init(coral.heap.allocator)); + .channel = (try context.register_writable_resource_access(TypedChannel)) orelse set: { + try context.world.set_resource(TypedChannel.init(coral.heap.allocator)); - const app = context.world.get_resource(.none, App).?; + const app = context.world.get_resource(App).?; - try context.world.on_event(app.events.post_update, coral.system_fn(TypedChannel.swap), .{ + try context.world.on_event(app.events.post_update, flow.system_fn(TypedChannel.swap), .{ .label = "swap channel of " ++ @typeName(Message), }); - try context.world.on_event(app.events.exit, coral.system_fn(TypedChannel.cleanup), .{ + try context.world.on_event(app.events.exit, flow.system_fn(TypedChannel.cleanup), .{ .label = "clean up channel of " ++ @typeName(Message), }); - break: set (try context.register_read_write_resource_access(thread_restriction, TypedChannel)).?; + break: set (try context.register_writable_resource_access(TypedChannel)).?; }, }; } @@ -130,5 +132,3 @@ pub fn Send(comptime Message: type) type { } }; } - -const thread_restriction = coral.World.ThreadRestriction.none; diff --git a/src/ona/ona.zig b/src/ona/ona.zig index 51f6f1e..c3b51ea 100644 --- a/src/ona/ona.zig +++ b/src/ona/ona.zig @@ -6,6 +6,8 @@ const coral = @import("coral"); const ext = @import("./ext.zig"); +const flow = @import("flow"); + pub const gfx = @import("./gfx.zig"); pub const msg = @import("./msg.zig"); @@ -23,7 +25,13 @@ pub const Options = struct { }; }; -pub const Setup = fn (*coral.World, App.Events) anyerror!void; +pub const Read = flow.Read; + +pub const Setup = fn (*flow.World, App.Events) anyerror!void; + +pub const World = flow.World; + +pub const Write = flow.Write; pub const default_middlewares = &.{ gfx.setup, @@ -37,7 +45,7 @@ pub fn start_app(setup: Setup, options: Options) anyerror!void { } var world = try switch (options.execution) { - .single_threaded => coral.World.init(0), + .single_threaded => flow.World.init(0), .thread_share => |thread_share| init: { const cpu_count = @as(u32, @intCast(std.math.clamp(std.Thread.getCpuCount() catch |cpu_count_error| { @@ -48,7 +56,7 @@ pub fn start_app(setup: Setup, options: Options) anyerror!void { }); }, 0, std.math.maxInt(u32)))); - break: init coral.World.init(coral.scalars.fractional(cpu_count, thread_share) orelse 0); + break: init flow.World.init(coral.scalars.fractional(cpu_count, thread_share) orelse 0); }, }; @@ -64,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(.none, App{ + const app = try world.set_get_resource(App{ .events = &events, .target_frame_time = 1.0 / @as(f64, @floatFromInt(options.tick_rate)), .is_running = true, @@ -101,3 +109,5 @@ pub fn start_app(setup: Setup, options: Options) anyerror!void { try world.run_event(events.exit); } + +pub const system_fn = flow.system_fn; diff --git a/tools/sokol-shdc b/tools/sokol-shdc deleted file mode 100755 index 2c16e3a..0000000 --- a/tools/sokol-shdc +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0331c07b47617a76af9ea8bd87fc103af1f09ea0ce82c991925e47b3887e0618 -size 15195920 diff --git a/tools/sokol-shdc.exe b/tools/sokol-shdc.exe deleted file mode 100644 index a3811e3..0000000 --- a/tools/sokol-shdc.exe +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ccf08bc0d7cd96b83273340104728204ed2673ff1c47d782494313354eba2635 -size 9369088 From 2ba249f4bfea7c5d0fe8b293b9b33fad8715f573 Mon Sep 17 00:00:00 2001 From: kayomn Date: Tue, 2 Jul 2024 23:15:23 +0100 Subject: [PATCH 03/30] Use a single SPIRV binary for the default rendering 2D shader --- src/ona/gfx/device.zig | 220 +++++++++++++++++++---------------------- 1 file changed, 104 insertions(+), 116 deletions(-) diff --git a/src/ona/gfx/device.zig b/src/ona/gfx/device.zig index 4cfb5dd..c43619b 100644 --- a/src/ona/gfx/device.zig +++ b/src/ona/gfx/device.zig @@ -622,8 +622,8 @@ const Rendering2D = struct { defer arena.deinit(); const shader_desc = try spirv.compile(&arena, .{ - .fragment_spirv = shader_sources.fragment_spirv[0 ..], - .vertex_spirv = shader_sources.vertex_spirv[0 ..], + .fragment_spirv = shader_spirv[0 ..], + .vertex_spirv = shader_spirv[0 ..], }); std.log.info("{s}\n", .{shader_desc.fs.source}); @@ -757,120 +757,108 @@ const Rendering2D = struct { sokol.gfx.beginPass(pass); } - const shader_sources = .{ - .vertex_spirv = &[_]u32{ - 0x07230203, 0x00010000, 0x0008000b, 0x00000053, 0x00000000, 0x00020011, 0x00000001, 0x0006000b, - 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001, - 0x0010000f, 0x00000000, 0x00000004, 0x6e69616d, 0x00000000, 0x0000000b, 0x0000000d, 0x00000013, - 0x0000001a, 0x00000034, 0x00000036, 0x0000003d, 0x0000003f, 0x00000042, 0x0000004c, 0x0000004e, - 0x00030003, 0x00000002, 0x000001ae, 0x00040005, 0x00000004, 0x6e69616d, 0x00000000, 0x00060005, - 0x00000009, 0x6c726f77, 0x6f705f64, 0x69746973, 0x00006e6f, 0x00060005, 0x0000000b, 0x74736e69, - 0x65636e61, 0x69726f5f, 0x006e6967, 0x00040005, 0x0000000d, 0x6873656d, 0x0079785f, 0x00060005, - 0x00000013, 0x74736e69, 0x65636e61, 0x6162785f, 0x00736973, 0x00060005, 0x0000001a, 0x74736e69, - 0x65636e61, 0x6162795f, 0x00736973, 0x00070005, 0x0000001e, 0x6a6f7270, 0x65746365, 0x6f705f64, - 0x69746973, 0x00006e6f, 0x00050005, 0x00000021, 0x6a6f7250, 0x69746365, 0x00006e6f, 0x00080006, - 0x00000021, 0x00000000, 0x6a6f7270, 0x69746365, 0x6d5f6e6f, 0x69727461, 0x00000078, 0x00030005, - 0x00000023, 0x00000000, 0x00060005, 0x00000032, 0x505f6c67, 0x65567265, 0x78657472, 0x00000000, - 0x00060006, 0x00000032, 0x00000000, 0x505f6c67, 0x7469736f, 0x006e6f69, 0x00070006, 0x00000032, - 0x00000001, 0x505f6c67, 0x746e696f, 0x657a6953, 0x00000000, 0x00070006, 0x00000032, 0x00000002, - 0x435f6c67, 0x4470696c, 0x61747369, 0x0065636e, 0x00030005, 0x00000034, 0x00000000, 0x00060005, - 0x00000036, 0x74736e69, 0x65636e61, 0x7065645f, 0x00006874, 0x00040005, 0x0000003d, 0x6f6c6f63, - 0x00000072, 0x00060005, 0x0000003f, 0x74736e69, 0x65636e61, 0x6e69745f, 0x00000074, 0x00050005, - 0x00000041, 0x74636572, 0x736f705f, 0x00000000, 0x00060005, 0x00000042, 0x74736e69, 0x65636e61, - 0x6365725f, 0x00000074, 0x00050005, 0x00000045, 0x74636572, 0x7a69735f, 0x00000065, 0x00030005, - 0x0000004c, 0x00007675, 0x00040005, 0x0000004e, 0x6873656d, 0x0076755f, 0x00040047, 0x0000000b, - 0x0000001e, 0x00000004, 0x00040047, 0x0000000d, 0x0000001e, 0x00000000, 0x00040047, 0x00000013, - 0x0000001e, 0x00000002, 0x00040047, 0x0000001a, 0x0000001e, 0x00000003, 0x00040048, 0x00000021, - 0x00000000, 0x00000005, 0x00050048, 0x00000021, 0x00000000, 0x00000023, 0x00000000, 0x00050048, - 0x00000021, 0x00000000, 0x00000007, 0x00000010, 0x00030047, 0x00000021, 0x00000002, 0x00040047, - 0x00000023, 0x00000022, 0x00000000, 0x00040047, 0x00000023, 0x00000021, 0x00000000, 0x00050048, - 0x00000032, 0x00000000, 0x0000000b, 0x00000000, 0x00050048, 0x00000032, 0x00000001, 0x0000000b, - 0x00000001, 0x00050048, 0x00000032, 0x00000002, 0x0000000b, 0x00000003, 0x00030047, 0x00000032, - 0x00000002, 0x00040047, 0x00000036, 0x0000001e, 0x00000006, 0x00040047, 0x0000003d, 0x0000001e, - 0x00000000, 0x00040047, 0x0000003f, 0x0000001e, 0x00000005, 0x00040047, 0x00000042, 0x0000001e, - 0x00000007, 0x00040047, 0x0000004c, 0x0000001e, 0x00000001, 0x00040047, 0x0000004e, 0x0000001e, - 0x00000001, 0x00020013, 0x00000002, 0x00030021, 0x00000003, 0x00000002, 0x00030016, 0x00000006, - 0x00000020, 0x00040017, 0x00000007, 0x00000006, 0x00000002, 0x00040020, 0x00000008, 0x00000007, - 0x00000007, 0x00040020, 0x0000000a, 0x00000001, 0x00000007, 0x0004003b, 0x0000000a, 0x0000000b, - 0x00000001, 0x0004003b, 0x0000000a, 0x0000000d, 0x00000001, 0x00040015, 0x0000000e, 0x00000020, - 0x00000000, 0x0004002b, 0x0000000e, 0x0000000f, 0x00000000, 0x00040020, 0x00000010, 0x00000001, - 0x00000006, 0x0004003b, 0x0000000a, 0x00000013, 0x00000001, 0x0004002b, 0x0000000e, 0x00000017, - 0x00000001, 0x0004003b, 0x0000000a, 0x0000001a, 0x00000001, 0x00040017, 0x0000001f, 0x00000006, - 0x00000004, 0x00040018, 0x00000020, 0x0000001f, 0x00000004, 0x0003001e, 0x00000021, 0x00000020, - 0x00040020, 0x00000022, 0x00000002, 0x00000021, 0x0004003b, 0x00000022, 0x00000023, 0x00000002, - 0x00040015, 0x00000024, 0x00000020, 0x00000001, 0x0004002b, 0x00000024, 0x00000025, 0x00000000, - 0x00040020, 0x00000026, 0x00000002, 0x00000020, 0x0004002b, 0x00000006, 0x0000002a, 0x00000000, - 0x0004002b, 0x00000006, 0x0000002b, 0x3f800000, 0x0004001c, 0x00000031, 0x00000006, 0x00000017, - 0x0005001e, 0x00000032, 0x0000001f, 0x00000006, 0x00000031, 0x00040020, 0x00000033, 0x00000003, - 0x00000032, 0x0004003b, 0x00000033, 0x00000034, 0x00000003, 0x0004003b, 0x00000010, 0x00000036, - 0x00000001, 0x00040020, 0x0000003b, 0x00000003, 0x0000001f, 0x0004003b, 0x0000003b, 0x0000003d, - 0x00000003, 0x00040020, 0x0000003e, 0x00000001, 0x0000001f, 0x0004003b, 0x0000003e, 0x0000003f, - 0x00000001, 0x0004003b, 0x0000003e, 0x00000042, 0x00000001, 0x00040020, 0x0000004b, 0x00000003, - 0x00000007, 0x0004003b, 0x0000004b, 0x0000004c, 0x00000003, 0x0004003b, 0x0000000a, 0x0000004e, - 0x00000001, 0x00050036, 0x00000002, 0x00000004, 0x00000000, 0x00000003, 0x000200f8, 0x00000005, - 0x0004003b, 0x00000008, 0x00000009, 0x00000007, 0x0004003b, 0x00000008, 0x0000001e, 0x00000007, - 0x0004003b, 0x00000008, 0x00000041, 0x00000007, 0x0004003b, 0x00000008, 0x00000045, 0x00000007, - 0x0004003d, 0x00000007, 0x0000000c, 0x0000000b, 0x00050041, 0x00000010, 0x00000011, 0x0000000d, - 0x0000000f, 0x0004003d, 0x00000006, 0x00000012, 0x00000011, 0x0004003d, 0x00000007, 0x00000014, - 0x00000013, 0x0005008e, 0x00000007, 0x00000015, 0x00000014, 0x00000012, 0x00050081, 0x00000007, - 0x00000016, 0x0000000c, 0x00000015, 0x00050041, 0x00000010, 0x00000018, 0x0000000d, 0x00000017, - 0x0004003d, 0x00000006, 0x00000019, 0x00000018, 0x0004003d, 0x00000007, 0x0000001b, 0x0000001a, - 0x0005008e, 0x00000007, 0x0000001c, 0x0000001b, 0x00000019, 0x00050081, 0x00000007, 0x0000001d, - 0x00000016, 0x0000001c, 0x0003003e, 0x00000009, 0x0000001d, 0x00050041, 0x00000026, 0x00000027, - 0x00000023, 0x00000025, 0x0004003d, 0x00000020, 0x00000028, 0x00000027, 0x0004003d, 0x00000007, - 0x00000029, 0x00000009, 0x00050051, 0x00000006, 0x0000002c, 0x00000029, 0x00000000, 0x00050051, - 0x00000006, 0x0000002d, 0x00000029, 0x00000001, 0x00070050, 0x0000001f, 0x0000002e, 0x0000002c, - 0x0000002d, 0x0000002a, 0x0000002b, 0x00050091, 0x0000001f, 0x0000002f, 0x00000028, 0x0000002e, - 0x0007004f, 0x00000007, 0x00000030, 0x0000002f, 0x0000002f, 0x00000000, 0x00000001, 0x0003003e, - 0x0000001e, 0x00000030, 0x0004003d, 0x00000007, 0x00000035, 0x0000001e, 0x0004003d, 0x00000006, - 0x00000037, 0x00000036, 0x00050051, 0x00000006, 0x00000038, 0x00000035, 0x00000000, 0x00050051, - 0x00000006, 0x00000039, 0x00000035, 0x00000001, 0x00070050, 0x0000001f, 0x0000003a, 0x00000038, - 0x00000039, 0x00000037, 0x0000002b, 0x00050041, 0x0000003b, 0x0000003c, 0x00000034, 0x00000025, - 0x0003003e, 0x0000003c, 0x0000003a, 0x0004003d, 0x0000001f, 0x00000040, 0x0000003f, 0x0003003e, - 0x0000003d, 0x00000040, 0x0004003d, 0x0000001f, 0x00000043, 0x00000042, 0x0007004f, 0x00000007, - 0x00000044, 0x00000043, 0x00000043, 0x00000000, 0x00000001, 0x0003003e, 0x00000041, 0x00000044, - 0x0004003d, 0x0000001f, 0x00000046, 0x00000042, 0x0007004f, 0x00000007, 0x00000047, 0x00000046, - 0x00000046, 0x00000002, 0x00000003, 0x0004003d, 0x0000001f, 0x00000048, 0x00000042, 0x0007004f, - 0x00000007, 0x00000049, 0x00000048, 0x00000048, 0x00000000, 0x00000001, 0x00050083, 0x00000007, - 0x0000004a, 0x00000047, 0x00000049, 0x0003003e, 0x00000045, 0x0000004a, 0x0004003d, 0x00000007, - 0x0000004d, 0x00000041, 0x0004003d, 0x00000007, 0x0000004f, 0x0000004e, 0x0004003d, 0x00000007, - 0x00000050, 0x00000045, 0x00050085, 0x00000007, 0x00000051, 0x0000004f, 0x00000050, 0x00050081, - 0x00000007, 0x00000052, 0x0000004d, 0x00000051, 0x0003003e, 0x0000004c, 0x00000052, 0x000100fd, - 0x00010038, - }, - - .fragment_spirv = &[_]u32{ - 0x07230203, 0x00010000, 0x0008000b, 0x00000028, 0x00000000, 0x00020011, 0x00000001, 0x0006000b, - 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001, - 0x0008000f, 0x00000004, 0x00000004, 0x6e69616d, 0x00000000, 0x00000009, 0x00000016, 0x0000001a, - 0x00030010, 0x00000004, 0x00000007, 0x00030003, 0x00000002, 0x000001ae, 0x00040005, 0x00000004, - 0x6e69616d, 0x00000000, 0x00040005, 0x00000009, 0x65786574, 0x0000006c, 0x00030005, 0x0000000c, - 0x00786574, 0x00030005, 0x00000010, 0x00706d73, 0x00030005, 0x00000016, 0x00007675, 0x00040005, - 0x0000001a, 0x6f6c6f63, 0x00000072, 0x00040047, 0x00000009, 0x0000001e, 0x00000000, 0x00040047, - 0x0000000c, 0x00000022, 0x00000000, 0x00040047, 0x0000000c, 0x00000021, 0x00000000, 0x00040047, - 0x00000010, 0x00000022, 0x00000000, 0x00040047, 0x00000010, 0x00000021, 0x00000000, 0x00040047, - 0x00000016, 0x0000001e, 0x00000001, 0x00040047, 0x0000001a, 0x0000001e, 0x00000000, 0x00020013, - 0x00000002, 0x00030021, 0x00000003, 0x00000002, 0x00030016, 0x00000006, 0x00000020, 0x00040017, - 0x00000007, 0x00000006, 0x00000004, 0x00040020, 0x00000008, 0x00000003, 0x00000007, 0x0004003b, - 0x00000008, 0x00000009, 0x00000003, 0x00090019, 0x0000000a, 0x00000006, 0x00000001, 0x00000000, - 0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x00040020, 0x0000000b, 0x00000000, 0x0000000a, - 0x0004003b, 0x0000000b, 0x0000000c, 0x00000000, 0x0002001a, 0x0000000e, 0x00040020, 0x0000000f, - 0x00000000, 0x0000000e, 0x0004003b, 0x0000000f, 0x00000010, 0x00000000, 0x0003001b, 0x00000012, - 0x0000000a, 0x00040017, 0x00000014, 0x00000006, 0x00000002, 0x00040020, 0x00000015, 0x00000001, - 0x00000014, 0x0004003b, 0x00000015, 0x00000016, 0x00000001, 0x00040020, 0x00000019, 0x00000001, - 0x00000007, 0x0004003b, 0x00000019, 0x0000001a, 0x00000001, 0x00040015, 0x0000001d, 0x00000020, - 0x00000000, 0x0004002b, 0x0000001d, 0x0000001e, 0x00000003, 0x00040020, 0x0000001f, 0x00000003, - 0x00000006, 0x0004002b, 0x00000006, 0x00000022, 0x00000000, 0x00020014, 0x00000023, 0x00050036, - 0x00000002, 0x00000004, 0x00000000, 0x00000003, 0x000200f8, 0x00000005, 0x0004003d, 0x0000000a, - 0x0000000d, 0x0000000c, 0x0004003d, 0x0000000e, 0x00000011, 0x00000010, 0x00050056, 0x00000012, - 0x00000013, 0x0000000d, 0x00000011, 0x0004003d, 0x00000014, 0x00000017, 0x00000016, 0x00050057, - 0x00000007, 0x00000018, 0x00000013, 0x00000017, 0x0004003d, 0x00000007, 0x0000001b, 0x0000001a, - 0x00050085, 0x00000007, 0x0000001c, 0x00000018, 0x0000001b, 0x0003003e, 0x00000009, 0x0000001c, - 0x00050041, 0x0000001f, 0x00000020, 0x00000009, 0x0000001e, 0x0004003d, 0x00000006, 0x00000021, - 0x00000020, 0x000500b4, 0x00000023, 0x00000024, 0x00000021, 0x00000022, 0x000300f7, 0x00000026, - 0x00000000, 0x000400fa, 0x00000024, 0x00000025, 0x00000026, 0x000200f8, 0x00000025, 0x000100fc, - 0x000200f8, 0x00000026, 0x000100fd, 0x00010038, - }, + const shader_spirv = &[_]u32{ + 0x07230203, 0x00010000, 0x00110000, 0x0000006f, 0x00000000, 0x00020011, 0x00000001, 0x0006000b, + 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001, + 0x0008000f, 0x00000004, 0x00000002, 0x6e69616d, 0x00000000, 0x00000003, 0x00000004, 0x00000005, + 0x0010000f, 0x00000000, 0x00000006, 0x6e69616d, 0x00000000, 0x00000007, 0x00000008, 0x00000009, + 0x0000000a, 0x0000000b, 0x0000000c, 0x0000000d, 0x0000000e, 0x0000000f, 0x00000010, 0x00000011, + 0x00030010, 0x00000002, 0x00000007, 0x00030003, 0x00000002, 0x000001ae, 0x00030003, 0x00000002, + 0x000001ae, 0x00040005, 0x00000002, 0x6e69616d, 0x00000000, 0x00040005, 0x00000003, 0x65786574, + 0x0000006c, 0x00030005, 0x00000012, 0x00786574, 0x00030005, 0x00000013, 0x00706d73, 0x00030005, + 0x00000004, 0x00007675, 0x00040005, 0x00000005, 0x6f6c6f63, 0x00000072, 0x00040005, 0x00000006, + 0x6e69616d, 0x00000000, 0x00060005, 0x00000014, 0x6c726f77, 0x6f705f64, 0x69746973, 0x00006e6f, + 0x00060005, 0x00000007, 0x74736e69, 0x65636e61, 0x69726f5f, 0x006e6967, 0x00040005, 0x00000008, + 0x6873656d, 0x0079785f, 0x00060005, 0x00000009, 0x74736e69, 0x65636e61, 0x6162785f, 0x00736973, + 0x00060005, 0x0000000a, 0x74736e69, 0x65636e61, 0x6162795f, 0x00736973, 0x00070005, 0x00000015, + 0x6a6f7270, 0x65746365, 0x6f705f64, 0x69746973, 0x00006e6f, 0x00050005, 0x00000016, 0x6a6f7250, + 0x69746365, 0x00006e6f, 0x00080006, 0x00000016, 0x00000000, 0x6a6f7270, 0x69746365, 0x6d5f6e6f, + 0x69727461, 0x00000078, 0x00030005, 0x00000017, 0x00000000, 0x00060005, 0x00000018, 0x505f6c67, + 0x65567265, 0x78657472, 0x00000000, 0x00060006, 0x00000018, 0x00000000, 0x505f6c67, 0x7469736f, + 0x006e6f69, 0x00070006, 0x00000018, 0x00000001, 0x505f6c67, 0x746e696f, 0x657a6953, 0x00000000, + 0x00070006, 0x00000018, 0x00000002, 0x435f6c67, 0x4470696c, 0x61747369, 0x0065636e, 0x00030005, + 0x0000000b, 0x00000000, 0x00060005, 0x0000000c, 0x74736e69, 0x65636e61, 0x7065645f, 0x00006874, + 0x00040005, 0x0000000d, 0x6f6c6f63, 0x00000072, 0x00060005, 0x0000000e, 0x74736e69, 0x65636e61, + 0x6e69745f, 0x00000074, 0x00050005, 0x00000019, 0x74636572, 0x736f705f, 0x00000000, 0x00060005, + 0x0000000f, 0x74736e69, 0x65636e61, 0x6365725f, 0x00000074, 0x00050005, 0x0000001a, 0x74636572, + 0x7a69735f, 0x00000065, 0x00030005, 0x00000010, 0x00007675, 0x00040005, 0x00000011, 0x6873656d, + 0x0076755f, 0x00040047, 0x00000003, 0x0000001e, 0x00000000, 0x00040047, 0x00000012, 0x00000022, + 0x00000000, 0x00040047, 0x00000012, 0x00000021, 0x00000000, 0x00040047, 0x00000013, 0x00000022, + 0x00000000, 0x00040047, 0x00000013, 0x00000021, 0x00000000, 0x00040047, 0x00000004, 0x0000001e, + 0x00000001, 0x00040047, 0x00000005, 0x0000001e, 0x00000000, 0x00040047, 0x00000007, 0x0000001e, + 0x00000004, 0x00040047, 0x00000008, 0x0000001e, 0x00000000, 0x00040047, 0x00000009, 0x0000001e, + 0x00000002, 0x00040047, 0x0000000a, 0x0000001e, 0x00000003, 0x00040048, 0x00000016, 0x00000000, + 0x00000005, 0x00050048, 0x00000016, 0x00000000, 0x00000023, 0x00000000, 0x00050048, 0x00000016, + 0x00000000, 0x00000007, 0x00000010, 0x00030047, 0x00000016, 0x00000002, 0x00040047, 0x00000017, + 0x00000022, 0x00000000, 0x00040047, 0x00000017, 0x00000021, 0x00000000, 0x00050048, 0x00000018, + 0x00000000, 0x0000000b, 0x00000000, 0x00050048, 0x00000018, 0x00000001, 0x0000000b, 0x00000001, + 0x00050048, 0x00000018, 0x00000002, 0x0000000b, 0x00000003, 0x00030047, 0x00000018, 0x00000002, + 0x00040047, 0x0000000c, 0x0000001e, 0x00000006, 0x00040047, 0x0000000d, 0x0000001e, 0x00000000, + 0x00040047, 0x0000000e, 0x0000001e, 0x00000005, 0x00040047, 0x0000000f, 0x0000001e, 0x00000007, + 0x00040047, 0x00000010, 0x0000001e, 0x00000001, 0x00040047, 0x00000011, 0x0000001e, 0x00000001, + 0x00020013, 0x0000001b, 0x00030021, 0x0000001c, 0x0000001b, 0x00030016, 0x0000001d, 0x00000020, + 0x00040017, 0x0000001e, 0x0000001d, 0x00000004, 0x00040020, 0x0000001f, 0x00000003, 0x0000001e, + 0x0004003b, 0x0000001f, 0x00000003, 0x00000003, 0x00090019, 0x00000020, 0x0000001d, 0x00000001, + 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x00040020, 0x00000021, 0x00000000, + 0x00000020, 0x0004003b, 0x00000021, 0x00000012, 0x00000000, 0x0002001a, 0x00000022, 0x00040020, + 0x00000023, 0x00000000, 0x00000022, 0x0004003b, 0x00000023, 0x00000013, 0x00000000, 0x0003001b, + 0x00000024, 0x00000020, 0x00040017, 0x00000025, 0x0000001d, 0x00000002, 0x00040020, 0x00000026, + 0x00000001, 0x00000025, 0x0004003b, 0x00000026, 0x00000004, 0x00000001, 0x00040020, 0x00000027, + 0x00000001, 0x0000001e, 0x0004003b, 0x00000027, 0x00000005, 0x00000001, 0x00040015, 0x00000028, + 0x00000020, 0x00000000, 0x0004002b, 0x00000028, 0x00000029, 0x00000003, 0x00040020, 0x0000002a, + 0x00000003, 0x0000001d, 0x0004002b, 0x0000001d, 0x0000002b, 0x00000000, 0x00020014, 0x0000002c, + 0x00040020, 0x0000002d, 0x00000007, 0x00000025, 0x0004003b, 0x00000026, 0x00000007, 0x00000001, + 0x0004003b, 0x00000026, 0x00000008, 0x00000001, 0x0004002b, 0x00000028, 0x0000002e, 0x00000000, + 0x00040020, 0x0000002f, 0x00000001, 0x0000001d, 0x0004003b, 0x00000026, 0x00000009, 0x00000001, + 0x0004002b, 0x00000028, 0x00000030, 0x00000001, 0x0004003b, 0x00000026, 0x0000000a, 0x00000001, + 0x00040018, 0x00000031, 0x0000001e, 0x00000004, 0x0003001e, 0x00000016, 0x00000031, 0x00040020, + 0x00000032, 0x00000002, 0x00000016, 0x0004003b, 0x00000032, 0x00000017, 0x00000002, 0x00040015, + 0x00000033, 0x00000020, 0x00000001, 0x0004002b, 0x00000033, 0x00000034, 0x00000000, 0x00040020, + 0x00000035, 0x00000002, 0x00000031, 0x0004002b, 0x0000001d, 0x00000036, 0x00000000, 0x0004002b, + 0x0000001d, 0x00000037, 0x3f800000, 0x0004001c, 0x00000038, 0x0000001d, 0x00000030, 0x0005001e, + 0x00000018, 0x0000001e, 0x0000001d, 0x00000038, 0x00040020, 0x00000039, 0x00000003, 0x00000018, + 0x0004003b, 0x00000039, 0x0000000b, 0x00000003, 0x0004003b, 0x0000002f, 0x0000000c, 0x00000001, + 0x0004003b, 0x0000001f, 0x0000000d, 0x00000003, 0x0004003b, 0x00000027, 0x0000000e, 0x00000001, + 0x0004003b, 0x00000027, 0x0000000f, 0x00000001, 0x00040020, 0x0000003a, 0x00000003, 0x00000025, + 0x0004003b, 0x0000003a, 0x00000010, 0x00000003, 0x0004003b, 0x00000026, 0x00000011, 0x00000001, + 0x00050036, 0x0000001b, 0x00000002, 0x00000000, 0x0000001c, 0x000200f8, 0x0000003b, 0x0004003d, + 0x00000020, 0x0000003c, 0x00000012, 0x0004003d, 0x00000022, 0x0000003d, 0x00000013, 0x00050056, + 0x00000024, 0x0000003e, 0x0000003c, 0x0000003d, 0x0004003d, 0x00000025, 0x0000003f, 0x00000004, + 0x00050057, 0x0000001e, 0x00000040, 0x0000003e, 0x0000003f, 0x0004003d, 0x0000001e, 0x00000041, + 0x00000005, 0x00050085, 0x0000001e, 0x00000042, 0x00000040, 0x00000041, 0x0003003e, 0x00000003, + 0x00000042, 0x00050041, 0x0000002a, 0x00000043, 0x00000003, 0x00000029, 0x0004003d, 0x0000001d, + 0x00000044, 0x00000043, 0x000500b4, 0x0000002c, 0x00000045, 0x00000044, 0x0000002b, 0x000300f7, + 0x00000046, 0x00000000, 0x000400fa, 0x00000045, 0x00000047, 0x00000046, 0x000200f8, 0x00000047, + 0x000100fc, 0x000200f8, 0x00000046, 0x000100fd, 0x00010038, 0x00050036, 0x0000001b, 0x00000006, + 0x00000000, 0x0000001c, 0x000200f8, 0x00000048, 0x0004003b, 0x0000002d, 0x00000014, 0x00000007, + 0x0004003b, 0x0000002d, 0x00000015, 0x00000007, 0x0004003b, 0x0000002d, 0x00000019, 0x00000007, + 0x0004003b, 0x0000002d, 0x0000001a, 0x00000007, 0x0004003d, 0x00000025, 0x00000049, 0x00000007, + 0x00050041, 0x0000002f, 0x0000004a, 0x00000008, 0x0000002e, 0x0004003d, 0x0000001d, 0x0000004b, + 0x0000004a, 0x0004003d, 0x00000025, 0x0000004c, 0x00000009, 0x0005008e, 0x00000025, 0x0000004d, + 0x0000004c, 0x0000004b, 0x00050081, 0x00000025, 0x0000004e, 0x00000049, 0x0000004d, 0x00050041, + 0x0000002f, 0x0000004f, 0x00000008, 0x00000030, 0x0004003d, 0x0000001d, 0x00000050, 0x0000004f, + 0x0004003d, 0x00000025, 0x00000051, 0x0000000a, 0x0005008e, 0x00000025, 0x00000052, 0x00000051, + 0x00000050, 0x00050081, 0x00000025, 0x00000053, 0x0000004e, 0x00000052, 0x0003003e, 0x00000014, + 0x00000053, 0x00050041, 0x00000035, 0x00000054, 0x00000017, 0x00000034, 0x0004003d, 0x00000031, + 0x00000055, 0x00000054, 0x0004003d, 0x00000025, 0x00000056, 0x00000014, 0x00050051, 0x0000001d, + 0x00000057, 0x00000056, 0x00000000, 0x00050051, 0x0000001d, 0x00000058, 0x00000056, 0x00000001, + 0x00070050, 0x0000001e, 0x00000059, 0x00000057, 0x00000058, 0x00000036, 0x00000037, 0x00050091, + 0x0000001e, 0x0000005a, 0x00000055, 0x00000059, 0x0007004f, 0x00000025, 0x0000005b, 0x0000005a, + 0x0000005a, 0x00000000, 0x00000001, 0x0003003e, 0x00000015, 0x0000005b, 0x0004003d, 0x00000025, + 0x0000005c, 0x00000015, 0x0004003d, 0x0000001d, 0x0000005d, 0x0000000c, 0x00050051, 0x0000001d, + 0x0000005e, 0x0000005c, 0x00000000, 0x00050051, 0x0000001d, 0x0000005f, 0x0000005c, 0x00000001, + 0x00070050, 0x0000001e, 0x00000060, 0x0000005e, 0x0000005f, 0x0000005d, 0x00000037, 0x00050041, + 0x0000001f, 0x00000061, 0x0000000b, 0x00000034, 0x0003003e, 0x00000061, 0x00000060, 0x0004003d, + 0x0000001e, 0x00000062, 0x0000000e, 0x0003003e, 0x0000000d, 0x00000062, 0x0004003d, 0x0000001e, + 0x00000063, 0x0000000f, 0x0007004f, 0x00000025, 0x00000064, 0x00000063, 0x00000063, 0x00000000, + 0x00000001, 0x0003003e, 0x00000019, 0x00000064, 0x0004003d, 0x0000001e, 0x00000065, 0x0000000f, + 0x0007004f, 0x00000025, 0x00000066, 0x00000065, 0x00000065, 0x00000002, 0x00000003, 0x0004003d, + 0x0000001e, 0x00000067, 0x0000000f, 0x0007004f, 0x00000025, 0x00000068, 0x00000067, 0x00000067, + 0x00000000, 0x00000001, 0x00050083, 0x00000025, 0x00000069, 0x00000066, 0x00000068, 0x0003003e, + 0x0000001a, 0x00000069, 0x0004003d, 0x00000025, 0x0000006a, 0x00000019, 0x0004003d, 0x00000025, + 0x0000006b, 0x00000011, 0x0004003d, 0x00000025, 0x0000006c, 0x0000001a, 0x00050085, 0x00000025, + 0x0000006d, 0x0000006b, 0x0000006c, 0x00050081, 0x00000025, 0x0000006e, 0x0000006a, 0x0000006d, + 0x0003003e, 0x00000010, 0x0000006e, 0x000100fd, 0x00010038, }; }; From 842dbd56cf45e382a7de2e9737a20af010e15f91 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sat, 6 Jul 2024 03:25:09 +0100 Subject: [PATCH 04/30] Add runtime shader reflection support --- .vscode/launch.json | 2 +- src/coral/ascii.zig | 6 +- src/coral/io.zig | 32 ++-- src/coral/utf8.zig | 59 +++++-- src/ona/gfx/device.zig | 76 ++++---- src/ona/gfx/spirv.zig | 384 +++++++++++++++++++++++++++++------------ 6 files changed, 385 insertions(+), 174 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 89d52ec..e0928e0 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "target": "${workspaceRoot}/zig-out/bin/main", "cwd": "${workspaceRoot}/debug/", - "valuesFormatting": "parseText", + "valuesFormatting": "prettyPrinters", "preLaunchTask": "Build All" }, ] diff --git a/src/coral/ascii.zig b/src/coral/ascii.zig index 2d4e26a..0339e7d 100644 --- a/src/coral/ascii.zig +++ b/src/coral/ascii.zig @@ -115,7 +115,7 @@ pub const DecimalFormat = struct { } } - pub fn print(self: DecimalFormat, writer: io.Writer, value: anytype) io.PrintError!void { + pub fn format(self: DecimalFormat, writer: io.Writer, value: anytype) io.PrintError!void { if (value == 0) { return io.print(writer, switch (self.positive_prefix) { .none => "0", @@ -178,13 +178,13 @@ pub const HexadecimalFormat = struct { positive_prefix: enum {none, plus, space} = .none, casing: enum {lower, upper} = .lower, - const default = HexadecimalFormat{ + pub const default = HexadecimalFormat{ .delimiter = "", .positive_prefix = .none, .casing = .lower, }; - pub fn print(self: HexadecimalFormat, writer: io.Writer, value: anytype) ?usize { + pub fn format(self: HexadecimalFormat, writer: io.Writer, value: anytype) io.Error!void { // TODO: Implement. _ = self; _ = writer; diff --git a/src/coral/io.zig b/src/coral/io.zig index 383825e..545b64f 100644 --- a/src/coral/io.zig +++ b/src/coral/io.zig @@ -108,13 +108,23 @@ pub fn Generator(comptime Output: type, comptime input_types: []const type) type }; } -pub const PrintError = Error || error { - IncompleteWrite, +pub const NullWritable = struct { + written: usize = 0, + + pub fn write(self: *NullWritable, buffer: []const Byte) Error!usize { + self.written += buffer.len; + + return buffer.len; + } + + pub fn writer(self: *NullWritable) Writer { + return Writer.bind(NullWritable, self, write); + } }; -pub const Reader = Generator(Error!usize, &.{[]coral.Byte}); +pub const Reader = Generator(Error!usize, &.{[]Byte}); -pub const Writer = Generator(Error!usize, &.{[]const coral.Byte}); +pub const Writer = Generator(Error!usize, &.{[]const Byte}); pub fn alloc_read(input: coral.io.Reader, allocator: std.mem.Allocator) []coral.Byte { const buffer = coral.Stack(coral.Byte){.allocator = allocator}; @@ -138,12 +148,6 @@ pub fn bytes_of(value: anytype) []const Byte { }; } -pub fn print(writer: Writer, utf8: []const u8) PrintError!void { - if (try writer.yield(.{utf8}) != utf8.len) { - return error.IncompleteWrite; - } -} - pub fn skip_n(input: Reader, distance: u64) Error!void { var buffer = @as([512]coral.Byte, undefined); var remaining = distance; @@ -159,7 +163,7 @@ pub fn skip_n(input: Reader, distance: u64) Error!void { } } -pub fn slice_sentineled(comptime sen: anytype, ptr: [*:sen]const @TypeOf(sen)) [:sen]const @TypeOf(sen) { +pub fn slice_sentineled(comptime Sentinel: type, comptime sen: Sentinel, ptr: [*:sen]const Sentinel) [:sen]const Sentinel { var len = @as(usize, 0); while (ptr[len] != sen) { @@ -206,3 +210,9 @@ pub fn stream_n(input: Reader, output: Writer, limit: usize) Error!usize { remaining -= read; } } + +pub fn write_all(writer: Writer, utf8: []const u8) Error!void { + if (try writer.yield(.{utf8}) != utf8.len) { + return error.UnavailableResource; + } +} diff --git a/src/coral/utf8.zig b/src/coral/utf8.zig index 2d02b5c..4992107 100644 --- a/src/coral/utf8.zig +++ b/src/coral/utf8.zig @@ -19,15 +19,54 @@ pub fn alloc_formatted(allocator: std.mem.Allocator, comptime format: []const u8 return buffer.to_allocation(formatted_len, 0); } -fn count_formatted(comptime format: []const u8, args: anytype) usize { - var count = io.defaultWritable{}; +pub fn count_formatted(comptime format: []const u8, args: anytype) usize { + var count = io.NullWritable{}; - print_formatted(count.writer(), format, args) catch unreachable; + write_formatted(count.writer(), format, args) catch unreachable; return count.written; } -pub fn print_formatted(writer: io.Writer, comptime format: []const u8, args: anytype) io.PrintError!void { +pub fn print_formatted(buffer: [:0]coral.io.Byte, comptime format: []const u8, args: anytype) io.Error![:0]u8 { + const Seekable = struct { + buffer: []coral.io.Byte, + cursor: usize, + + const Self = @This(); + + fn write(self: *Self, input: []const coral.io.Byte) io.Error!usize { + const range = @min(input.len, self.buffer.len - self.cursor); + const tail = self.cursor + range; + + @memcpy(self.buffer[self.cursor .. tail], input); + + self.cursor = tail; + + return range; + } + }; + + const len = count_formatted(format, args); + + if (len > buffer.len) { + return error.UnavailableResource; + } + + var seekable = Seekable{ + .buffer = buffer, + .cursor = 0, + }; + + try write_formatted(coral.io.Writer.bind(Seekable, &seekable, Seekable.write), format, args); + + if (buffer.len < len) { + buffer[len] = 0; + } + + return buffer[0 .. len:0]; +} + +pub fn write_formatted(writer: io.Writer, comptime format: []const u8, args: anytype) io.Error!void { switch (@typeInfo(@TypeOf(args))) { .Struct => |arguments_struct| { comptime var arg_index = 0; @@ -67,7 +106,7 @@ pub fn print_formatted(writer: io.Writer, comptime format: []const u8, args: any @compileError("format specifiers cannot be named when using a tuple struct"); } - try io.print(writer, format[head .. (tail - 1)]); + try io.write_all(writer, format[head .. (tail - 1)]); head = tail; tail += 1; @@ -93,14 +132,14 @@ pub fn print_formatted(writer: io.Writer, comptime format: []const u8, args: any } } - try io.print(writer, format[head .. ]); + try io.write_all(writer, format[head .. ]); }, else => @compileError("`arguments` must be a struct type"), } } -noinline fn print_formatted_value(writer: io.Writer, value: anytype) io.PrintError!void { +noinline fn print_formatted_value(writer: io.Writer, value: anytype) io.Error!void { const Value = @TypeOf(value); return switch (@typeInfo(Value)) { @@ -109,9 +148,9 @@ noinline fn print_formatted_value(writer: io.Writer, value: anytype) io.PrintErr .Enum => io.print(writer, @tagName(value)), .Pointer => |pointer| switch (pointer.size) { - .Many, .C => ascii.HexadecimalFormat.default.print(writer, @intFromPtr(value)), - .One => if (pointer.child == []const u8) io.print(writer, *value) else ascii.HexadecimalFormat.default.print(writer, @intFromPtr(value)), - .Slice => if (pointer.child == u8) io.print(writer, value) else @compileError(unformattableMessage(Value)), + .Many, .C => ascii.HexadecimalFormat.default.format(writer, @intFromPtr(value)), + .One => if (pointer.child == []const u8) io.write_all(writer, *value) else ascii.HexadecimalFormat.default.print(writer, @intFromPtr(value)), + .Slice => if (pointer.child == u8) io.write_all(writer, value) else @compileError(unformattableMessage(Value)), }, else => @compileError(unformattableMessage(Value)), diff --git a/src/ona/gfx/device.zig b/src/ona/gfx/device.zig index c43619b..60a2f73 100644 --- a/src/ona/gfx/device.zig +++ b/src/ona/gfx/device.zig @@ -380,7 +380,7 @@ const Loop = struct { ext.SDL_GL_DeleteContext(context); } - var rendering_2d = try Rendering2D.init(coral.heap.allocator); + var rendering_2d = try Rendering2D.init(); defer rendering_2d.deinit(); @@ -535,6 +535,12 @@ const Rendering2D = struct { const mesh_2d = resource_cast(frame.mesh.?).payload.mesh_2d; + const texture_image, const texture_sampler = switch (resource_cast(frame.texture.?).payload) { + .texture => |texture| .{texture.image, texture.sampler}, + .render_target => |render_target| .{render_target.color_image, render_target.sampler}, + else => unreachable, + }; + var bindings = sokol.gfx.Bindings{ .vertex_buffers = get: { var buffers = [_]sokol.gfx.Buffer{.{}} ** 8; @@ -546,44 +552,40 @@ const Rendering2D = struct { .index_buffer = mesh_2d.index_buffer, - .fs = switch (resource_cast(frame.texture.?).payload) { - .texture => |texture| .{ - .images = get: { - var images = [_]sokol.gfx.Image{.{}} ** 12; + .fs = .{ + .images = get: { + var images = [_]sokol.gfx.Image{.{}} ** 12; - images[0] = texture.image; + images[0] = texture_image; - break: get images; - }, - - .samplers = get: { - var samplers = [_]sokol.gfx.Sampler{.{}} ** 8; - - samplers[0] = texture.sampler; - - break: get samplers; - }, + break: get images; }, - .render_target => |render_target| .{ - .images = get: { - var images = [_]sokol.gfx.Image{.{}} ** 12; + .samplers = get: { + var samplers = [_]sokol.gfx.Sampler{.{}} ** 8; - images[0] = render_target.color_image; + samplers[0] = texture_sampler; - break: get images; - }, + break: get samplers; + }, + }, - .samplers = get: { - var samplers = [_]sokol.gfx.Sampler{.{}} ** 8; + .vs = .{ + .images = get: { + var images = [_]sokol.gfx.Image{.{}} ** 12; - samplers[0] = render_target.sampler; + images[0] = texture_image; - break: get samplers; - }, + break: get images; }, - else => unreachable, + .samplers = get: { + var samplers = [_]sokol.gfx.Sampler{.{}} ** 8; + + samplers[0] = texture_sampler; + + break: get samplers; + }, }, }; @@ -602,7 +604,7 @@ const Rendering2D = struct { } } - fn init(allocator: std.mem.Allocator) spirv.CompileError!Rendering2D { + fn init() spirv.Error!Rendering2D { sokol.gfx.setup(.{ .environment = .{ .defaults = .{ @@ -617,17 +619,15 @@ const Rendering2D = struct { }, }); - var arena = std.heap.ArenaAllocator.init(allocator); + var spirv_unit = try spirv.Unit.init(); - defer arena.deinit(); + defer spirv_unit.deinit(); - const shader_desc = try spirv.compile(&arena, .{ - .fragment_spirv = shader_spirv[0 ..], - .vertex_spirv = shader_spirv[0 ..], - }); + try spirv_unit.compile(shader_spirv[0 ..], .vertex); + try spirv_unit.compile(shader_spirv[0 ..], .fragment); - std.log.info("{s}\n", .{shader_desc.fs.source}); - std.log.info("{s}", .{shader_desc.vs.source}); + std.log.info("{s}\n", .{spirv_unit.shader_desc.fs.source}); + std.log.info("{s}", .{spirv_unit.shader_desc.vs.source}); return .{ .batching_pipeline = sokol.gfx.makePipeline(.{ @@ -689,7 +689,7 @@ const Rendering2D = struct { }, }, - .shader = sokol.gfx.makeShader(shader_desc), + .shader = sokol.gfx.makeShader(spirv_unit.shader_desc), .index_type = .UINT16, }), diff --git a/src/ona/gfx/spirv.zig b/src/ona/gfx/spirv.zig index 857f4c2..9794360 100644 --- a/src/ona/gfx/spirv.zig +++ b/src/ona/gfx/spirv.zig @@ -8,123 +8,285 @@ const sokol = @import("sokol"); const std = @import("std"); -pub const CompileError = std.mem.Allocator.Error || error { +pub const Error = std.mem.Allocator.Error || error { UnsupportedTarget, - InvalidSpirV, - UnsupportedSpirv, - UnknownFailure, + InvalidSPIRV, + UnsupportedSPIRV, }; -pub const Sources = struct { - vertex_spirv: []const u32, - fragment_spirv: []const u32, +pub const Unit = struct { + context: ext.spvc_context, + shader_desc: sokol.gfx.ShaderDesc, + attrs_used: u32 = 0, + + pub fn compile(self: *Unit, spirv: []const u32, stage: Stage) Error!void { + const execution_model, const stage_desc = switch (stage) { + .vertex => .{ext.SpvExecutionModelVertex, &self.shader_desc.vs}, + .fragment => .{ext.SpvExecutionModelFragment, &self.shader_desc.fs}, + }; + + const Backend = struct { + target: ext.spvc_backend, + option_values: []const struct {ext.spvc_compiler_option, c_uint}, + }; + + const backend: Backend = switch (sokol.gfx.queryBackend()) { + .GLCORE => .{ + .target = ext.SPVC_BACKEND_GLSL, + + .option_values = &.{ + .{ext.SPVC_COMPILER_OPTION_GLSL_VERSION, 430}, + .{ext.SPVC_COMPILER_OPTION_GLSL_ES, @intFromBool(false)}, + .{ext.SPVC_COMPILER_OPTION_GLSL_VULKAN_SEMANTICS, @intFromBool(false)}, + .{ext.SPVC_COMPILER_OPTION_GLSL_EMIT_UNIFORM_BUFFER_AS_PLAIN_UNIFORMS, @intFromBool(true)}, + }, + }, + + else => @panic("Unimplemented"), + }; + + const compiler = parse_and_configure: { + var parsed_ir: ext.spvc_parsed_ir = null; + + try to_error(ext.spvc_context_parse_spirv(self.context, spirv.ptr, spirv.len, &parsed_ir)); + + var compiler: ext.spvc_compiler = null; + + try to_error(ext.spvc_context_create_compiler(self.context, backend.target, parsed_ir, ext.SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, &compiler)); + try to_error(ext.spvc_compiler_build_combined_image_samplers(compiler)); + + var combined_image_samplers: []const ext.spvc_combined_image_sampler = &.{}; + + try to_error(ext.spvc_compiler_get_combined_image_samplers(compiler, @ptrCast(&combined_image_samplers.ptr), &combined_image_samplers.len)); + + var binding: u32 = 0; + + for (combined_image_samplers) |combined_image_sampler| { + var name_buffer = [_:0]u8{0} ** 255; + + const name = coral.utf8.print_formatted(&name_buffer, "{image_name}_{sampler_name}", .{ + .image_name = coral.io.slice_sentineled(u8, 0, ext.spvc_compiler_get_name(compiler, combined_image_sampler.image_id)), + .sampler_name = coral.io.slice_sentineled(u8, 0, ext.spvc_compiler_get_name(compiler, combined_image_sampler.sampler_id)), + }) catch { + return error.InvalidSPIRV; + }; + + ext.spvc_compiler_set_name(compiler, combined_image_sampler.combined_id, name); + ext.spvc_compiler_set_decoration(compiler, combined_image_sampler.combined_id, ext.SpvDecorationBinding, binding); + + binding += 1; + } + + break: parse_and_configure compiler; + }; + + try to_error(ext.spvc_compiler_set_entry_point(compiler, stage_desc.entry, @intCast(execution_model))); + + const resources = create: { + var resources: ext.spvc_resources = null; + + try to_error(ext.spvc_compiler_create_shader_resources(compiler, &resources)); + + break: create resources; + }; + + try reflect_uniform_blocks(compiler, resources, stage_desc); + try reflect_image_samplers(compiler, resources, stage_desc); + + try to_error(ext.spvc_compiler_install_compiler_options(compiler, create: { + var options: ext.spvc_compiler_options = null; + + try to_error(ext.spvc_compiler_create_compiler_options(compiler, &options)); + + for (backend.option_values) |option_value| { + const entry, const value = option_value; + + try to_error(ext.spvc_compiler_options_set_uint(options, entry, value)); + } + + break: create options; + })); + + try to_error(ext.spvc_compiler_compile(compiler, @ptrCast(&stage_desc.source))); + } + + pub fn deinit(self: *Unit) void { + ext.spvc_context_destroy(self.context); + + self.* = undefined; + } + + pub fn init() std.mem.Allocator.Error!Unit { + var context: ext.spvc_context = null; + + if (ext.spvc_context_create(&context) != ext.SPVC_SUCCESS) { + return error.OutOfMemory; + } + + errdefer ext.spvc_context_destroy(context); + + ext.spvc_context_set_error_callback(context, log_context_errors, null); + + return .{ + .context = context, + + .shader_desc = .{ + .vs = .{.entry = "main"}, + .fs = .{.entry = "main"}, + }, + }; + } }; -pub fn compile(arena: *std.heap.ArenaAllocator, sources: Sources) CompileError!sokol.gfx.ShaderDesc { - var context = @as(ext.spvc_context, null); +pub const Stage = enum { + fragment, + vertex, +}; - try switch (ext.spvc_context_create(&context)) { - ext.SPVC_ERROR_OUT_OF_MEMORY => error.OutOfMemory, - ext.SPVC_SUCCESS => {}, - else => error.UnknownFailure, - }; - - ext.spvc_context_set_error_callback(context, log_errors, null); - - defer ext.spvc_context_destroy(context); - - const arena_allocator = arena.allocator(); - const shader_source_sentinel = @as(u8, 0); - - return .{ - .vs = .{ - .source = try arena_allocator.dupeZ(u8, coral.io.slice_sentineled( - shader_source_sentinel, - @ptrCast(try compile_shader(context, ext.SpvExecutionModelVertex, sources.vertex_spirv)))), - }, - - .fs = .{ - .source = try arena_allocator.dupeZ(u8, coral.io.slice_sentineled( - shader_source_sentinel, - @ptrCast(try compile_shader(context, ext.SpvExecutionModelFragment, sources.fragment_spirv)))), - }, - }; -} - -fn compile_shader(context: ext.spvc_context, model: ext.SpvExecutionModel, spirv: []const u32) CompileError![*]const u8 { - var parsed_ir = @as(ext.spvc_parsed_ir, null); - - try switch (ext.spvc_context_parse_spirv(context, spirv.ptr, spirv.len, &parsed_ir)) { - ext.SPVC_ERROR_OUT_OF_MEMORY => error.OutOfMemory, - ext.SPVC_ERROR_INVALID_SPIRV => error.InvalidSpirV, - ext.SPVC_SUCCESS => {}, - else => error.UnknownFailure, - }; - - var compiler = @as(ext.spvc_compiler, null); - - try switch (ext.spvc_context_create_compiler(context, ext.SPVC_BACKEND_GLSL, parsed_ir, ext.SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, &compiler)) { - ext.SPVC_ERROR_OUT_OF_MEMORY => error.OutOfMemory, - ext.SPVC_ERROR_INVALID_ARGUMENT => error.UnsupportedTarget, - ext.SPVC_SUCCESS => {}, - else => error.UnknownFailure, - }; - - try switch (ext.spvc_compiler_build_combined_image_samplers(compiler)) { - ext.SPVC_ERROR_UNSUPPORTED_SPIRV => error.UnsupportedSpirv, - ext.SPVC_SUCCESS => {}, - else => error.UnknownFailure, - }; - - try switch (ext.spvc_compiler_set_entry_point(compiler, "main", model)) { - ext.SPVC_SUCCESS => {}, - else => error.UnknownFailure, - }; - - var options = @as(ext.spvc_compiler_options, null); - - try switch (ext.spvc_compiler_create_compiler_options(compiler, &options)) { - ext.SPVC_ERROR_OUT_OF_MEMORY => error.OutOfMemory, - ext.SPVC_SUCCESS => {}, - else => error.UnknownFailure, - }; - - try switch (ext.spvc_compiler_options_set_uint(options, ext.SPVC_COMPILER_OPTION_GLSL_VERSION, 430)) { - ext.SPVC_ERROR_INVALID_ARGUMENT => error.InvalidSpirV, - ext.SPVC_SUCCESS => {}, - else => error.UnknownFailure, - }; - - try switch (ext.spvc_compiler_options_set_bool(options, ext.SPVC_COMPILER_OPTION_GLSL_ES, @intFromBool(false))) { - ext.SPVC_ERROR_INVALID_ARGUMENT => error.InvalidSpirV, - ext.SPVC_SUCCESS => {}, - else => error.UnknownFailure, - }; - - try switch (ext.spvc_compiler_options_set_bool(options, ext.SPVC_COMPILER_OPTION_GLSL_VULKAN_SEMANTICS, @intFromBool(false))) { - ext.SPVC_ERROR_INVALID_ARGUMENT => error.InvalidSpirV, - ext.SPVC_SUCCESS => {}, - else => error.UnknownFailure, - }; - - try switch (ext.spvc_compiler_install_compiler_options(compiler, options)) { - ext.SPVC_SUCCESS => {}, - else => error.UnknownFailure, - }; - - var source = @as([*]const u8, undefined); - - try switch (ext.spvc_compiler_compile(compiler, @ptrCast(&source))) { - ext.SPVC_ERROR_OUT_OF_MEMORY => error.OutOfMemory, - ext.SPVC_ERROR_UNSUPPORTED_SPIRV => error.UnsupportedSpirv, - ext.SPVC_SUCCESS => {}, - else => error.UnknownFailure, - }; - - return source; -} - -fn log_errors(userdata: ?*anyopaque, error_message: [*c]const u8) callconv(.C) void { +fn log_context_errors(userdata: ?*anyopaque, error_message: [*c]const u8) callconv(.C) void { std.debug.assert(userdata == null); std.log.err("{s}", .{error_message}); } + +fn reflect_image_samplers(compiler: ext.spvc_compiler, resources: ext.spvc_resources, stage: *sokol.gfx.ShaderStageDesc) Error!void { + var reflected_sampled_images: []const ext.spvc_reflected_resource = &.{}; + + try to_error(ext.spvc_resources_get_resource_list_for_type( + resources, + ext.SPVC_RESOURCE_TYPE_SAMPLED_IMAGE, + @ptrCast(&reflected_sampled_images.ptr), + &reflected_sampled_images.len, + )); + + if (reflected_sampled_images.len > stage.image_sampler_pairs.len) { + return error.UnsupportedSPIRV; + } + + for (0 .. reflected_sampled_images.len, reflected_sampled_images) |i, reflected_sampled_image| { + const sampled_image_type = ext.spvc_compiler_get_type_handle(compiler, reflected_sampled_image.type_id); + + if (ext.spvc_type_get_basetype(sampled_image_type) != ext.SPVC_BASETYPE_SAMPLED_IMAGE) { + return error.InvalidSPIRV; + } + + stage.images[i] = .{ + .multisampled = ext.spvc_type_get_image_multisampled(sampled_image_type) != 0, + + .image_type = try switch (ext.spvc_type_get_image_dimension(sampled_image_type)) { + ext.SpvDim2D => sokol.gfx.ImageType._2D, + else => error.InvalidSPIRV, + }, + + .sample_type = try switch (ext.spvc_type_get_basetype(ext.spvc_compiler_get_type_handle(compiler, ext.spvc_type_get_image_sampled_type(sampled_image_type)))) { + ext.SPVC_BASETYPE_FP32 => sokol.gfx.ImageSampleType.FLOAT, + else => error.InvalidSPIRV, + }, + + .used = true, + }; + + stage.samplers[i] = .{ + .sampler_type = .DEFAULT, + .used = true, + }; + + stage.image_sampler_pairs[i] = .{ + .glsl_name = ext.spvc_compiler_get_name(compiler, reflected_sampled_image.id), + .image_slot = @intCast(i), + .sampler_slot = @intCast(i), + .used = true, + }; + } +} + +fn reflect_uniform_blocks(compiler: ext.spvc_compiler, resources: ext.spvc_resources, stage: *sokol.gfx.ShaderStageDesc) Error!void { + var reflected_uniform_buffers: []const ext.spvc_reflected_resource = &.{}; + + try to_error(ext.spvc_resources_get_resource_list_for_type( + resources, + ext.SPVC_RESOURCE_TYPE_UNIFORM_BUFFER, + @ptrCast(&reflected_uniform_buffers.ptr), + &reflected_uniform_buffers.len, + )); + + if (reflected_uniform_buffers.len > stage.uniform_blocks.len) { + return error.UnsupportedSPIRV; + } + + for (stage.uniform_blocks[0 .. reflected_uniform_buffers.len], reflected_uniform_buffers) |*uniform_block, reflected_uniform_buffer| { + const uniform_buffer_type = ext.spvc_compiler_get_type_handle(compiler, reflected_uniform_buffer.type_id); + + if (ext.spvc_type_get_basetype(uniform_buffer_type) != ext.SPVC_BASETYPE_STRUCT) { + return error.InvalidSPIRV; + } + + const member_count = ext.spvc_type_get_num_member_types(uniform_buffer_type); + + if (member_count > uniform_block.uniforms.len) { + return error.UnsupportedSPIRV; + } + + try to_error(ext.spvc_compiler_get_declared_struct_size(compiler, uniform_buffer_type, &uniform_block.size)); + + var uniform_blocks_used: u32 = 0; + + while (uniform_blocks_used < member_count) : (uniform_blocks_used += 1) { + const member_type_id = ext.spvc_type_get_member_type(uniform_buffer_type, uniform_blocks_used); + const member_type = ext.spvc_compiler_get_type_handle(compiler, member_type_id); + + uniform_block.uniforms[uniform_blocks_used] = .{ + // .name = ext.spvc_compiler_get_member_name(compiler, ext.spvc_type_get_base_type_id(uniform_buffer_type), uniform_blocks_used), + + .array_count = @intCast(try switch (ext.spvc_type_get_num_array_dimensions(member_type)) { + 0 => 0, + + 1 => switch (ext.spvc_type_array_dimension_is_literal(member_type, 1) != 0) { + true => ext.spvc_type_get_array_dimension(member_type, 1), + false => error.InvalidSPIRV, + }, + + else => error.InvalidSPIRV, + }), + + .type = try switch (ext.spvc_type_get_basetype(member_type)) { + ext.SPVC_BASETYPE_INT32 => switch (ext.spvc_type_get_vector_size(member_type)) { + 1 => if (ext.spvc_type_get_columns(member_type) == 1) sokol.gfx.UniformType.INT else error.InvalidSPIRV, + 2 => if (ext.spvc_type_get_columns(member_type) == 1) sokol.gfx.UniformType.INT2 else error.InvalidSPIRV, + 3 => if (ext.spvc_type_get_columns(member_type) == 1) sokol.gfx.UniformType.INT3 else error.InvalidSPIRV, + 4 => if (ext.spvc_type_get_columns(member_type) == 1) sokol.gfx.UniformType.INT4 else error.InvalidSPIRV, + else => error.InvalidSPIRV, + }, + + ext.SPVC_BASETYPE_FP32 => switch (ext.spvc_type_get_vector_size(member_type)) { + 1 => if (ext.spvc_type_get_columns(member_type) == 1) sokol.gfx.UniformType.FLOAT else error.InvalidSPIRV, + 2 => if (ext.spvc_type_get_columns(member_type) == 1) sokol.gfx.UniformType.FLOAT2 else error.InvalidSPIRV, + 3 => if (ext.spvc_type_get_columns(member_type) == 1) sokol.gfx.UniformType.FLOAT3 else error.InvalidSPIRV, + + 4 => switch (ext.spvc_type_get_columns(member_type)) { + 1 => sokol.gfx.UniformType.FLOAT4, + 4 => sokol.gfx.UniformType.MAT4, + else => error.InvalidSPIRV, + }, + + else => error.InvalidSPIRV, + }, + + else => error.InvalidSPIRV, + }, + }; + + // uniform_block.uniforms[uniform_blocks_used].name = ext.spvc_compiler_get_member_name(compiler, ext.spvc_type_get_base_type_id(uniform_buffer_type), uniform_blocks_used); + } + } +} + +fn to_error(result: ext.spvc_result) Error!void { + return switch (result) { + ext.SPVC_SUCCESS => {}, + ext.SPVC_ERROR_INVALID_SPIRV => error.InvalidSPIRV, + ext.SPVC_ERROR_UNSUPPORTED_SPIRV => error.UnsupportedSPIRV, + ext.SPVC_ERROR_OUT_OF_MEMORY => error.OutOfMemory, + ext.SPVC_ERROR_INVALID_ARGUMENT, ext.SPVC_ERROR_INT_MAX => unreachable, + else => unreachable, + }; +} From e17d2b74ca4640183ceca5ba9cc741d3ff3c8f36 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sat, 6 Jul 2024 16:00:40 +0100 Subject: [PATCH 05/30] Fix coordinate systems to be consistent between backends --- src/ona/gfx.zig | 15 +++- src/ona/gfx/device.zig | 140 ++++++----------------------- src/ona/gfx/shaders/render_2d.frag | 5 +- src/ona/gfx/shaders/render_2d.vert | 18 ++-- src/ona/gfx/spirv.zig | 12 +++ 5 files changed, 55 insertions(+), 135 deletions(-) diff --git a/src/ona/gfx.zig b/src/ona/gfx.zig index 8c314ee..912b24d 100644 --- a/src/ona/gfx.zig +++ b/src/ona/gfx.zig @@ -62,10 +62,10 @@ pub const Assets = struct { .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}}, + .{.xy = .{-width, -height}, .uv = .{0, 1}}, + .{.xy = .{width, -height}, .uv = .{1, 1}}, + .{.xy = .{width, height}, .uv = .{1, 0}}, + .{.xy = .{-width, height}, .uv = .{0, 0}}, }, }, }); @@ -211,6 +211,13 @@ pub const Queue = struct { var renders = coral.stack.Sequential(device.RenderChain){.allocator = coral.heap.allocator}; }; +pub const Rect = struct { + left: f32, + top: f32, + right: f32, + bottom: f32, +}; + pub const Transform2D = extern struct { xbasis: Point2D = .{1, 0}, ybasis: Point2D = .{0, 1}, diff --git a/src/ona/gfx/device.zig b/src/ona/gfx/device.zig index 60a2f73..b5b9eca 100644 --- a/src/ona/gfx/device.zig +++ b/src/ona/gfx/device.zig @@ -623,11 +623,15 @@ const Rendering2D = struct { defer spirv_unit.deinit(); - try spirv_unit.compile(shader_spirv[0 ..], .vertex); - try spirv_unit.compile(shader_spirv[0 ..], .fragment); + const shader_spirv = spirv.embed_shader("./shaders/render_2d.spv"); + + try spirv_unit.compile(shader_spirv, .vertex); + + std.log.info("{s}\n", .{spirv_unit.shader_desc.vs.source}); + + try spirv_unit.compile(shader_spirv, .fragment); std.log.info("{s}\n", .{spirv_unit.shader_desc.fs.source}); - std.log.info("{s}", .{spirv_unit.shader_desc.vs.source}); return .{ .batching_pipeline = sokol.gfx.makePipeline(.{ @@ -735,7 +739,13 @@ const Rendering2D = struct { const render_target = resource_cast(render_texture).payload.render_target; pass.attachments = render_target.attachments; - frame.projection = orthographic_projection(0.0, @floatFromInt(render_target.width), 0.0, @floatFromInt(render_target.height), -1.0, 1.0); + + frame.projection = orthographic_projection(-1.0, 1.0, .{ + .left = 0, + .top = 0, + .right = @floatFromInt(render_target.width), + .bottom = @floatFromInt(render_target.height), + }); } else { var target_width, var target_height = [_]c_int{0, 0}; @@ -751,115 +761,16 @@ const Rendering2D = struct { .gl = .{.framebuffer = 0}, }; - frame.projection = orthographic_projection(0.0, @floatFromInt(target_width), @floatFromInt(target_height), 0.0, -1.0, 1.0); + frame.projection = orthographic_projection(-1.0, 1.0, .{ + .left = 0, + .top = 0, + .right = @floatFromInt(target_width), + .bottom = @floatFromInt(target_height), + }); } sokol.gfx.beginPass(pass); } - - const shader_spirv = &[_]u32{ - 0x07230203, 0x00010000, 0x00110000, 0x0000006f, 0x00000000, 0x00020011, 0x00000001, 0x0006000b, - 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001, - 0x0008000f, 0x00000004, 0x00000002, 0x6e69616d, 0x00000000, 0x00000003, 0x00000004, 0x00000005, - 0x0010000f, 0x00000000, 0x00000006, 0x6e69616d, 0x00000000, 0x00000007, 0x00000008, 0x00000009, - 0x0000000a, 0x0000000b, 0x0000000c, 0x0000000d, 0x0000000e, 0x0000000f, 0x00000010, 0x00000011, - 0x00030010, 0x00000002, 0x00000007, 0x00030003, 0x00000002, 0x000001ae, 0x00030003, 0x00000002, - 0x000001ae, 0x00040005, 0x00000002, 0x6e69616d, 0x00000000, 0x00040005, 0x00000003, 0x65786574, - 0x0000006c, 0x00030005, 0x00000012, 0x00786574, 0x00030005, 0x00000013, 0x00706d73, 0x00030005, - 0x00000004, 0x00007675, 0x00040005, 0x00000005, 0x6f6c6f63, 0x00000072, 0x00040005, 0x00000006, - 0x6e69616d, 0x00000000, 0x00060005, 0x00000014, 0x6c726f77, 0x6f705f64, 0x69746973, 0x00006e6f, - 0x00060005, 0x00000007, 0x74736e69, 0x65636e61, 0x69726f5f, 0x006e6967, 0x00040005, 0x00000008, - 0x6873656d, 0x0079785f, 0x00060005, 0x00000009, 0x74736e69, 0x65636e61, 0x6162785f, 0x00736973, - 0x00060005, 0x0000000a, 0x74736e69, 0x65636e61, 0x6162795f, 0x00736973, 0x00070005, 0x00000015, - 0x6a6f7270, 0x65746365, 0x6f705f64, 0x69746973, 0x00006e6f, 0x00050005, 0x00000016, 0x6a6f7250, - 0x69746365, 0x00006e6f, 0x00080006, 0x00000016, 0x00000000, 0x6a6f7270, 0x69746365, 0x6d5f6e6f, - 0x69727461, 0x00000078, 0x00030005, 0x00000017, 0x00000000, 0x00060005, 0x00000018, 0x505f6c67, - 0x65567265, 0x78657472, 0x00000000, 0x00060006, 0x00000018, 0x00000000, 0x505f6c67, 0x7469736f, - 0x006e6f69, 0x00070006, 0x00000018, 0x00000001, 0x505f6c67, 0x746e696f, 0x657a6953, 0x00000000, - 0x00070006, 0x00000018, 0x00000002, 0x435f6c67, 0x4470696c, 0x61747369, 0x0065636e, 0x00030005, - 0x0000000b, 0x00000000, 0x00060005, 0x0000000c, 0x74736e69, 0x65636e61, 0x7065645f, 0x00006874, - 0x00040005, 0x0000000d, 0x6f6c6f63, 0x00000072, 0x00060005, 0x0000000e, 0x74736e69, 0x65636e61, - 0x6e69745f, 0x00000074, 0x00050005, 0x00000019, 0x74636572, 0x736f705f, 0x00000000, 0x00060005, - 0x0000000f, 0x74736e69, 0x65636e61, 0x6365725f, 0x00000074, 0x00050005, 0x0000001a, 0x74636572, - 0x7a69735f, 0x00000065, 0x00030005, 0x00000010, 0x00007675, 0x00040005, 0x00000011, 0x6873656d, - 0x0076755f, 0x00040047, 0x00000003, 0x0000001e, 0x00000000, 0x00040047, 0x00000012, 0x00000022, - 0x00000000, 0x00040047, 0x00000012, 0x00000021, 0x00000000, 0x00040047, 0x00000013, 0x00000022, - 0x00000000, 0x00040047, 0x00000013, 0x00000021, 0x00000000, 0x00040047, 0x00000004, 0x0000001e, - 0x00000001, 0x00040047, 0x00000005, 0x0000001e, 0x00000000, 0x00040047, 0x00000007, 0x0000001e, - 0x00000004, 0x00040047, 0x00000008, 0x0000001e, 0x00000000, 0x00040047, 0x00000009, 0x0000001e, - 0x00000002, 0x00040047, 0x0000000a, 0x0000001e, 0x00000003, 0x00040048, 0x00000016, 0x00000000, - 0x00000005, 0x00050048, 0x00000016, 0x00000000, 0x00000023, 0x00000000, 0x00050048, 0x00000016, - 0x00000000, 0x00000007, 0x00000010, 0x00030047, 0x00000016, 0x00000002, 0x00040047, 0x00000017, - 0x00000022, 0x00000000, 0x00040047, 0x00000017, 0x00000021, 0x00000000, 0x00050048, 0x00000018, - 0x00000000, 0x0000000b, 0x00000000, 0x00050048, 0x00000018, 0x00000001, 0x0000000b, 0x00000001, - 0x00050048, 0x00000018, 0x00000002, 0x0000000b, 0x00000003, 0x00030047, 0x00000018, 0x00000002, - 0x00040047, 0x0000000c, 0x0000001e, 0x00000006, 0x00040047, 0x0000000d, 0x0000001e, 0x00000000, - 0x00040047, 0x0000000e, 0x0000001e, 0x00000005, 0x00040047, 0x0000000f, 0x0000001e, 0x00000007, - 0x00040047, 0x00000010, 0x0000001e, 0x00000001, 0x00040047, 0x00000011, 0x0000001e, 0x00000001, - 0x00020013, 0x0000001b, 0x00030021, 0x0000001c, 0x0000001b, 0x00030016, 0x0000001d, 0x00000020, - 0x00040017, 0x0000001e, 0x0000001d, 0x00000004, 0x00040020, 0x0000001f, 0x00000003, 0x0000001e, - 0x0004003b, 0x0000001f, 0x00000003, 0x00000003, 0x00090019, 0x00000020, 0x0000001d, 0x00000001, - 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x00040020, 0x00000021, 0x00000000, - 0x00000020, 0x0004003b, 0x00000021, 0x00000012, 0x00000000, 0x0002001a, 0x00000022, 0x00040020, - 0x00000023, 0x00000000, 0x00000022, 0x0004003b, 0x00000023, 0x00000013, 0x00000000, 0x0003001b, - 0x00000024, 0x00000020, 0x00040017, 0x00000025, 0x0000001d, 0x00000002, 0x00040020, 0x00000026, - 0x00000001, 0x00000025, 0x0004003b, 0x00000026, 0x00000004, 0x00000001, 0x00040020, 0x00000027, - 0x00000001, 0x0000001e, 0x0004003b, 0x00000027, 0x00000005, 0x00000001, 0x00040015, 0x00000028, - 0x00000020, 0x00000000, 0x0004002b, 0x00000028, 0x00000029, 0x00000003, 0x00040020, 0x0000002a, - 0x00000003, 0x0000001d, 0x0004002b, 0x0000001d, 0x0000002b, 0x00000000, 0x00020014, 0x0000002c, - 0x00040020, 0x0000002d, 0x00000007, 0x00000025, 0x0004003b, 0x00000026, 0x00000007, 0x00000001, - 0x0004003b, 0x00000026, 0x00000008, 0x00000001, 0x0004002b, 0x00000028, 0x0000002e, 0x00000000, - 0x00040020, 0x0000002f, 0x00000001, 0x0000001d, 0x0004003b, 0x00000026, 0x00000009, 0x00000001, - 0x0004002b, 0x00000028, 0x00000030, 0x00000001, 0x0004003b, 0x00000026, 0x0000000a, 0x00000001, - 0x00040018, 0x00000031, 0x0000001e, 0x00000004, 0x0003001e, 0x00000016, 0x00000031, 0x00040020, - 0x00000032, 0x00000002, 0x00000016, 0x0004003b, 0x00000032, 0x00000017, 0x00000002, 0x00040015, - 0x00000033, 0x00000020, 0x00000001, 0x0004002b, 0x00000033, 0x00000034, 0x00000000, 0x00040020, - 0x00000035, 0x00000002, 0x00000031, 0x0004002b, 0x0000001d, 0x00000036, 0x00000000, 0x0004002b, - 0x0000001d, 0x00000037, 0x3f800000, 0x0004001c, 0x00000038, 0x0000001d, 0x00000030, 0x0005001e, - 0x00000018, 0x0000001e, 0x0000001d, 0x00000038, 0x00040020, 0x00000039, 0x00000003, 0x00000018, - 0x0004003b, 0x00000039, 0x0000000b, 0x00000003, 0x0004003b, 0x0000002f, 0x0000000c, 0x00000001, - 0x0004003b, 0x0000001f, 0x0000000d, 0x00000003, 0x0004003b, 0x00000027, 0x0000000e, 0x00000001, - 0x0004003b, 0x00000027, 0x0000000f, 0x00000001, 0x00040020, 0x0000003a, 0x00000003, 0x00000025, - 0x0004003b, 0x0000003a, 0x00000010, 0x00000003, 0x0004003b, 0x00000026, 0x00000011, 0x00000001, - 0x00050036, 0x0000001b, 0x00000002, 0x00000000, 0x0000001c, 0x000200f8, 0x0000003b, 0x0004003d, - 0x00000020, 0x0000003c, 0x00000012, 0x0004003d, 0x00000022, 0x0000003d, 0x00000013, 0x00050056, - 0x00000024, 0x0000003e, 0x0000003c, 0x0000003d, 0x0004003d, 0x00000025, 0x0000003f, 0x00000004, - 0x00050057, 0x0000001e, 0x00000040, 0x0000003e, 0x0000003f, 0x0004003d, 0x0000001e, 0x00000041, - 0x00000005, 0x00050085, 0x0000001e, 0x00000042, 0x00000040, 0x00000041, 0x0003003e, 0x00000003, - 0x00000042, 0x00050041, 0x0000002a, 0x00000043, 0x00000003, 0x00000029, 0x0004003d, 0x0000001d, - 0x00000044, 0x00000043, 0x000500b4, 0x0000002c, 0x00000045, 0x00000044, 0x0000002b, 0x000300f7, - 0x00000046, 0x00000000, 0x000400fa, 0x00000045, 0x00000047, 0x00000046, 0x000200f8, 0x00000047, - 0x000100fc, 0x000200f8, 0x00000046, 0x000100fd, 0x00010038, 0x00050036, 0x0000001b, 0x00000006, - 0x00000000, 0x0000001c, 0x000200f8, 0x00000048, 0x0004003b, 0x0000002d, 0x00000014, 0x00000007, - 0x0004003b, 0x0000002d, 0x00000015, 0x00000007, 0x0004003b, 0x0000002d, 0x00000019, 0x00000007, - 0x0004003b, 0x0000002d, 0x0000001a, 0x00000007, 0x0004003d, 0x00000025, 0x00000049, 0x00000007, - 0x00050041, 0x0000002f, 0x0000004a, 0x00000008, 0x0000002e, 0x0004003d, 0x0000001d, 0x0000004b, - 0x0000004a, 0x0004003d, 0x00000025, 0x0000004c, 0x00000009, 0x0005008e, 0x00000025, 0x0000004d, - 0x0000004c, 0x0000004b, 0x00050081, 0x00000025, 0x0000004e, 0x00000049, 0x0000004d, 0x00050041, - 0x0000002f, 0x0000004f, 0x00000008, 0x00000030, 0x0004003d, 0x0000001d, 0x00000050, 0x0000004f, - 0x0004003d, 0x00000025, 0x00000051, 0x0000000a, 0x0005008e, 0x00000025, 0x00000052, 0x00000051, - 0x00000050, 0x00050081, 0x00000025, 0x00000053, 0x0000004e, 0x00000052, 0x0003003e, 0x00000014, - 0x00000053, 0x00050041, 0x00000035, 0x00000054, 0x00000017, 0x00000034, 0x0004003d, 0x00000031, - 0x00000055, 0x00000054, 0x0004003d, 0x00000025, 0x00000056, 0x00000014, 0x00050051, 0x0000001d, - 0x00000057, 0x00000056, 0x00000000, 0x00050051, 0x0000001d, 0x00000058, 0x00000056, 0x00000001, - 0x00070050, 0x0000001e, 0x00000059, 0x00000057, 0x00000058, 0x00000036, 0x00000037, 0x00050091, - 0x0000001e, 0x0000005a, 0x00000055, 0x00000059, 0x0007004f, 0x00000025, 0x0000005b, 0x0000005a, - 0x0000005a, 0x00000000, 0x00000001, 0x0003003e, 0x00000015, 0x0000005b, 0x0004003d, 0x00000025, - 0x0000005c, 0x00000015, 0x0004003d, 0x0000001d, 0x0000005d, 0x0000000c, 0x00050051, 0x0000001d, - 0x0000005e, 0x0000005c, 0x00000000, 0x00050051, 0x0000001d, 0x0000005f, 0x0000005c, 0x00000001, - 0x00070050, 0x0000001e, 0x00000060, 0x0000005e, 0x0000005f, 0x0000005d, 0x00000037, 0x00050041, - 0x0000001f, 0x00000061, 0x0000000b, 0x00000034, 0x0003003e, 0x00000061, 0x00000060, 0x0004003d, - 0x0000001e, 0x00000062, 0x0000000e, 0x0003003e, 0x0000000d, 0x00000062, 0x0004003d, 0x0000001e, - 0x00000063, 0x0000000f, 0x0007004f, 0x00000025, 0x00000064, 0x00000063, 0x00000063, 0x00000000, - 0x00000001, 0x0003003e, 0x00000019, 0x00000064, 0x0004003d, 0x0000001e, 0x00000065, 0x0000000f, - 0x0007004f, 0x00000025, 0x00000066, 0x00000065, 0x00000065, 0x00000002, 0x00000003, 0x0004003d, - 0x0000001e, 0x00000067, 0x0000000f, 0x0007004f, 0x00000025, 0x00000068, 0x00000067, 0x00000067, - 0x00000000, 0x00000001, 0x00050083, 0x00000025, 0x00000069, 0x00000066, 0x00000068, 0x0003003e, - 0x0000001a, 0x00000069, 0x0004003d, 0x00000025, 0x0000006a, 0x00000019, 0x0004003d, 0x00000025, - 0x0000006b, 0x00000011, 0x0004003d, 0x00000025, 0x0000006c, 0x0000001a, 0x00050085, 0x00000025, - 0x0000006d, 0x0000006b, 0x0000006c, 0x00050081, 0x00000025, 0x0000006e, 0x0000006a, 0x0000006d, - 0x0003003e, 0x00000010, 0x0000006e, 0x000100fd, 0x00010038, - }; }; const Resource = struct { @@ -919,16 +830,15 @@ fn clone_command(self: gfx.Command, arena: *std.heap.ArenaAllocator) std.mem.All }; } -fn orthographic_projection(left: f32, right: f32, bottom: f32, top: f32, zNear: f32, zFar: f32) Projection { - const width = right - left; - const height = top - bottom; - const depth = zFar - zNear; +fn orthographic_projection(near: f32, far: f32, viewport: gfx.Rect) Projection { + 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, -(2 / depth), 0}, - .{-((right + left) / width), -((top + bottom) / height), -((zFar + zNear) / depth), 1}, + .{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/shaders/render_2d.frag b/src/ona/gfx/shaders/render_2d.frag index a059a43..40e5c20 100644 --- a/src/ona/gfx/shaders/render_2d.frag +++ b/src/ona/gfx/shaders/render_2d.frag @@ -1,7 +1,6 @@ #version 430 -layout (binding = 0) uniform texture2D tex; -layout (binding = 0) uniform sampler smp; +layout (binding = 0) uniform sampler2D sprite; layout (location = 0) in vec4 color; layout (location = 1) in vec2 uv; @@ -9,7 +8,7 @@ layout (location = 1) in vec2 uv; layout (location = 0) out vec4 texel; void main() { - texel = texture(sampler2D(tex, smp), uv) * color; + texel = texture(sprite, uv) * color; if (texel.a == 0) { discard; diff --git a/src/ona/gfx/shaders/render_2d.vert b/src/ona/gfx/shaders/render_2d.vert index cea5831..f7d730e 100644 --- a/src/ona/gfx/shaders/render_2d.vert +++ b/src/ona/gfx/shaders/render_2d.vert @@ -10,27 +10,19 @@ layout (location = 5) in vec4 instance_tint; layout (location = 6) in float instance_depth; layout (location = 7) in vec4 instance_rect; +layout (location = 0) out vec4 color; +layout (location = 1) out vec2 uv; + layout (binding = 0) uniform Projection { mat4 projection_matrix; }; -layout (location = 0) out vec4 color; -layout (location = 1) out vec2 uv; - void main() { - // Calculate the world position of the vertex const vec2 world_position = instance_origin + mesh_xy.x * instance_xbasis + mesh_xy.y * instance_ybasis; const vec2 projected_position = (projection_matrix * vec4(world_position, 0, 1)).xy; + const vec2 rect_size = instance_rect.zw - instance_rect.xy; - // Set the position of the vertex in clip space gl_Position = vec4(projected_position, instance_depth, 1.0); color = instance_tint; - - // Calculate the width and height from left, top, right, bottom configuration - const vec2 rect_pos = instance_rect.xy; // left, top - const vec2 rect_size = instance_rect.zw - instance_rect.xy; // right - left, bottom - top - - // Calculate the adjusted UV coordinates using the instance_rect - uv = rect_pos + (mesh_uv * rect_size); + uv = instance_rect.xy + (mesh_uv * rect_size); } - diff --git a/src/ona/gfx/spirv.zig b/src/ona/gfx/spirv.zig index 9794360..e47f631 100644 --- a/src/ona/gfx/spirv.zig +++ b/src/ona/gfx/spirv.zig @@ -39,6 +39,7 @@ pub const Unit = struct { .{ext.SPVC_COMPILER_OPTION_GLSL_ES, @intFromBool(false)}, .{ext.SPVC_COMPILER_OPTION_GLSL_VULKAN_SEMANTICS, @intFromBool(false)}, .{ext.SPVC_COMPILER_OPTION_GLSL_EMIT_UNIFORM_BUFFER_AS_PLAIN_UNIFORMS, @intFromBool(true)}, + .{ext.SPVC_COMPILER_OPTION_FLIP_VERTEX_Y, @intFromBool(true)}, }, }, @@ -143,6 +144,17 @@ pub const Stage = enum { vertex, }; +pub fn embed_shader(comptime path: []const u8) []const u32 { + const shader = @embedFile(path); + const alignment = @alignOf(u32); + + if ((shader.len % alignment) != 0) { + @compileError("size of file contents at " ++ path ++ " must be aligned to a multiple of 4"); + } + + return @as(*const [shader.len / alignment]u32, @ptrCast(@alignCast(shader))); +} + fn log_context_errors(userdata: ?*anyopaque, error_message: [*c]const u8) callconv(.C) void { std.debug.assert(userdata == null); std.log.err("{s}", .{error_message}); From 640a06c86b0437c9f73944dc0d25eaa5fd15201a Mon Sep 17 00:00:00 2001 From: kayomn Date: Sat, 6 Jul 2024 17:42:59 +0100 Subject: [PATCH 06/30] Integrate update shader compilation step to building --- b64_dump.py | 29 -------- build.zig | 203 ++++++++++++++++++++++++++++++++++++++++------------ readme.md | 1 + 3 files changed, 160 insertions(+), 73 deletions(-) delete mode 100755 b64_dump.py diff --git a/b64_dump.py b/b64_dump.py deleted file mode 100755 index 720bac9..0000000 --- a/b64_dump.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/python - -import sys -import base64 -import struct - -def format_base64_to_u32(base64_string): - # Decode the Base64 string - decoded_bytes = base64.b64decode(base64_string) - - # Interpret the bytes as a sequence of u32 values - u32_values = struct.unpack('I' * (len(decoded_bytes) // 4), decoded_bytes) - - # Format the u32 values as C-style hex values - formatted_u32_values = ', '.join(f'0x{value:08x}' for value in u32_values) - - # Split the formatted string into lines of 8 values each - lines = [', '.join(formatted_u32_values.split(', ')[i:i+8]) for i in range(0, len(u32_values), 8)] - - # Print the formatted values - print(',\n'.join(lines)) - -if __name__ == "__main__": - if len(sys.argv) != 2: - print(f"Usage: python {sys.argv[0]} ") - sys.exit(1) - - base64_string = sys.argv[1] - format_base64_to_u32(base64_string) diff --git a/build.zig b/build.zig index e7da8c8..8393b7e 100644 --- a/build.zig +++ b/build.zig @@ -2,6 +2,17 @@ const builtin = @import("builtin"); const std = @import("std"); +const SubPath = struct { + buffer: [max]u8 = [_]u8{0} ** max, + unused: u8 = max, + + const max = 255; + + pub fn utf8(self: *const SubPath) [:0]const u8 { + return self.buffer[0 .. (max - self.unused):0]; + } +}; + pub fn build(b: *std.Build) !void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); @@ -48,16 +59,52 @@ pub fn build(b: *std.Build) !void { }); ona_module.addIncludePath(b.path("ext/")); - ona_module.linkLibrary(build_spirvcross(b, target, optimize)); - b.step("test", "Run unit tests").dependOn(create: { + ona_module.linkLibrary(spirv_cross: { + const dir = "ext/spirv-cross/"; + + const sources = [_][]const u8{ + "spirv_cross.cpp", + "spirv_parser.cpp", + "spirv_cross_parsed_ir.cpp", + "spirv_cfg.cpp", + "spirv_glsl.cpp", + "spirv_msl.cpp", + "spirv_hlsl.cpp", + "spirv_reflect.cpp", + "spirv_cross_util.cpp", + "spirv_cross_c.cpp", + }; + + const lib = b.addStaticLibrary(.{ + .name = "spirvcross", + .target = target, + .optimize = optimize, + }); + + switch (lib.rootModuleTarget().abi) { + .msvc => lib.linkLibC(), + else => lib.linkLibCpp(), + } + + inline for (sources) |src| { + lib.addCSourceFile(.{ + .file = b.path(dir ++ src), + .flags = &.{"-fstrict-aliasing", "-DSPIRV_CROSS_C_API_GLSL", "-DSPIRV_CROSS_C_API_HLSL", "-DSPIRV_CROSS_C_API_MSL"}, + }); + } + + break: spirv_cross lib; + }); + + b.step("test", "Run unit tests").dependOn(tests: { const tests = b.addTest(.{ .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, }); - break: create &tests.step; + break: tests &tests.step; }); b.installArtifact(add: { @@ -73,47 +120,115 @@ pub fn build(b: *std.Build) !void { exe.linkLibC(); exe.linkSystemLibrary("SDL2"); + const shaders_sub_path = "src/ona/gfx/shaders/"; + + var shaders_dir = try std.fs.cwd().openDir(shaders_sub_path, .{ + .iterate = true, + }); + + defer shaders_dir.close(); + + var shaders_walker = try shaders_dir.walk(b.allocator); + + defer shaders_walker.deinit(); + + const Shader = struct { + source_sub_path: SubPath = .{}, + binary_sub_path: SubPath = .{}, + }; + + var pending_shaders = std.ArrayList(Shader).init(b.allocator); + + defer pending_shaders.deinit(); + + scan_shaders: while (try shaders_walker.next()) |entry| { + if (entry.kind != .file) { + continue: scan_shaders; + } + + const is_shader_file = std.mem.endsWith(u8, entry.path, ".frag") or std.mem.endsWith(u8, entry.path, ".vert"); + + if (!is_shader_file) { + continue: scan_shaders; + } + + const shader_name = std.fs.path.stem(entry.path); + + for (pending_shaders.items) |pending_shader| { + if (std.mem.endsWith(u8, pending_shader.source_sub_path.utf8(), shader_name)) { + continue: scan_shaders; + } + } + + var shader = Shader{}; + const source_sub_path = try std.fmt.bufPrint(&shader.source_sub_path.buffer, "{s}{s}", .{shaders_sub_path, shader_name}); + const binary_sub_path = try std.fmt.bufPrint(&shader.binary_sub_path.buffer, "{s}.spv", .{source_sub_path}); + + shaders_dir.access(std.fs.path.basename(binary_sub_path), .{.mode = .read_only}) catch { + shader.source_sub_path.unused -= @intCast(source_sub_path.len); + shader.binary_sub_path.unused -= @intCast(binary_sub_path.len); + + try pending_shaders.append(shader); + + continue: scan_shaders; + }; + + if ((try shaders_dir.statFile(entry.basename)).mtime > (try shaders_dir.statFile(std.fs.path.basename(binary_sub_path))).mtime) { + shader.source_sub_path.unused -= @intCast(source_sub_path.len); + shader.binary_sub_path.unused -= @intCast(binary_sub_path.len); + + try pending_shaders.append(shader); + + continue: scan_shaders; + } + } + + for (pending_shaders.items) |pending_shader| { + var vertex_binary_sub_path = SubPath{}; + var fragment_binary_sub_path = SubPath{}; + const source_sub_path_utf8 = pending_shader.source_sub_path.utf8(); + + vertex_binary_sub_path.unused -= @intCast((try std.fmt.bufPrint(&vertex_binary_sub_path.buffer, "{s}.vert.spv", .{source_sub_path_utf8})).len); + fragment_binary_sub_path.unused -= @intCast((try std.fmt.bufPrint(&fragment_binary_sub_path.buffer, "{s}.frag.spv", .{source_sub_path_utf8})).len); + + const vertex_binary_sub_path_utf8 = vertex_binary_sub_path.utf8(); + const fragment_binary_sub_path_utf8 = fragment_binary_sub_path.utf8(); + + const link_command = b.addSystemCommand(&.{ + "spirv-link", + vertex_binary_sub_path_utf8, + fragment_binary_sub_path_utf8, + "-o", + pending_shader.binary_sub_path.utf8(), + }); + + exe.step.dependOn(&link_command.step); + + link_command.step.dependOn(compile_vertex: { + const compile_command = b.addSystemCommand(&.{ + "glslangValidator", + "-V", + vertex_binary_sub_path_utf8[0 .. vertex_binary_sub_path_utf8.len - 4], + "-o", + vertex_binary_sub_path_utf8, + }); + + break: compile_vertex &compile_command.step; + }); + + link_command.step.dependOn(compile_fragment: { + const compile_command = b.addSystemCommand(&.{ + "glslangValidator", + "-V", + fragment_binary_sub_path_utf8[0 .. fragment_binary_sub_path_utf8.len - 4], + "-o", + fragment_binary_sub_path_utf8, + }); + + break: compile_fragment &compile_command.step; + }); + } + break: add exe; }); } - -fn build_spirvcross( - b: *std.Build, - target: std.Build.ResolvedTarget, - mode: std.builtin.OptimizeMode, -) *std.Build.Step.Compile { - const dir = "ext/spirv-cross/"; - - const sources = [_][]const u8{ - "spirv_cross.cpp", - "spirv_parser.cpp", - "spirv_cross_parsed_ir.cpp", - "spirv_cfg.cpp", - "spirv_glsl.cpp", - "spirv_msl.cpp", - "spirv_hlsl.cpp", - "spirv_reflect.cpp", - "spirv_cross_util.cpp", - "spirv_cross_c.cpp", - }; - - const lib = b.addStaticLibrary(.{ - .name = "spirvcross", - .target = target, - .optimize = mode, - }); - - switch (lib.rootModuleTarget().abi) { - .msvc => lib.linkLibC(), - else => lib.linkLibCpp(), - } - - inline for (sources) |src| { - lib.addCSourceFile(.{ - .file = b.path(dir ++ src), - .flags = &.{"-fstrict-aliasing", "-DSPIRV_CROSS_C_API_GLSL", "-DSPIRV_CROSS_C_API_HLSL", "-DSPIRV_CROSS_C_API_MSL"}, - }); - } - - return lib; -} diff --git a/readme.md b/readme.md index 7e8848a..e8d558e 100644 --- a/readme.md +++ b/readme.md @@ -33,6 +33,7 @@ Ona is also the Catalan word for "wave". Ona currently depends the following third-party tools to build it: * Platform support for SDL2 at version 2.0.20 or above. + * SPIR-V shader compilation system utilities, namely `glslangValidator` and `spirv-link`. * Zig compiler toolchain. As the project evolves, dependencies on libraries external to the project codebase will be minimized or removed outright to meet the goals of the project as closely as possible. From c3d4cbe7878506d23c61a23cc25459dd53f9d732 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sat, 6 Jul 2024 18:45:19 +0200 Subject: [PATCH 07/30] Add full engine build to CI before testing --- .drone.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.drone.yml b/.drone.yml index 4a37890..8335e9d 100644 --- a/.drone.yml +++ b/.drone.yml @@ -5,4 +5,5 @@ steps: - name: build & test image: chainguard/zig:latest commands: + - zig build - zig build test From 97e64733da5a72f685135a488e1f3a3c978f02ba Mon Sep 17 00:00:00 2001 From: kayomn Date: Sat, 6 Jul 2024 21:27:42 +0200 Subject: [PATCH 08/30] Register new external dependencies with the CI --- .drone.yml | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/.drone.yml b/.drone.yml index 8335e9d..96ed275 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,9 +1,27 @@ kind: pipeline +type: docker name: continuous integration steps: -- name: build & test - image: chainguard/zig:latest - commands: - - zig build - - zig build test + - name: setup + image: ubuntu:latest + commands: + - apt-get update + - apt-get install -y wget gnupg software-properties-common + - apt-get install -y sdl2-dev glslang-tools spirv-tools + - wget https://ziglang.org/download/0.10.1/zig-linux-x86_64-0.10.1.tar.xz + - tar -xf zig-linux-x86_64-0.10.1.tar.xz + - export PATH=$PATH:$(pwd)/zig-linux-x86_64-0.10.1 + - zig version + + - name: build + image: ubuntu:latest + commands: + - export PATH=$PATH:$(pwd)/zig-linux-x86_64-0.10.1 + - zig build + + - name: test + image: ubuntu:latest + commands: + - export PATH=$PATH:$(pwd)/zig-linux-x86_64-0.10.1 + - zig build test \ No newline at end of file From 762fb5ed559e5d46e1416bf3feda2affe1171073 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sat, 6 Jul 2024 21:35:55 +0200 Subject: [PATCH 09/30] Fix broken dependencies in CI --- .drone.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.drone.yml b/.drone.yml index 96ed275..230f10e 100644 --- a/.drone.yml +++ b/.drone.yml @@ -7,8 +7,7 @@ steps: image: ubuntu:latest commands: - apt-get update - - apt-get install -y wget gnupg software-properties-common - - apt-get install -y sdl2-dev glslang-tools spirv-tools + - apt-get install -y wget libsdl2-dev glslang-tools spirv-tools - wget https://ziglang.org/download/0.10.1/zig-linux-x86_64-0.10.1.tar.xz - tar -xf zig-linux-x86_64-0.10.1.tar.xz - export PATH=$PATH:$(pwd)/zig-linux-x86_64-0.10.1 From 54b443ca336f63c2b43c1ea91388c4fe7d12a96c Mon Sep 17 00:00:00 2001 From: kayomn Date: Sat, 6 Jul 2024 22:01:13 +0200 Subject: [PATCH 10/30] Add xz-utils to CI dependencies --- .drone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index 230f10e..b0efd3b 100644 --- a/.drone.yml +++ b/.drone.yml @@ -7,7 +7,7 @@ steps: image: ubuntu:latest commands: - apt-get update - - apt-get install -y wget libsdl2-dev glslang-tools spirv-tools + - apt-get install -y xz-utils wget libsdl2-dev glslang-tools spirv-tools - wget https://ziglang.org/download/0.10.1/zig-linux-x86_64-0.10.1.tar.xz - tar -xf zig-linux-x86_64-0.10.1.tar.xz - export PATH=$PATH:$(pwd)/zig-linux-x86_64-0.10.1 From ea43c326e6ed0db493707fb2f5745fc1275b8355 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sat, 6 Jul 2024 22:09:04 +0200 Subject: [PATCH 11/30] Update Zig version in CI to 0.13.0 --- .drone.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.drone.yml b/.drone.yml index b0efd3b..362660f 100644 --- a/.drone.yml +++ b/.drone.yml @@ -5,22 +5,24 @@ name: continuous integration steps: - name: setup image: ubuntu:latest + environment: + ZIG_VERSION: 0.13.0 commands: - apt-get update - apt-get install -y xz-utils wget libsdl2-dev glslang-tools spirv-tools - - wget https://ziglang.org/download/0.10.1/zig-linux-x86_64-0.10.1.tar.xz - - tar -xf zig-linux-x86_64-0.10.1.tar.xz - - export PATH=$PATH:$(pwd)/zig-linux-x86_64-0.10.1 + - wget https://ziglang.org/download/${ZIG_VERSION}/zig-linux-x86_64-${ZIG_VERSION}.tar.xz + - tar -xf zig-linux-x86_64-${ZIG_VERSION}.tar.xz + - export PATH=$PATH:$(pwd)/zig-linux-x86_64-${ZIG_VERSION} - zig version - name: build image: ubuntu:latest commands: - - export PATH=$PATH:$(pwd)/zig-linux-x86_64-0.10.1 + - export PATH=$PATH:$(pwd)/zig-linux-x86_64-${ZIG_VERSION} - zig build - name: test image: ubuntu:latest commands: - - export PATH=$PATH:$(pwd)/zig-linux-x86_64-0.10.1 + - export PATH=$PATH:$(pwd)/zig-linux-x86_64-${ZIG_VERSION} - zig build test \ No newline at end of file From 3e734ccf74514451aea5cffc503feb2a814e2218 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 7 Jul 2024 00:37:18 +0200 Subject: [PATCH 12/30] Fix missing environment variables --- .drone.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.drone.yml b/.drone.yml index 362660f..c94229c 100644 --- a/.drone.yml +++ b/.drone.yml @@ -2,11 +2,13 @@ kind: pipeline type: docker name: continuous integration +environment: + ZIG_VERSION: 0.13.0 + steps: - name: setup image: ubuntu:latest - environment: - ZIG_VERSION: 0.13.0 + commands: - apt-get update - apt-get install -y xz-utils wget libsdl2-dev glslang-tools spirv-tools From 1a20d555996d12f9e2f393047403dda97d35d502 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 7 Jul 2024 00:46:11 +0200 Subject: [PATCH 13/30] Attempt CI fix --- .drone.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index c94229c..9430913 100644 --- a/.drone.yml +++ b/.drone.yml @@ -8,7 +8,8 @@ environment: steps: - name: setup image: ubuntu:latest - + environment: + ZIG_VERSION: ${ZIG_VERSION} commands: - apt-get update - apt-get install -y xz-utils wget libsdl2-dev glslang-tools spirv-tools @@ -19,12 +20,16 @@ steps: - name: build image: ubuntu:latest + environment: + ZIG_VERSION: ${ZIG_VERSION} commands: - export PATH=$PATH:$(pwd)/zig-linux-x86_64-${ZIG_VERSION} - zig build - name: test image: ubuntu:latest + environment: + ZIG_VERSION: ${ZIG_VERSION} commands: - export PATH=$PATH:$(pwd)/zig-linux-x86_64-${ZIG_VERSION} - zig build test \ No newline at end of file From 750b2e503d2d8b5c0d9aec60f30b20dc005f7a79 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 7 Jul 2024 00:48:49 +0200 Subject: [PATCH 14/30] Attempt CI fix #2 --- .drone.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.drone.yml b/.drone.yml index 9430913..38aa7c8 100644 --- a/.drone.yml +++ b/.drone.yml @@ -2,14 +2,11 @@ kind: pipeline type: docker name: continuous integration -environment: - ZIG_VERSION: 0.13.0 - steps: - name: setup image: ubuntu:latest environment: - ZIG_VERSION: ${ZIG_VERSION} + ZIG_VERSION: 0.13.0 commands: - apt-get update - apt-get install -y xz-utils wget libsdl2-dev glslang-tools spirv-tools @@ -21,7 +18,7 @@ steps: - name: build image: ubuntu:latest environment: - ZIG_VERSION: ${ZIG_VERSION} + ZIG_VERSION: 0.13.0 commands: - export PATH=$PATH:$(pwd)/zig-linux-x86_64-${ZIG_VERSION} - zig build @@ -29,7 +26,7 @@ steps: - name: test image: ubuntu:latest environment: - ZIG_VERSION: ${ZIG_VERSION} + ZIG_VERSION: 0.13.0 commands: - export PATH=$PATH:$(pwd)/zig-linux-x86_64-${ZIG_VERSION} - zig build test \ No newline at end of file From 3ed4082a8569ee5100b798d5b6b8052da4bfba1c Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 7 Jul 2024 00:52:18 +0200 Subject: [PATCH 15/30] Attempt CI fix 3 --- .drone.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.drone.yml b/.drone.yml index 38aa7c8..1baca82 100644 --- a/.drone.yml +++ b/.drone.yml @@ -10,9 +10,9 @@ steps: commands: - apt-get update - apt-get install -y xz-utils wget libsdl2-dev glslang-tools spirv-tools - - wget https://ziglang.org/download/${ZIG_VERSION}/zig-linux-x86_64-${ZIG_VERSION}.tar.xz - - tar -xf zig-linux-x86_64-${ZIG_VERSION}.tar.xz - - export PATH=$PATH:$(pwd)/zig-linux-x86_64-${ZIG_VERSION} + - wget https://ziglang.org/download/$${ZIG_VERSION}/zig-linux-x86_64-$${ZIG_VERSION}.tar.xz + - tar -xf zig-linux-x86_64-$${ZIG_VERSION}.tar.xz + - export PATH=$PATH:$(pwd)/zig-linux-x86_64-$${ZIG_VERSION} - zig version - name: build @@ -20,7 +20,7 @@ steps: environment: ZIG_VERSION: 0.13.0 commands: - - export PATH=$PATH:$(pwd)/zig-linux-x86_64-${ZIG_VERSION} + - export PATH=$PATH:$(pwd)/zig-linux-x86_64-$${ZIG_VERSION} - zig build - name: test @@ -28,5 +28,5 @@ steps: environment: ZIG_VERSION: 0.13.0 commands: - - export PATH=$PATH:$(pwd)/zig-linux-x86_64-${ZIG_VERSION} + - export PATH=$PATH:$(pwd)/zig-linux-x86_64-$${ZIG_VERSION} - zig build test \ No newline at end of file From 391adb1816b96b17dbecc7180a90063aa712a909 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 7 Jul 2024 01:03:39 +0200 Subject: [PATCH 16/30] Remove duplicate environment variables from CI --- .drone.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.drone.yml b/.drone.yml index 1baca82..bbde8b0 100644 --- a/.drone.yml +++ b/.drone.yml @@ -2,11 +2,12 @@ kind: pipeline type: docker name: continuous integration +environment: + ZIG_VERSION: 0.13.0 + steps: - name: setup image: ubuntu:latest - environment: - ZIG_VERSION: 0.13.0 commands: - apt-get update - apt-get install -y xz-utils wget libsdl2-dev glslang-tools spirv-tools @@ -17,16 +18,12 @@ steps: - name: build image: ubuntu:latest - environment: - ZIG_VERSION: 0.13.0 commands: - export PATH=$PATH:$(pwd)/zig-linux-x86_64-$${ZIG_VERSION} - zig build - name: test image: ubuntu:latest - environment: - ZIG_VERSION: 0.13.0 commands: - export PATH=$PATH:$(pwd)/zig-linux-x86_64-$${ZIG_VERSION} - zig build test \ No newline at end of file From 0944e1315eed59f9130bf9facd091990bc844374 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 7 Jul 2024 01:12:31 +0200 Subject: [PATCH 17/30] Add check TLS stage to CI --- .drone.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.drone.yml b/.drone.yml index bbde8b0..b753650 100644 --- a/.drone.yml +++ b/.drone.yml @@ -16,6 +16,12 @@ steps: - export PATH=$PATH:$(pwd)/zig-linux-x86_64-$${ZIG_VERSION} - zig version + - name: check-tls + image: ubuntu:latest + commands: + - openssl version + - openssl ciphers -v | grep -i tls + - name: build image: ubuntu:latest commands: From 7cdcbdb018f97c10296939ac62e4ddf066dfcb35 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 7 Jul 2024 01:17:17 +0200 Subject: [PATCH 18/30] Add openssl to CI dependencies --- .drone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index b753650..eac48d2 100644 --- a/.drone.yml +++ b/.drone.yml @@ -10,7 +10,7 @@ steps: image: ubuntu:latest commands: - apt-get update - - apt-get install -y xz-utils wget libsdl2-dev glslang-tools spirv-tools + - apt-get install -y openssl xz-utils wget libsdl2-dev glslang-tools spirv-tools - wget https://ziglang.org/download/$${ZIG_VERSION}/zig-linux-x86_64-$${ZIG_VERSION}.tar.xz - tar -xf zig-linux-x86_64-$${ZIG_VERSION}.tar.xz - export PATH=$PATH:$(pwd)/zig-linux-x86_64-$${ZIG_VERSION} From 28a4ba4deee32eccbe92f6fe4b4fa39e42fe1617 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 7 Jul 2024 01:21:40 +0200 Subject: [PATCH 19/30] Simplify CI steps --- .drone.yml | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/.drone.yml b/.drone.yml index eac48d2..4bf34df 100644 --- a/.drone.yml +++ b/.drone.yml @@ -6,25 +6,13 @@ environment: ZIG_VERSION: 0.13.0 steps: - - name: setup + - name: build image: ubuntu:latest commands: - apt-get update - apt-get install -y openssl xz-utils wget libsdl2-dev glslang-tools spirv-tools - wget https://ziglang.org/download/$${ZIG_VERSION}/zig-linux-x86_64-$${ZIG_VERSION}.tar.xz - tar -xf zig-linux-x86_64-$${ZIG_VERSION}.tar.xz - - export PATH=$PATH:$(pwd)/zig-linux-x86_64-$${ZIG_VERSION} - - zig version - - - name: check-tls - image: ubuntu:latest - commands: - - openssl version - - openssl ciphers -v | grep -i tls - - - name: build - image: ubuntu:latest - commands: - export PATH=$PATH:$(pwd)/zig-linux-x86_64-$${ZIG_VERSION} - zig build From fdbf018672d33fad69e3a08d47931e2b6d2170d3 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 7 Jul 2024 01:25:46 +0200 Subject: [PATCH 20/30] Add submodules support to CI --- .drone.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.drone.yml b/.drone.yml index 4bf34df..2a2b4e8 100644 --- a/.drone.yml +++ b/.drone.yml @@ -2,6 +2,10 @@ kind: pipeline type: docker name: continuous integration +clone: + depth: 1 + submodules: true + environment: ZIG_VERSION: 0.13.0 From 09b9c875b071aef906b312b11f3cc2a9d7dc0172 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 7 Jul 2024 01:31:12 +0200 Subject: [PATCH 21/30] Add submodules support step to CI --- .drone.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.drone.yml b/.drone.yml index 2a2b4e8..7f045d1 100644 --- a/.drone.yml +++ b/.drone.yml @@ -2,14 +2,15 @@ kind: pipeline type: docker name: continuous integration -clone: - depth: 1 - submodules: true - environment: ZIG_VERSION: 0.13.0 steps: + - name: submodules + image: ubuntu:latest + commands: + - git submodule update --init --recursive + - name: build image: ubuntu:latest commands: From a6314b0f0c9a7b6c58809b41f3b4efe03fbde3b2 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 7 Jul 2024 01:35:31 +0200 Subject: [PATCH 22/30] Add Git and LFS as dependencies to submodules CI stage --- .drone.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index 7f045d1..4587b05 100644 --- a/.drone.yml +++ b/.drone.yml @@ -9,7 +9,9 @@ steps: - name: submodules image: ubuntu:latest commands: - - git submodule update --init --recursive + - apt-get update + - apt-get install -y git git-lfs + - git submodule update --init --recursive - name: build image: ubuntu:latest From 9c54ed4683419b766b9c8f9d5736fc7639009011 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 7 Jul 2024 01:44:26 +0200 Subject: [PATCH 23/30] Merge build and test stages of new CI --- .drone.yml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.drone.yml b/.drone.yml index 4587b05..0bbe9c2 100644 --- a/.drone.yml +++ b/.drone.yml @@ -13,7 +13,7 @@ steps: - apt-get install -y git git-lfs - git submodule update --init --recursive - - name: build + - name: build & test image: ubuntu:latest commands: - apt-get update @@ -22,9 +22,4 @@ steps: - tar -xf zig-linux-x86_64-$${ZIG_VERSION}.tar.xz - export PATH=$PATH:$(pwd)/zig-linux-x86_64-$${ZIG_VERSION} - zig build - - - name: test - image: ubuntu:latest - commands: - - export PATH=$PATH:$(pwd)/zig-linux-x86_64-$${ZIG_VERSION} - - zig build test \ No newline at end of file + - zig build test From 99c38184775839cbb64347b80c5d66ac5d235c31 Mon Sep 17 00:00:00 2001 From: kayomn Date: Wed, 17 Jul 2024 23:32:10 +0100 Subject: [PATCH 24/30] Simplify 2D rendering interfaces --- debug/ca.frag | 28 + debug/test.bmp | 3 - src/coral/ascii.zig | 16 +- src/coral/asyncio.zig | 76 ++ src/coral/coral.zig | 104 +++ src/coral/files.zig | 53 +- src/coral/io.zig | 20 - src/coral/map.zig | 10 +- src/coral/slices.zig | 18 - src/coral/utf8.zig | 19 +- src/flow/dag.zig | 2 +- src/flow/states.zig | 4 +- src/flow/system.zig | 16 +- src/main.zig | 62 +- src/ona/gfx.zig | 349 ++++---- src/ona/gfx/{formats.zig => bmp.zig} | 24 +- src/ona/gfx/colors.zig | 13 + src/ona/gfx/commands.zig | 80 -- src/ona/gfx/device.zig | 847 ------------------ src/ona/gfx/handles.zig | 45 + src/ona/gfx/lina.zig | 29 + src/ona/gfx/rendering.zig | 662 ++++++++++++++ src/ona/gfx/resources.zig | 149 +++ .../{render_2d.frag => 2d_default.frag} | 4 + .../{render_2d.vert => 2d_default.vert} | 2 +- src/ona/gfx/spirv.zig | 184 ++-- 26 files changed, 1452 insertions(+), 1367 deletions(-) create mode 100644 debug/ca.frag delete mode 100644 debug/test.bmp create mode 100644 src/coral/asyncio.zig rename src/ona/gfx/{formats.zig => bmp.zig} (83%) create mode 100644 src/ona/gfx/colors.zig delete mode 100644 src/ona/gfx/commands.zig delete mode 100644 src/ona/gfx/device.zig create mode 100644 src/ona/gfx/handles.zig create mode 100644 src/ona/gfx/lina.zig create mode 100644 src/ona/gfx/rendering.zig create mode 100644 src/ona/gfx/resources.zig rename src/ona/gfx/shaders/{render_2d.frag => 2d_default.frag} (78%) rename src/ona/gfx/shaders/{render_2d.vert => 2d_default.vert} (95%) diff --git a/debug/ca.frag b/debug/ca.frag new file mode 100644 index 0000000..b4218e0 --- /dev/null +++ b/debug/ca.frag @@ -0,0 +1,28 @@ +#version 430 + +layout (binding = 0) uniform sampler2D sprite; + +layout (location = 0) in vec4 color; +layout (location = 1) in vec2 uv; + +layout (location = 0) out vec4 texel; + +layout (binding = 0) readonly buffer Camera { + mat4 projection_matrix; +}; + +layout (binding = 1) readonly buffer Material { + float effect_magnitude; +}; + +void main() { + const vec2 red_channel_uv = uv + vec2(effect_magnitude, 0.0); + const vec2 blue_channel_uv = uv + vec2(-effect_magnitude, 0.0); + const vec4 original_texel = texture(sprite, uv); + + texel = vec4(texture(sprite, red_channel_uv).r, original_texel.g, texture(sprite, blue_channel_uv).b, original_texel.a) * color; + + if (texel.a == 0) { + discard; + } +} diff --git a/debug/test.bmp b/debug/test.bmp deleted file mode 100644 index 2d7f6e7..0000000 --- a/debug/test.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e31f120e1c23ea9c949441bca87e504c1e8f2e90b6dff9bd1cd559ddfd4dd225 -size 16522 diff --git a/src/coral/ascii.zig b/src/coral/ascii.zig index 0339e7d..11830ee 100644 --- a/src/coral/ascii.zig +++ b/src/coral/ascii.zig @@ -7,7 +7,7 @@ const scalars = @import("./scalars.zig"); const std = @import("std"); pub const DecimalFormat = struct { - delimiter: []const coral.Byte, + delimiter: []const coral.io.Byte, positive_prefix: enum {none, plus, space}, pub const default = DecimalFormat{ @@ -115,9 +115,9 @@ pub const DecimalFormat = struct { } } - pub fn format(self: DecimalFormat, writer: io.Writer, value: anytype) io.PrintError!void { + pub fn format(self: DecimalFormat, writer: io.Writer, value: anytype) io.Error!void { if (value == 0) { - return io.print(writer, switch (self.positive_prefix) { + return io.write_all(writer, switch (self.positive_prefix) { .none => "0", .plus => "+0", .space => " 0", @@ -151,21 +151,21 @@ pub const DecimalFormat = struct { } } - return io.print(writer, buffer[buffer_start ..]); + return io.write_all(writer, buffer[buffer_start ..]); }, .Float => |float| { if (value < 0) { - try io.print(writer, "-"); + try io.write_all(writer, "-"); } const Float = @TypeOf(value); const Int = std.meta.Int(.unsigned, float.bits); const integer = @as(Int, @intFromFloat(value)); - try self.print(writer, integer); - try io.print(writer, "."); - try self.print(writer, @as(Int, @intFromFloat((value - @as(Float, @floatFromInt(integer))) * 100))); + try self.format(writer, integer); + try io.write_all(writer, "."); + try self.format(writer, @as(Int, @intFromFloat((value - @as(Float, @floatFromInt(integer))) * 100))); }, else => @compileError("`" ++ @typeName(Value) ++ "` cannot be formatted to a decimal string"), diff --git a/src/coral/asyncio.zig b/src/coral/asyncio.zig new file mode 100644 index 0000000..485beab --- /dev/null +++ b/src/coral/asyncio.zig @@ -0,0 +1,76 @@ +const std = @import("std"); + +pub fn BlockingQueue(comptime max: usize, comptime T: type) type { + return struct { + buffer: [max]T = undefined, + head: usize = 0, + tail: usize = 0, + mutex: std.Thread.Mutex = .{}, + not_empty: std.Thread.Condition = .{}, + not_full: std.Thread.Condition = .{}, + + const Self = @This(); + + pub fn enqueue(self: *Self, item: T) void { + self.mutex.lock(); + + defer self.mutex.unlock(); + + while (((self.tail + 1) % max) == self.head) { + self.not_full.wait(&self.mutex); + } + + self.buffer[self.tail] = item; + self.tail = (self.tail + 1) % max; + + self.not_empty.signal(); + } + + pub fn dequeue(self: *Self) T { + self.mutex.lock(); + + defer self.mutex.unlock(); + + while (self.head == self.tail) { + self.not_empty.wait(&self.mutex); + } + + const item = self.buffer[self.head]; + + self.buffer[self.head] = undefined; + self.head = (self.head + 1) % max; + + self.not_full.signal(); + + return item; + } + }; +} + +pub fn Future(comptime T: type) type { + return struct { + value: ?T = null, + resolved: std.Thread.ResetEvent = .{}, + + const Self = @This(); + + pub fn get(self: *Self) *T { + // TODO: Make async. + self.resolved.wait(); + + return &self.value.?; + } + + pub fn resolve(self: *Self, value: T) bool { + if (self.resolved.isSet()) { + return false; + } + + self.value = value; + + self.resolved.set(); + + return true; + } + }; +} diff --git a/src/coral/coral.zig b/src/coral/coral.zig index fae4ce8..777aca3 100644 --- a/src/coral/coral.zig +++ b/src/coral/coral.zig @@ -1,5 +1,7 @@ pub const ascii = @import("./ascii.zig"); +pub const asyncio = @import("./asyncio.zig"); + pub const files = @import("./files.zig"); pub const hashes = @import("./hashes.zig"); @@ -16,6 +18,108 @@ pub const slices = @import("./slices.zig"); pub const stack = @import("./stack.zig"); +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), + first_free_index: usize = 0, + + const Entry = union (enum) { + free_index: usize, + occupied: Value, + }; + + pub const Values = struct { + cursor: usize = 0, + pool: *const Self, + + pub fn next(self: *Values) ?*Value { + while (self.cursor < self.pool.entries.len()) { + defer self.cursor += 1; + + switch (self.pool.entries.values[self.cursor]) { + .free_index => { + continue; + }, + + .occupied => |*occupied| { + return occupied; + }, + } + } + + return null; + } + }; + + const Self = @This(); + + pub fn deinit(self: *Self) void { + self.entries.deinit(); + + self.* = undefined; + } + + pub fn get(self: *Self, key: usize) ?*Value { + return switch (self.entries.values[key]) { + .free_index => null, + .occupied => |*occupied| occupied, + }; + } + + pub fn init(allocator: std.mem.Allocator) Self { + return .{ + .entries = .{.allocator = allocator}, + }; + } + + pub fn insert(self: *Self, value: Value) std.mem.Allocator.Error!usize { + const entries_count = self.entries.len(); + + if (self.first_free_index == entries_count) { + try self.entries.push_grow(.{.occupied = value}); + + self.first_free_index += 1; + + return entries_count; + } + + const insersion_index = self.first_free_index; + + self.first_free_index = self.entries.values[self.first_free_index].free_index; + self.entries.values[insersion_index] = .{.occupied = value}; + + return insersion_index; + } + + pub fn remove(self: *Self, key: usize) ?Value { + if (key >= self.entries.len()) { + return null; + } + + switch (self.entries.values[key]) { + .free_index => { + return null; + }, + + .occupied => |occupied| { + self.entries.values[key] = .{.free_index = self.first_free_index}; + self.first_free_index = key; + + return occupied; + }, + } + } + + pub fn values(self: *const Self) Values { + return .{ + .pool = self, + }; + } + }; +} diff --git a/src/coral/files.zig b/src/coral/files.zig index 273202f..8756f3e 100644 --- a/src/coral/files.zig +++ b/src/coral/files.zig @@ -4,11 +4,20 @@ const io = @import("./io.zig"); const std = @import("std"); -pub const Error = error { +pub const AccessError = error { FileNotFound, FileInaccessible, }; +pub const ReadAllError = AccessError || error { + ReadIncomplete, +}; + +pub const ReadAllOptions = struct { + offset: u64 = 0, + limit: u64 = std.math.maxInt(u64), +}; + pub const Stat = struct { size: u64, }; @@ -18,15 +27,37 @@ pub const Storage = struct { 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, + stat: *const fn (*anyopaque, []const u8) AccessError!Stat, + read: *const fn (*anyopaque, []const u8, usize, []io.Byte) AccessError!usize, }; - pub fn read_bytes(self: Storage, path: []const u8, offset: usize, output: []io.Byte) Error!usize { + pub fn read(self: Storage, path: []const u8, output: []io.Byte, offset: u64) AccessError!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 { + pub fn read_all(self: Storage, path: []const u8, output: []io.Byte, options: ReadAllOptions) ReadAllError![]const io.Byte { + const bytes_read = try self.vtable.read(self.userdata, path, options.offset, output); + + if (try self.vtable.read(self.userdata, path, options.offset, output) != output.len) { + return error.ReadIncomplete; + } + + return output[0 .. bytes_read]; + } + + pub fn read_alloc(self: Storage, path: []const u8, allocator: std.mem.Allocator, options: ReadAllOptions) (std.mem.Allocator.Error || ReadAllError)![]io.Byte { + const buffer = try allocator.alloc(io.Byte, @min((try self.stat(path)).size, options.limit)); + + errdefer allocator.free(buffer); + + if (try self.vtable.read(self.userdata, path, options.offset, buffer) != buffer.len) { + return error.ReadIncomplete; + } + + return buffer; + } + + pub fn read_foreign(self: Storage, path: []const u8, offset: u64, comptime Type: type) AccessError!?Type { const decoded = (try self.read_native(path, offset, Type)) orelse { return null; }; @@ -37,7 +68,7 @@ pub const Storage = struct { }; } - pub fn read_native(self: Storage, path: []const u8, offset: usize, comptime Type: type) Error!?Type { + pub fn read_native(self: Storage, path: []const u8, offset: u64, comptime Type: type) AccessError!?Type { var buffer = @as([@sizeOf(Type)]io.Byte, undefined); if (try self.vtable.read(self.userdata, path, offset, &buffer) != buffer.len) { @@ -47,6 +78,10 @@ pub const Storage = struct { return @as(*align(1) const Type, @ptrCast(&buffer)).*; } + pub fn stat(self: Storage, path: []const u8) AccessError!Stat { + return self.vtable.stat(self.userdata, path); + } + pub const read_little = switch (native_endian) { .little => read_native, .big => read_foreign, @@ -60,7 +95,7 @@ pub const Storage = struct { pub const bundle = init: { const Bundle = struct { - fn full_path(path: []const u8) Error![4095:0]u8 { + fn full_path(path: []const u8) AccessError![4095:0]u8 { var buffer = [_:0]u8{0} ** 4095; _ = std.fs.cwd().realpath(path, &buffer) catch { @@ -70,7 +105,7 @@ pub const bundle = init: { return buffer; } - fn read(_: *anyopaque, path: []const u8, offset: usize, output: []io.Byte) Error!usize { + fn read(_: *anyopaque, path: []const u8, offset: usize, output: []io.Byte) AccessError!usize { var file = std.fs.openFileAbsoluteZ(&(try full_path(path)), .{.mode = .read_only}) catch |open_error| { return switch (open_error) { error.FileNotFound => error.FileNotFound, @@ -89,7 +124,7 @@ pub const bundle = init: { return file.read(output) catch error.FileInaccessible; } - fn stat(_: *anyopaque, path: []const u8) Error!Stat { + fn stat(_: *anyopaque, path: []const u8) AccessError!Stat { const file_stat = get: { var file = std.fs.openFileAbsoluteZ(&(try full_path(path)), .{.mode = .read_only}) catch |open_error| { return switch (open_error) { diff --git a/src/coral/io.zig b/src/coral/io.zig index 545b64f..e220a55 100644 --- a/src/coral/io.zig +++ b/src/coral/io.zig @@ -138,16 +138,6 @@ pub fn alloc_read(input: coral.io.Reader, allocator: std.mem.Allocator) []coral. pub const bits_per_byte = 8; -pub fn bytes_of(value: anytype) []const Byte { - const pointer_info = @typeInfo(@TypeOf(value)).Pointer; - - return switch (pointer_info.size) { - .One => @as([*]const Byte, @ptrCast(value))[0 .. @sizeOf(pointer_info.child)], - .Slice => @as([*]const Byte, @ptrCast(value.ptr))[0 .. @sizeOf(pointer_info.child) * value.len], - else => @compileError("`value` must be single-element pointer or slice type"), - }; -} - pub fn skip_n(input: Reader, distance: u64) Error!void { var buffer = @as([512]coral.Byte, undefined); var remaining = distance; @@ -163,16 +153,6 @@ pub fn skip_n(input: Reader, distance: u64) Error!void { } } -pub fn slice_sentineled(comptime Sentinel: type, comptime sen: Sentinel, ptr: [*:sen]const Sentinel) [:sen]const Sentinel { - var len = @as(usize, 0); - - while (ptr[len] != sen) { - len += 1; - } - - return ptr[0 .. len:sen]; -} - pub fn stream_all(input: Reader, output: Writer) Error!usize { var buffer = @as([512]coral.Byte, undefined); var copied = @as(usize, 0); diff --git a/src/coral/map.zig b/src/coral/map.zig index 6ac848e..eb7de4d 100644 --- a/src/coral/map.zig +++ b/src/coral/map.zig @@ -147,7 +147,7 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime traits: Traits( self.* = undefined; } - pub fn get_ptr(self: Self, key: Key) ?*Value { + pub fn get(self: Self, key: Key) ?*Value { if (self.len == 0) { return null; } @@ -169,14 +169,6 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime traits: Traits( return null; } - pub fn get(self: Self, key: Key) ?Value { - if (self.get_ptr(key)) |value| { - return value.*; - } - - return null; - } - pub fn emplace(self: *Self, key: Key, value: Value) std.mem.Allocator.Error!bool { try self.rehash(load_max); diff --git a/src/coral/slices.zig b/src/coral/slices.zig index 5a0953f..2a4f80a 100644 --- a/src/coral/slices.zig +++ b/src/coral/slices.zig @@ -115,24 +115,6 @@ pub fn get_ptr(slice: anytype, index: usize) ?ElementPtr(@TypeOf(slice)) { return &slice[index]; } -pub fn index_of(haystack: anytype, offset: usize, needle: std.meta.Child(@TypeOf(haystack))) ?usize { - for (offset .. haystack.len) |i| { - if (haystack[i] == needle) { - return i; - } - } - - return null; -} - -pub fn index_of_any(haystack: anytype, offset: usize, needle: []const std.meta.Child(@TypeOf(haystack))) ?usize { - return std.mem.indexOfAnyPos(std.meta.Child(@TypeOf(haystack)), haystack, offset, needle); -} - -pub fn index_of_seq(haystack: anytype, offset: usize, needle: []const std.meta.Child(@TypeOf(haystack))) ?usize { - return std.mem.indexOfPos(std.meta.Child(@TypeOf(haystack)), haystack, offset, needle); -} - pub fn parallel_alloc(comptime Element: type, allocator: std.mem.Allocator, n: usize) std.mem.Allocator.Error!Parallel(Element) { const alignment = @alignOf(Element); const Slices = Parallel(Element); diff --git a/src/coral/utf8.zig b/src/coral/utf8.zig index 4992107..44000e7 100644 --- a/src/coral/utf8.zig +++ b/src/coral/utf8.zig @@ -6,17 +6,14 @@ const io = @import("./io.zig"); const std = @import("std"); -pub fn alloc_formatted(allocator: std.mem.Allocator, comptime format: []const u8, args: anytype) std.mem.Allocator.Error![]coral.Byte { - var buffer = coral.Stack(coral.Byte){.allocator = allocator}; +pub fn alloc_formatted(allocator: std.mem.Allocator, comptime format: []const u8, args: anytype) std.mem.Allocator.Error![:0]u8 { const formatted_len = count_formatted(format, args); + const buffer = try allocator.allocSentinel(u8, formatted_len, 0); - try buffer.grow(formatted_len); + errdefer allocator.free(buffer); - errdefer buffer.deinit(); - - print_formatted(buffer.writer(), format, args) catch unreachable; - - return buffer.to_allocation(formatted_len, 0); + // TODO: This is dumb. + return @constCast(print_formatted(buffer, format, args) catch unreachable); } pub fn count_formatted(comptime format: []const u8, args: anytype) usize { @@ -27,7 +24,7 @@ pub fn count_formatted(comptime format: []const u8, args: anytype) usize { return count.written; } -pub fn print_formatted(buffer: [:0]coral.io.Byte, comptime format: []const u8, args: anytype) io.Error![:0]u8 { +pub fn print_formatted(buffer: [:0]coral.io.Byte, comptime format: []const u8, args: anytype) io.Error![:0]const u8 { const Seekable = struct { buffer: []coral.io.Byte, cursor: usize, @@ -143,8 +140,8 @@ noinline fn print_formatted_value(writer: io.Writer, value: anytype) io.Error!vo const Value = @TypeOf(value); return switch (@typeInfo(Value)) { - .Int => ascii.DecimalFormat.default.print(writer, value), - .Float => ascii.DecimalFormat.default.print(writer, value), + .Int => ascii.DecimalFormat.default.format(writer, value), + .Float => ascii.DecimalFormat.default.format(writer, value), .Enum => io.print(writer, @tagName(value)), .Pointer => |pointer| switch (pointer.size) { diff --git a/src/flow/dag.zig b/src/flow/dag.zig index ea35bc0..931a49f 100644 --- a/src/flow/dag.zig +++ b/src/flow/dag.zig @@ -80,7 +80,7 @@ pub fn Graph(comptime Payload: type) type { return false; }; - if (coral.slices.index_of(edges.values, 0, edge_node) == null) { + if (std.mem.indexOfScalar(Node, edges.values, edge_node) == null) { try edges.push_grow(edge_node); } diff --git a/src/flow/states.zig b/src/flow/states.zig index d4a0e2e..d5874d5 100644 --- a/src/flow/states.zig +++ b/src/flow/states.zig @@ -18,7 +18,7 @@ pub const Table = struct { } pub fn get(self: Table, comptime Resource: type) ?*Resource { - if (self.table.get_ptr(type_id(Resource))) |entry| { + if (self.table.get(type_id(Resource))) |entry| { return @ptrCast(@alignCast(entry.ptr)); } @@ -42,7 +42,7 @@ pub const Table = struct { const Value = @TypeOf(value); const value_id = type_id(Value); - if (self.table.get_ptr(value_id)) |entry| { + if (self.table.get(value_id)) |entry| { @as(*Value, @ptrCast(@alignCast(entry.ptr))).* = value; } else { const resource_allocator = self.arena.allocator(); diff --git a/src/flow/system.zig b/src/flow/system.zig index 25dce4d..01e4a17 100644 --- a/src/flow/system.zig +++ b/src/flow/system.zig @@ -49,16 +49,16 @@ pub const BindContext = struct { } const read_write_resource_nodes = lazily_create: { - break: lazily_create self.systems.read_write_resource_id_nodes.get_ptr(id) orelse insert: { + break: lazily_create self.systems.read_write_resource_id_nodes.get(id) orelse insert: { std.debug.assert(try self.systems.read_write_resource_id_nodes.emplace(id, .{ .allocator = coral.heap.allocator, })); - break: insert self.systems.read_write_resource_id_nodes.get_ptr(id).?; + break: insert self.systems.read_write_resource_id_nodes.get(id).?; }; }; - if (coral.slices.index_of(read_write_resource_nodes.values, 0, self.node) == null) { + if (std.mem.indexOfScalar(dag.Node, read_write_resource_nodes.values, self.node) == null) { try read_write_resource_nodes.push_grow(self.node); } @@ -77,16 +77,16 @@ pub const BindContext = struct { } const read_only_resource_nodes = lazily_create: { - break: lazily_create self.systems.read_only_resource_id_nodes.get_ptr(id) orelse insert: { + break: lazily_create self.systems.read_only_resource_id_nodes.get(id) orelse insert: { std.debug.assert(try self.systems.read_only_resource_id_nodes.emplace(id, .{ .allocator = coral.heap.allocator, })); - break: insert self.systems.read_only_resource_id_nodes.get_ptr(id).?; + break: insert self.systems.read_only_resource_id_nodes.get(id).?; }; }; - if (coral.slices.index_of(read_only_resource_nodes.values, 0, self.node) == null) { + if (std.mem.indexOfScalar(dag.Node, read_only_resource_nodes.values, self.node) == null) { try read_only_resource_nodes.push_grow(self.node); } @@ -415,12 +415,12 @@ pub const Schedule = struct { const nodes = lazily_create: { const system_id = @intFromPtr(info); - break: lazily_create self.system_id_nodes.get_ptr(system_id) orelse insert: { + break: lazily_create self.system_id_nodes.get(system_id) orelse insert: { std.debug.assert(try self.system_id_nodes.emplace(system_id, .{ .allocator = self.system_id_nodes.allocator, })); - break: insert self.system_id_nodes.get_ptr(system_id).?; + break: insert self.system_id_nodes.get(system_id).?; }; }; diff --git a/src/main.zig b/src/main.zig index b49021c..f345239 100644 --- a/src/main.zig +++ b/src/main.zig @@ -5,10 +5,9 @@ const std = @import("std"); const ona = @import("ona"); const Actors = struct { - instances: coral.stack.Sequential(ona.gfx.Point2D) = .{.allocator = coral.heap.allocator}, - quad_mesh_2d: ?*ona.gfx.Handle = null, - body_texture: ?*ona.gfx.Handle = null, - render_texture: ?*ona.gfx.Handle = null, + instances: coral.stack.Sequential(@Vector(2, f32)) = .{.allocator = coral.heap.allocator}, + body_texture: ona.gfx.Texture = .default, + render_texture: ona.gfx.Texture = .default, }; const Player = struct { @@ -23,20 +22,17 @@ pub fn main() !void { }); } -fn load(display: ona.Write(ona.gfx.Display), actors: ona.Write(Actors), assets: ona.Write(ona.gfx.Assets)) !void { - display.res.width, display.res.height = .{1280, 720}; - actors.res.body_texture = try assets.res.create_from_file(coral.files.bundle, "actor.bmp"); - actors.res.quad_mesh_2d = try assets.res.create_quad_mesh_2d(@splat(1)); +fn load(config: ona.Write(ona.gfx.Config), actors: ona.Write(Actors), assets: ona.Write(ona.gfx.Assets)) !void { + config.res.width, config.res.height = .{1280, 720}; + actors.res.body_texture = try assets.res.load_texture_file(coral.files.bundle, "actor.bmp"); - actors.res.render_texture = try assets.res.context.create(.{ - .texture = .{ - .format = .rgba8, + actors.res.render_texture = try assets.res.load_texture(.{ + .format = .rgba8, - .access = .{ - .render = .{ - .width = display.res.width, - .height = display.res.height, - }, + .access = .{ + .render = .{ + .width = config.res.width, + .height = config.res.height, }, }, }); @@ -48,41 +44,41 @@ fn exit(actors: ona.Write(Actors)) void { actors.res.instances.deinit(); } -fn render(queue: ona.gfx.Queue, actors: ona.Write(Actors), display: ona.Read(ona.gfx.Display)) !void { - try queue.commands.append(.{.target = .{ - .texture = actors.res.render_texture.?, +fn render(commands: ona.gfx.Commands, actors: ona.Write(Actors), config: ona.Read(ona.gfx.Config)) !void { + try commands.set_target(.{ + .texture = actors.res.render_texture, .clear_color = .{0, 0, 0, 0}, .clear_depth = 0, - }}); + .clear_stencil = 0, + }); for (actors.res.instances.values) |instance| { - try queue.commands.append(.{.instance_2d = .{ - .mesh_2d = actors.res.quad_mesh_2d.?, - .texture = actors.res.body_texture.?, + try commands.draw_texture(.{ + .texture = actors.res.body_texture, .transform = .{ .origin = instance, .xbasis = .{64, 0}, .ybasis = .{0, 64}, }, - }}); + }); } - try queue.commands.append(.{.target = .{ + try commands.set_target(.{ .clear_color = null, .clear_depth = null, - }}); + .clear_stencil = null, + }); - try queue.commands.append(.{.instance_2d = .{ - .mesh_2d = actors.res.quad_mesh_2d.?, - .texture = actors.res.render_texture.?, + try commands.draw_texture(.{ + .texture = actors.res.render_texture, .transform = .{ - .origin = .{@floatFromInt(display.res.width / 2), @floatFromInt(display.res.height / 2)}, - .xbasis = .{@floatFromInt(display.res.width), 0}, - .ybasis = .{0, @floatFromInt(display.res.height)}, + .origin = .{@floatFromInt(config.res.width / 2), @floatFromInt(config.res.height / 2)}, + .xbasis = .{@floatFromInt(config.res.width), 0}, + .ybasis = .{0, @floatFromInt(config.res.height)}, }, - }}); + }); } fn update(player: ona.Read(Player), actors: ona.Write(Actors), mapping: ona.Read(ona.act.Mapping)) !void { diff --git a/src/ona/gfx.zig b/src/ona/gfx.zig index 912b24d..8a6780f 100644 --- a/src/ona/gfx.zig +++ b/src/ona/gfx.zig @@ -1,40 +1,89 @@ const App = @import("./App.zig"); +pub const colors = @import("./gfx/colors.zig"); + const coral = @import("coral"); -const device = @import("./gfx/device.zig"); +const bmp = @import("./gfx/bmp.zig"); const ext = @import("./ext.zig"); const flow = @import("flow"); -const formats = @import("./gfx/formats.zig"); +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 std = @import("std"); -pub const AssetFormat = struct { - extension: []const u8, - file_desc: *const FileDesc, +pub const Assets = struct { + window: *ext.SDL_Window, + texture_formats: coral.stack.Sequential(TextureFormat), + staging_arena: std.heap.ArenaAllocator, + frame_rendered: std.Thread.ResetEvent = .{}, - pub const Error = std.mem.Allocator.Error || coral.files.Error || error { + pub const LoadError = std.mem.Allocator.Error; + + pub const LoadFileError = LoadError || coral.files.AccessError || error { FormatUnsupported, }; - pub const FileDesc = fn (*std.heap.ArenaAllocator, coral.files.Storage, []const u8) Error!Desc; -}; + pub const TextureFormat = struct { + extension: []const u8, + load_file: *const fn (*std.heap.ArenaAllocator, coral.files.Storage, []const u8) LoadFileError!Texture.Desc, + }; -pub const Assets = struct { - context: device.Context, - formats: coral.stack.Sequential(AssetFormat), - staging_arena: std.heap.ArenaAllocator, + fn deinit(self: *Assets) void { + rendering.enqueue_work(.shutdown); + self.staging_arena.deinit(); + self.texture_formats.deinit(); + } - pub fn create_from_file( - self: *Assets, - storage: coral.files.Storage, - path: []const u8, - ) (OpenError || AssetFormat.Error)!*Handle { + fn init() !Assets { + 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); + } + + try rendering.startup(window); + + return .{ + .staging_arena = std.heap.ArenaAllocator.init(coral.heap.allocator), + .texture_formats = .{.allocator = coral.heap.allocator}, + .window = window, + }; + } + + pub fn load_texture(_: *Assets, desc: Texture.Desc) std.mem.Allocator.Error!Texture { + var loaded = coral.asyncio.Future(std.mem.Allocator.Error!Texture){}; + + rendering.enqueue_work(.{ + .load_texture = .{ + .desc = desc, + .loaded = &loaded, + }, + }); + + return loaded.get().*; + } + + pub fn load_texture_file(self: *Assets, storage: coral.files.Storage, path: []const u8) LoadFileError!Texture { defer { const max_cache_size = 536870912; @@ -43,107 +92,62 @@ pub const Assets = struct { } } - for (self.formats.values) |format| { + for (self.texture_formats.values) |format| { if (!std.mem.endsWith(u8, path, format.extension)) { continue; } - return self.context.create(try format.file_desc(&self.staging_arena, storage, path)); + return self.load_texture(try format.load_file(&self.staging_arena, storage, path)); } return error.FormatUnsupported; } - pub fn create_quad_mesh_2d(self: *Assets, extents: Point2D) OpenError!*Handle { - const width, const height = extents / @as(Point2D, @splat(2)); - - return self.context.create(.{ - .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 thread_restriction = .main; }; -pub const Color = @Vector(4, f32); +pub const Commands = struct { + list: *rendering.Commands.List, -pub const Command = union (enum) { - instance_2d: Instance2D, - target: Target, - - pub const Instance2D = struct { - texture: *Handle, - mesh_2d: *Handle, - transform: Transform2D, + pub const State = struct { + commands: *rendering.Commands, }; - pub const Target = struct { - texture: ?*Handle = null, - clear_color: ?Color, - clear_depth: ?f32, - }; + pub fn bind(_: flow.system.BindContext) std.mem.Allocator.Error!State { + var created_commands = coral.asyncio.Future(std.mem.Allocator.Error!*rendering.Commands){}; + + rendering.enqueue_work(.{ + .create_commands = .{ + .created = &created_commands, + }, + }); + + return .{ + .commands = try created_commands.get().*, + }; + } + + pub fn init(state: *State) Commands { + return .{ + .list = state.commands.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 set_target(self: Commands, command: rendering.Command.SetTarget) std.mem.Allocator.Error!void { + try self.list.append(.{.set_target = command}); + } }; -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 { - format: Format, - access: Access, - - pub const Access = union (enum) { - static: struct { - width: u16, - data: []const coral.io.Byte, - }, - - 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, - }; - } - }; - }; -}; - -pub const Display = struct { +pub const Config = struct { width: u16 = 1280, height: u16 = 720, - clear_color: Color = colors.black, + clear_color: lina.Color = colors.black, }; -pub const Handle = opaque {}; - pub const Input = union (enum) { key_up: Key, key_down: Key, @@ -160,57 +164,6 @@ pub const Input = union (enum) { }; }; -pub const OpenError = std.mem.Allocator.Error || error { - -}; - -pub const Point2D = @Vector(2, f32); - -pub const Queue = struct { - commands: *device.RenderList, - - pub const State = struct { - command_index: usize, - }; - - pub fn bind(_: flow.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)); - - 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()); - - 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 Rect = struct { left: f32, top: f32, @@ -218,32 +171,7 @@ pub const Rect = struct { bottom: f32, }; -pub const Transform2D = extern struct { - xbasis: Point2D = .{1, 0}, - ybasis: Point2D = .{0, 1}, - origin: Point2D = @splat(0), -}; - -const builtin_formats = [_]AssetFormat{ - .{ - .extension = "bmp", - .file_desc = formats.bmp_file_desc, - }, -}; - -pub const colors = struct { - pub const black = greyscale(0); - - pub const white = greyscale(1); - - pub fn greyscale(v: f32) Color { - return .{v, v, v, 1}; - } - - pub fn rgb(r: f32, g: f32, b: f32) Color { - return .{r, g, b, 1}; - } -}; +pub const Texture = handles.Texture; pub fn poll(app: flow.Write(App), inputs: msg.Send(Input)) !void { var event = @as(ext.SDL_Event, undefined); @@ -263,40 +191,71 @@ pub fn setup(world: *flow.World, events: App.Events) (error {Unsupported} || std return error.Unsupported; } - var context = try device.Context.init(); + const assets = create: { + var assets = try Assets.init(); - errdefer context.deinit(); + errdefer { + assets.deinit(); + } - var registered_formats = coral.stack.Sequential(AssetFormat){.allocator = coral.heap.allocator}; + break: create try world.set_get_resource(assets); + }; - errdefer registered_formats.deinit(); + assets.frame_rendered.set(); - try registered_formats.grow(builtin_formats.len); - std.debug.assert(registered_formats.push_all(&builtin_formats)); + errdefer { + assets.deinit(); + } - try world.set_resource(Assets{ - .staging_arena = std.heap.ArenaAllocator.init(coral.heap.allocator), - .formats = registered_formats, - .context = context, - }); + const builtin_texture_formats = [_]Assets.TextureFormat{ + .{ + .extension = "bmp", + .load_file = bmp.load_file, + }, + }; - try world.set_resource(Display{}); + for (builtin_texture_formats) |format| { + try assets.texture_formats.push_grow(format); + } + + try world.set_resource(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"}); } pub fn stop(assets: flow.Write(Assets)) void { - assets.res.staging_arena.deinit(); - assets.res.formats.deinit(); - assets.res.context.deinit(); + assets.res.deinit(); } -pub fn synchronize(assets: flow.Write(Assets), display: flow.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, +pub fn synchronize(assets: flow.Write(Assets), config: flow.Read(Config)) !void { + assets.res.frame_rendered.wait(); + assets.res.frame_rendered.reset(); + + var commands_swapped = std.Thread.ResetEvent{}; + + rendering.enqueue_work(.{ + .rotate_commands = .{ + .finished = &commands_swapped, + }, + }); + + var display_width, var display_height = [_]c_int{0, 0}; + + ext.SDL_GL_GetDrawableSize(assets.res.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); + } + + 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, + }, }); } diff --git a/src/ona/gfx/formats.zig b/src/ona/gfx/bmp.zig similarity index 83% rename from src/ona/gfx/formats.zig rename to src/ona/gfx/bmp.zig index 98d5952..b761a2d 100644 --- a/src/ona/gfx/formats.zig +++ b/src/ona/gfx/bmp.zig @@ -1,14 +1,10 @@ const coral = @import("coral"); -const gfx = @import("../gfx.zig"); +const handles = @import("./handles.zig"); const std = @import("std"); -pub fn bmp_file_desc( - arena: *std.heap.ArenaAllocator, - storage: coral.files.Storage, - path: []const u8, -) gfx.AssetFormat.Error!gfx.Desc { +pub fn load_file(arena: *std.heap.ArenaAllocator, storage: coral.files.Storage, path: []const u8) !handles.Texture.Desc { const header = try storage.read_little(path, 0, extern struct { type: [2]u8 align (1), file_size: u32 align (1), @@ -51,7 +47,7 @@ pub fn bmp_file_desc( while (buffer_offset < pixels.len) { const line = pixels[buffer_offset .. buffer_offset + byte_stride]; - if (try storage.read_bytes(path, file_offset, line) != byte_stride) { + if (try storage.read(path, line, file_offset) != byte_stride) { return error.FormatUnsupported; } @@ -71,15 +67,13 @@ pub fn bmp_file_desc( } return .{ - .texture = .{ - .format = .rgba8, + .format = .rgba8, - .access = .{ - .static = .{ - .width = pixel_width, - .data = pixels, - }, + .access = .{ + .static = .{ + .width = pixel_width, + .data = pixels, }, - } + }, }; } diff --git a/src/ona/gfx/colors.zig b/src/ona/gfx/colors.zig new file mode 100644 index 0000000..1682d07 --- /dev/null +++ b/src/ona/gfx/colors.zig @@ -0,0 +1,13 @@ +const lina = @import("./lina.zig"); + +pub const black = greyscale(0); + +pub const white = greyscale(1); + +pub fn greyscale(v: f32) lina.Color { + return .{v, v, v, 1}; +} + +pub fn rgb(r: f32, g: f32, b: f32) lina.Color { + return .{r, g, b, 1}; +} diff --git a/src/ona/gfx/commands.zig b/src/ona/gfx/commands.zig deleted file mode 100644 index cece28c..0000000 --- a/src/ona/gfx/commands.zig +++ /dev/null @@ -1,80 +0,0 @@ -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 deleted file mode 100644 index b5b9eca..0000000 --- a/src/ona/gfx/device.zig +++ /dev/null @@ -1,847 +0,0 @@ -const commands = @import("./commands.zig"); - -const coral = @import("coral"); - -const ext = @import("../ext.zig"); - -const gfx = @import("../gfx.zig"); - -const sokol = @import("sokol"); - -const spirv = @import("./spirv.zig"); - -const std = @import("std"); - -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 close_commands = self.loop.closes.pending(); - - std.debug.assert(close_commands.stack.cap > close_commands.stack.len()); - close_commands.append(handle) catch unreachable; - } - - pub fn create(self: *Context, desc: gfx.Desc) gfx.OpenError!*gfx.Handle { - const resource = try coral.heap.allocator.create(Resource); - - errdefer coral.heap.allocator.destroy(resource); - - try self.loop.creations.pending().append(.{ - .resource = resource, - .desc = desc, - }); - - const pending_destroys = self.loop.destroys.pending(); - - if (pending_destroys.stack.len() == pending_destroys.stack.cap) { - try pending_destroys.stack.grow(1); - } - - return @ptrCast(resource); - } - - pub fn deinit(self: *Context) void { - 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); - - 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 submit(self: *Context, submission: Submission) void { - self.loop.finished.wait(); - - defer self.loop.ready.post(); - - for (submission.renders) |*render| { - render.swap(); - } - - self.loop.creations.swap(); - self.loop.destroys.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 Frame = struct { - projection: Projection = .{@splat(0), @splat(0), @splat(0), @splat(0)}, - flushed_batch_count: usize = 0, - pushed_batch_count: usize = 0, - render_passes: usize = 0, - mesh: ?*gfx.Handle = null, - texture: ?*gfx.Handle = null, - material: ?*gfx.Handle = null, -}; - -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 = &.{}, - destroys: DestroyChain = DestroyChain.init(coral.heap.allocator), - creations: CreateChain = CreateChain.init(coral.heap.allocator), - has_resource_head: ?*Resource = null, - has_resource_tail: ?*Resource = null, - - const AtomicBool = std.atomic.Value(bool); - - const CreateCommand = struct { - resource: *Resource, - desc: gfx.Desc, - - fn clone(command: CreateCommand, arena: *std.heap.ArenaAllocator) std.mem.Allocator.Error!CreateCommand { - const allocator = arena.allocator(); - - return .{ - .desc = switch (command.desc) { - .texture => |texture| .{ - .texture = .{ - .access = switch (texture.access) { - .static => |static| .{ - .static = .{ - .data = try allocator.dupe(coral.io.Byte, static.data), - .width = static.width, - }, - }, - - .render => |render| .{ - .render = .{ - .width = render.width, - .height = render.height, - }, - }, - }, - - .format = texture.format, - }, - }, - - .mesh_2d => |mesh_2d| .{ - .mesh_2d = .{ - .vertices = try allocator.dupe(gfx.Desc.Mesh2D.Vertex, mesh_2d.vertices), - .indices = try allocator.dupe(u16, mesh_2d.indices), - }, - }, - }, - - .resource = command.resource, - }; - } - }; - - const CreateChain = commands.Chain(CreateCommand, CreateCommand.clone); - - const DestroyChain = commands.Chain(*Resource, null); - - fn consume_creations(self: *Loop) std.mem.Allocator.Error!void { - const create_commands = self.creations.submitted(); - - defer create_commands.clear(); - - for (create_commands.stack.values) |command| { - errdefer coral.heap.allocator.destroy(command.resource); - - command.resource.* = .{ - .payload = switch (command.desc) { - .texture => |texture| init: { - const pixel_format = switch (texture.format) { - .rgba8 => sokol.gfx.PixelFormat.RGBA8, - .bgra8 => sokol.gfx.PixelFormat.BGRA8, - }; - - switch (texture.access) { - .static => |static| { - break: init .{ - .texture = .{ - .image = sokol.gfx.makeImage(.{ - .width = static.width, - .height = @intCast(static.data.len / (static.width * texture.format.byte_size())), - .pixel_format = pixel_format, - - .data = .{ - .subimage = get: { - var subimage = [_][16]sokol.gfx.Range{.{.{}} ** 16} ** 6; - - subimage[0][0] = sokol.gfx.asRange(static.data); - - break: get subimage; - }, - }, - }), - - .sampler = sokol.gfx.makeSampler(.{}), - }, - }; - }, - - .render => |render| { - const color_image = sokol.gfx.makeImage(.{ - .width = render.width, - .height = render.height, - .render_target = true, - .pixel_format = pixel_format, - }); - - const depth_image = sokol.gfx.makeImage(.{ - .width = render.width, - .height = render.height, - .render_target = true, - .pixel_format = .DEPTH_STENCIL, - }); - - break: init .{ - .render_target = .{ - .attachments = sokol.gfx.makeAttachments(.{ - .colors = get: { - var attachments = [_]sokol.gfx.AttachmentDesc{.{}} ** 4; - - attachments[0] = .{ - .image = color_image, - }; - - break: get attachments; - }, - - .depth_stencil = .{ - .image = depth_image, - }, - }), - - .sampler = sokol.gfx.makeSampler(.{}), - .color_image = color_image, - .depth_image = depth_image, - .width = render.width, - .height = render.height, - }, - }; - }, - } - }, - - .mesh_2d => |mesh_2d| init: { - if (mesh_2d.indices.len > std.math.maxInt(u32)) { - return error.OutOfMemory; - } - - break: init .{ - .mesh_2d = .{ - .index_buffer = sokol.gfx.makeBuffer(.{ - .data = sokol.gfx.asRange(mesh_2d.indices), - .type = .INDEXBUFFER, - }), - - .vertex_buffer = sokol.gfx.makeBuffer(.{ - .data = sokol.gfx.asRange(mesh_2d.vertices), - .type = .VERTEXBUFFER, - }), - - .index_count = @intCast(mesh_2d.indices.len), - }, - }; - }, - }, - - .has_prev = self.has_resource_tail, - .has_next = null, - }; - - if (self.has_resource_tail) |resource_tail| { - resource_tail.has_next = command.resource; - } else { - std.debug.assert(self.has_resource_head == null); - - self.has_resource_head = command.resource; - } - - self.has_resource_tail = command.resource; - } - } - - fn consume_destroys(self: *Loop) std.mem.Allocator.Error!void { - const destroy_commands = self.destroys.submitted(); - - defer destroy_commands.clear(); - - for (destroy_commands.stack.values) |resource| { - defer coral.heap.allocator.destroy(resource); - - resource.destroy(); - - if (resource.has_prev) |resource_prev| { - resource_prev.has_next = resource.has_next; - } - - if (resource.has_next) |resource_next| { - resource_next.has_prev = resource.has_prev; - } - } - } - - fn deinit(self: *Loop) void { - self.destroys.deinit(); - self.creations.deinit(); - } - - 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_2d = try Rendering2D.init(); - - defer rendering_2d.deinit(); - - self.finished.post(); - - while (self.is_running.load(.monotonic)) { - self.ready.wait(); - - defer self.finished.post(); - - try self.consume_creations(); - - var frame = Frame{}; - - rendering_2d.set_target(&frame, window, .{ - .clear_color = self.clear_color, - .clear_depth = 0, - }); - - if (self.renders.len != 0) { - const renders_tail_index = self.renders.len - 1; - - for (self.renders[0 .. renders_tail_index]) |*render| { - const render_commands = render.submitted(); - - defer render_commands.clear(); - - for (render_commands.stack.values) |command| { - try switch (command) { - .instance_2d => |instance_2d| rendering_2d.batch(&frame, instance_2d), - .target => |target| rendering_2d.set_target(&frame, window, target), - }; - } - - rendering_2d.set_target(&frame, window, .{ - .clear_color = null, - .clear_depth = null, - }); - } - - const render_commands = self.renders[renders_tail_index].submitted(); - - defer render_commands.clear(); - - for (render_commands.stack.values) |command| { - try switch (command) { - .instance_2d => |instance_2d| rendering_2d.batch(&frame, instance_2d), - .target => |target| rendering_2d.set_target(&frame, window, target), - }; - } - - rendering_2d.flush(&frame); - } - - sokol.gfx.endPass(); - sokol.gfx.commit(); - ext.SDL_GL_SwapWindow(window); - - try self.consume_destroys(); - } - - var has_next_resource = self.has_resource_head; - - while (has_next_resource) |resource| { - std.log.info("destroying remaining gfx device resource {x}", .{@intFromPtr(resource)}); - resource.destroy(); - - has_next_resource = resource.has_next; - - coral.heap.allocator.destroy(resource); - } - } -}; - -const Projection = [4]@Vector(4, f32); - -pub const RenderChain = commands.Chain(gfx.Command, clone_command); - -pub const RenderList = commands.List(gfx.Command, clone_command); - -const Rendering2D = struct { - batching_pipeline: sokol.gfx.Pipeline, - batching_buffers: coral.stack.Sequential(sokol.gfx.Buffer), - - const Instance = 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, - }; - - fn batch(self: *Rendering2D, frame: *Frame, instance: gfx.Command.Instance2D) std.mem.Allocator.Error!void { - if (instance.mesh_2d != frame.mesh or instance.texture != frame.texture) { - self.flush(frame); - } - - frame.mesh = instance.mesh_2d; - frame.texture = instance.texture; - - const has_filled_buffer = (frame.pushed_batch_count % instances_per_buffer) == 0; - const pushed_buffer_count = frame.pushed_batch_count / instances_per_buffer; - - if (has_filled_buffer and pushed_buffer_count == self.batching_buffers.len()) { - var name_buffer = [_:0]u8{0} ** 64; - - const name_view = std.fmt.bufPrint(&name_buffer, "instance 2d buffer #{}", .{self.batching_buffers.len()}) catch { - unreachable; - }; - - const instance_buffer = sokol.gfx.makeBuffer(.{ - .label = name_view.ptr, - .size = @sizeOf(Instance) * instances_per_buffer, - .usage = .STREAM, - }); - - errdefer sokol.gfx.destroyBuffer(instance_buffer); - - try self.batching_buffers.push_grow(instance_buffer); - } - - _ = sokol.gfx.appendBuffer(self.batching_buffers.get().?, sokol.gfx.asRange(&Instance{ - .transform = instance.transform, - })); - - frame.pushed_batch_count += 1; - } - - fn deinit(self: *Rendering2D) void { - for (self.batching_buffers.values) |buffer| { - sokol.gfx.destroyBuffer(buffer); - } - - self.batching_buffers.deinit(); - sokol.gfx.destroyPipeline(self.batching_pipeline); - } - - fn flush(self: *Rendering2D, frame: *Frame) void { - const unflushed_count = frame.pushed_batch_count - frame.flushed_batch_count; - - if (unflushed_count == 0) { - return; - } - - sokol.gfx.applyPipeline(self.batching_pipeline); - sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&frame.projection)); - - const mesh_2d = resource_cast(frame.mesh.?).payload.mesh_2d; - - const texture_image, const texture_sampler = switch (resource_cast(frame.texture.?).payload) { - .texture => |texture| .{texture.image, texture.sampler}, - .render_target => |render_target| .{render_target.color_image, render_target.sampler}, - else => unreachable, - }; - - var bindings = sokol.gfx.Bindings{ - .vertex_buffers = get: { - var buffers = [_]sokol.gfx.Buffer{.{}} ** 8; - - buffers[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; - }, - }, - - .vs = .{ - .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_batch_count < frame.pushed_batch_count) { - const buffer_index = frame.flushed_batch_count / instances_per_buffer; - const buffer_offset = frame.flushed_batch_count % instances_per_buffer; - const instances_to_flush = @min(instances_per_buffer - buffer_offset, unflushed_count); - - bindings.vertex_buffers[buffer_indices.instance] = self.batching_buffers.values[buffer_index]; - bindings.vertex_buffer_offsets[buffer_indices.instance] = @intCast(@sizeOf(Instance) * buffer_offset); - - sokol.gfx.applyBindings(bindings); - sokol.gfx.draw(0, mesh_2d.index_count, @intCast(instances_to_flush)); - - frame.flushed_batch_count += instances_to_flush; - } - } - - fn init() spirv.Error!Rendering2D { - sokol.gfx.setup(.{ - .environment = .{ - .defaults = .{ - .color_format = .RGBA8, - .depth_format = .DEPTH_STENCIL, - .sample_count = 1, - }, - }, - - .logger = .{ - .func = sokol.log.func, - }, - }); - - var spirv_unit = try spirv.Unit.init(); - - defer spirv_unit.deinit(); - - const shader_spirv = spirv.embed_shader("./shaders/render_2d.spv"); - - try spirv_unit.compile(shader_spirv, .vertex); - - std.log.info("{s}\n", .{spirv_unit.shader_desc.vs.source}); - - try spirv_unit.compile(shader_spirv, .fragment); - - std.log.info("{s}\n", .{spirv_unit.shader_desc.fs.source}); - - return .{ - .batching_pipeline = sokol.gfx.makePipeline(.{ - .label = "2D drawing pipeline", - - .layout = .{ - .attrs = get: { - var attrs = [_]sokol.gfx.VertexAttrState{.{}} ** 16; - - attrs[0] = .{ - .format = .FLOAT2, - .buffer_index = buffer_indices.mesh, - }; - - attrs[1] = .{ - .format = .FLOAT2, - .buffer_index = buffer_indices.mesh, - }; - - attrs[2] = .{ - .format = .FLOAT2, - .buffer_index = buffer_indices.instance, - }; - - attrs[3] = .{ - .format = .FLOAT2, - .buffer_index = buffer_indices.instance, - }; - - attrs[4] = .{ - .format = .FLOAT2, - .buffer_index = buffer_indices.instance, - }; - - attrs[5] = .{ - .format = .UBYTE4N, - .buffer_index = buffer_indices.instance, - }; - - attrs[6] = .{ - .format = .FLOAT, - .buffer_index = buffer_indices.instance, - }; - - attrs[7] = .{ - .format = .FLOAT4, - .buffer_index = buffer_indices.instance, - }; - - break: get attrs; - }, - - .buffers = get: { - var buffers = [_]sokol.gfx.VertexBufferLayoutState{.{}} ** 8; - - buffers[buffer_indices.instance].step_func = .PER_INSTANCE; - - break: get buffers; - }, - }, - - .shader = sokol.gfx.makeShader(spirv_unit.shader_desc), - .index_type = .UINT16, - }), - - .batching_buffers = .{.allocator = coral.heap.allocator}, - }; - } - - const instances_per_buffer = 512; - - fn set_target(self: *Rendering2D, frame: *Frame, window: *ext.SDL_Window, target: gfx.Command.Target) void { - defer frame.render_passes += 1; - - if (frame.render_passes != 0) { - self.flush(frame); - sokol.gfx.endPass(); - } - - var pass = sokol.gfx.Pass{ - .action = .{ - .stencil = .{.load_action = .CLEAR}, - }, - }; - - if (target.clear_color) |color| { - pass.action.colors[0] = .{ - .load_action = .CLEAR, - .clear_value = @bitCast(color), - }; - } else { - pass.action.colors[0] = .{.load_action = .LOAD}; - } - - if (target.clear_depth) |depth| { - pass.action.depth = .{ - .load_action = .CLEAR, - .clear_value = depth, - }; - } else { - pass.action.depth = .{.load_action = .LOAD}; - } - - if (target.texture) |render_texture| { - const render_target = resource_cast(render_texture).payload.render_target; - - pass.attachments = render_target.attachments; - - frame.projection = orthographic_projection(-1.0, 1.0, .{ - .left = 0, - .top = 0, - .right = @floatFromInt(render_target.width), - .bottom = @floatFromInt(render_target.height), - }); - } else { - var target_width, var target_height = [_]c_int{0, 0}; - - ext.SDL_GL_GetDrawableSize(window, &target_width, &target_height); - std.debug.assert(target_width > 0 and target_height > 0); - - pass.swapchain = .{ - .width = target_width, - .height = target_height, - .sample_count = 1, - .color_format = .RGBA8, - .depth_format = .DEPTH_STENCIL, - .gl = .{.framebuffer = 0}, - }; - - frame.projection = orthographic_projection(-1.0, 1.0, .{ - .left = 0, - .top = 0, - .right = @floatFromInt(target_width), - .bottom = @floatFromInt(target_height), - }); - } - - sokol.gfx.beginPass(pass); - } -}; - -const Resource = struct { - has_prev: ?*Resource, - has_next: ?*Resource, - - payload: union (enum) { - 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, - }, - - render_target: struct { - sampler: sokol.gfx.Sampler, - color_image: sokol.gfx.Image, - depth_image: sokol.gfx.Image, - attachments: sokol.gfx.Attachments, - width: u16, - height: u16, - }, - }, - - fn destroy(self: Resource) void { - switch (self.payload) { - .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); - }, - - .render_target => |render_target| { - sokol.gfx.destroyImage(render_target.color_image); - sokol.gfx.destroyImage(render_target.depth_image); - sokol.gfx.destroySampler(render_target.sampler); - sokol.gfx.destroyAttachments(render_target.attachments); - }, - } - } -}; - -fn clone_command(self: gfx.Command, arena: *std.heap.ArenaAllocator) std.mem.Allocator.Error!gfx.Command { - _ = arena; - - return switch (self) { - .instance_2d => |instance_2d| .{.instance_2d = instance_2d}, - .target => |target| .{.target = target}, - }; -} - -fn orthographic_projection(near: f32, far: f32, viewport: gfx.Rect) Projection { - 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}, - }; -} - -fn resource_cast(handle: *gfx.Handle) *Resource { - return @ptrCast(@alignCast(handle)); -} diff --git a/src/ona/gfx/handles.zig b/src/ona/gfx/handles.zig new file mode 100644 index 0000000..8c8a014 --- /dev/null +++ b/src/ona/gfx/handles.zig @@ -0,0 +1,45 @@ +const coral = @import("coral"); + +const std = @import("std"); + +fn Handle(comptime HandleDesc: type) type { + return enum (u32) { + default, + _, + + pub const Desc = HandleDesc; + + const Self = @This(); + }; +} + +pub const Texture = Handle(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 new file mode 100644 index 0000000..3020f76 --- /dev/null +++ b/src/ona/gfx/lina.zig @@ -0,0 +1,29 @@ +const gfx = @import("../gfx.zig"); + +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 fn orthographic_projection(near: f32, far: f32, viewport: gfx.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/rendering.zig b/src/ona/gfx/rendering.zig new file mode 100644 index 0000000..6727443 --- /dev/null +++ b/src/ona/gfx/rendering.zig @@ -0,0 +1,662 @@ +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 spirv = @import("./spirv.zig"); + +const sokol = @import("sokol"); + +const std = @import("std"); + +pub const Command = union (enum) { + draw_texture: DrawTexture, + set_target: SetTarget, + + pub const DrawTexture = struct { + texture: handles.Texture, + transform: lina.Transform2D, + }; + + pub const SetTarget = struct { + texture: ?handles.Texture = null, + clear_color: ?lina.Color, + clear_depth: ?f32, + clear_stencil: ?u8, + }; + + fn clone(self: Command, arena: *std.heap.ArenaAllocator) !Command { + _ = arena; + + return switch (self) { + .draw_texture => |draw_texture| .{.draw_texture = draw_texture}, + .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_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 = null, + + const DrawTexture = extern struct { + transform: lina.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 { + if (command.texture != self.current_source_texture) { + self.flush(pools); + } + + self.current_source_texture = command.texture; + + const has_filled_current_buffer = (self.drawn_count % batches_per_buffer) == 0; + const buffer_count = self.drawn_count / batches_per_buffer; + + if (has_filled_current_buffer and buffer_count == texture_batch_buffers.len()) { + const instance_buffer = sokol.gfx.makeBuffer(.{ + .size = @sizeOf(DrawTexture) * batches_per_buffer, + .usage = .STREAM, + }); + + errdefer sokol.gfx.destroyBuffer(instance_buffer); + + try texture_batch_buffers.push_grow(instance_buffer); + } + + _ = sokol.gfx.appendBuffer(texture_batch_buffers.get().?, sokol.gfx.asRange(&DrawTexture{ + .transform = command.transform, + })); + + self.drawn_count += 1; + } + + pub fn flush(self: *Frame, pools: *Pools) void { + if (self.flushed_count == self.drawn_count) { + return; + } + + var bindings = sokol.gfx.Bindings{ + .index_buffer = quad_index_buffer, + }; + + bindings.vertex_buffers[vertex_indices.mesh] = quad_vertex_buffer; + + switch (pools.textures.get(@intFromEnum(self.current_source_texture)).?.*) { + .render => |render| { + bindings.fs.images[0] = render.color_image; + bindings.fs.samplers[0] = render.sampler; + bindings.vs.images[0] = render.color_image; + bindings.vs.samplers[0] = render.sampler; + }, + + .static => |static| { + bindings.fs.images[0] = static.image; + bindings.fs.samplers[0] = static.sampler; + bindings.vs.images[0] = static.image; + bindings.vs.samplers[0] = static.sampler; + }, + } + + if (self.current_target_texture) |target_texture| { + const target_view_buffer = pools.textures.get(@intFromEnum(target_texture)).?.render.view_buffer; + + bindings.fs.storage_buffers[0] = target_view_buffer; + bindings.vs.storage_buffers[0] = target_view_buffer; + } else { + bindings.fs.storage_buffers[0] = view_buffer; + bindings.vs.storage_buffers[0] = view_buffer; + } + + sokol.gfx.applyPipeline(texture_batching_pipeline); + + while (true) { + const buffer_index = self.flushed_count / batches_per_buffer; + const buffer_offset = self.flushed_count % batches_per_buffer; + const instances_to_flush = @min(batches_per_buffer - buffer_offset, self.drawn_count - self.flushed_count); + + self.flushed_count += instances_to_flush; + bindings.vertex_buffers[vertex_indices.instance] = texture_batch_buffers.values[buffer_index]; + bindings.vertex_buffer_offsets[vertex_indices.instance] = @intCast(@sizeOf(DrawTexture) * buffer_offset); + + sokol.gfx.applyBindings(bindings); + sokol.gfx.draw(0, 6, @intCast(instances_to_flush)); + + if (self.flushed_count == self.drawn_count) { + break; + } + } + } + + pub fn set_target(self: *Frame, pools: *Pools, command: Command.SetTarget) void { + sokol.gfx.endPass(); + + var pass = sokol.gfx.Pass{ + .action = .{.stencil = .{.load_action = .CLEAR}}, + }; + + if (command.clear_color) |color| { + pass.action.colors[0] = .{ + .load_action = .CLEAR, + .clear_value = @bitCast(color), + }; + } else { + pass.action.colors[0] = .{.load_action = .LOAD}; + } + + if (command.clear_depth) |depth| { + pass.action.depth = .{ + .load_action = .CLEAR, + .clear_value = depth, + }; + } else { + pass.action.depth = .{.load_action = .LOAD}; + } + + if (command.texture) |texture| { + pass.attachments = switch (pools.textures.get(@intFromEnum(texture)).?.*) { + .static => @panic("Cannot render to static textures"), + .render => |render| render.attachments, + }; + + self.current_target_texture = command.texture; + } else { + pass.swapchain = self.swapchain; + self.current_target_texture = null; + } + + sokol.gfx.beginPass(pass); + } + + const storage_bindings = .{ + .engine = 0, + .material = 1, + }; + + var texture_batch_buffers = coral.stack.Sequential(sokol.gfx.Buffer){.allocator = coral.heap.allocator}; + + const batches_per_buffer = 512; + + var texture_batching_pipeline: sokol.gfx.Pipeline = undefined; +}; + +const Pools = struct { + textures: TexturePool, + + const TexturePool = coral.Pool(resources.Texture); + + fn create_texture(self: *Pools, desc: handles.Texture.Desc) !handles.Texture { + var texture = try resources.Texture.init(desc); + + errdefer texture.deinit(); + + return @enumFromInt(try self.textures.insert(texture)); + } + + fn deinit(self: *Pools) void { + var textures = self.textures.values(); + + while (textures.next()) |texture| { + texture.deinit(); + } + + self.textures.deinit(); + } + + fn destroy_texture(self: *Pools, texture_key: handles.Texture) bool { + switch (texture_key) { + .default => {}, + + else => { + var texture = self.textures.remove(@intFromEnum(texture_key)) orelse { + return false; + }; + + texture.deinit(); + }, + } + + return true; + } + + fn init(allocator: std.mem.Allocator) !Pools { + var pools = Pools{ + .textures = TexturePool.init(allocator), + }; + + errdefer pools.deinit(); + + const default_texture_data = [_]u32{ + 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, + 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, + 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, + 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, + 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, + 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, + 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, + 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, + }; + + _ = try pools.create_texture(.{ + .format = .rgba8, + + .access = .{ + .static = .{ + .data = std.mem.asBytes(&default_texture_data), + .width = 8, + }, + }, + }); + + return pools; + } +}; + +pub const Work = union (enum) { + create_commands: CreateCommandsWork, + load_texture: LoadTextureWork, + render_frame: RenderFrameWork, + rotate_commands: RotateCommandsWork, + shutdown, + unload_texture: UnloadTextureWork, + + pub const CreateCommandsWork = struct { + created: *coral.asyncio.Future(std.mem.Allocator.Error!*Commands), + }; + + 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 UnloadTextureWork = struct { + handle: handles.Texture, + }; + + var pending: coral.asyncio.BlockingQueue(1024, Work) = .{}; +}; + +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; + +var view_buffer: sokol.gfx.Buffer = undefined; + +const vertex_indices = .{ + .mesh = 0, + .instance = 1, +}; + +const vertex_layout_state = sokol.gfx.VertexLayoutState{ + .attrs = get: { + var attrs = [_]sokol.gfx.VertexAttrState{.{}} ** 16; + + attrs[0] = .{ + .format = .FLOAT2, + .buffer_index = vertex_indices.mesh, + }; + + attrs[1] = .{ + .format = .FLOAT2, + .buffer_index = vertex_indices.mesh, + }; + + attrs[2] = .{ + .format = .FLOAT2, + .buffer_index = vertex_indices.instance, + }; + + attrs[3] = .{ + .format = .FLOAT2, + .buffer_index = vertex_indices.instance, + }; + + attrs[4] = .{ + .format = .FLOAT2, + .buffer_index = vertex_indices.instance, + }; + + attrs[5] = .{ + .format = .UBYTE4N, + .buffer_index = vertex_indices.instance, + }; + + attrs[6] = .{ + .format = .FLOAT, + .buffer_index = vertex_indices.instance, + }; + + attrs[7] = .{ + .format = .FLOAT4, + .buffer_index = vertex_indices.instance, + }; + + break: get attrs; + }, + + .buffers = get: { + var vertex_buffer_layouts = [_]sokol.gfx.VertexBufferLayoutState{.{}} ** 8; + + vertex_buffer_layouts[vertex_indices.instance].step_func = .PER_INSTANCE; + + break: get vertex_buffer_layouts; + }, +}; + +fn run(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); + }; + + defer ext.SDL_GL_DeleteContext(context); + + sokol.gfx.setup(.{ + .environment = .{ + .defaults = .{ + .color_format = .RGBA8, + .depth_format = .DEPTH_STENCIL, + .sample_count = 1, + }, + }, + + .logger = .{ + .func = sokol.log.func, + }, + }); + + defer { + sokol.gfx.shutdown(); + } + + var pools = try Pools.init(coral.heap.allocator); + + defer { + pools.deinit(); + } + + const Vertex = struct { + xy: @Vector(2, f32), + uv: @Vector(2, f32), + }; + + const quad_indices = [_]u16{0, 1, 2, 0, 2, 3}; + + const quad_vertices = [_]Vertex{ + .{.xy = .{-0.5, -0.5}, .uv = .{0, 1}}, + .{.xy = .{0.5, -0.5}, .uv = .{1, 1}}, + .{.xy = .{0.5, 0.5}, .uv = .{1, 0}}, + .{.xy = .{-0.5, 0.5}, .uv = .{0, 0}}, + }; + + quad_index_buffer = sokol.gfx.makeBuffer(.{ + .data = sokol.gfx.asRange(&quad_indices), + .type = .INDEXBUFFER, + }); + + quad_vertex_buffer = sokol.gfx.makeBuffer(.{ + .data = sokol.gfx.asRange(&quad_vertices), + .type = .VERTEXBUFFER, + }); + + view_buffer = sokol.gfx.makeBuffer(.{ + .data = sokol.gfx.asRange(&lina.orthographic_projection(-1.0, 1.0, .{ + .left = 0, + .top = 0, + .right = @floatFromInt(1280), + .bottom = @floatFromInt(720), + })), + + .type = .STORAGEBUFFER, + .usage = .IMMUTABLE, + }); + + const shader_spirv = @embedFile("./shaders/2d_default.spv"); + var spirv_unit = try spirv.Unit.init(coral.heap.allocator); + + defer spirv_unit.deinit(); + + try spirv_unit.compile(shader_spirv, .vertex); + try spirv_unit.compile(shader_spirv, .fragment); + + Frame.texture_batching_pipeline = sokol.gfx.makePipeline(.{ + .label = "2D drawing pipeline", + .layout = vertex_layout_state, + .shader = sokol.gfx.makeShader(spirv_unit.shader_desc), + .index_type = .UINT16, + }); + + 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); + } + }, + + .load_texture => |load| { + const texture = try pools.create_texture(load.desc); + + if (!load.loaded.resolve(texture)) { + std.debug.assert(pools.destroy_texture(texture)); + } + }, + + .render_frame => |render_frame| { + var frame = Frame{ + .swapchain = .{ + .width = render_frame.width, + .height = render_frame.height, + .sample_count = 1, + .color_format = .RGBA8, + .depth_format = .DEPTH_STENCIL, + .gl = .{.framebuffer = 0}, + } + }; + + sokol.gfx.beginPass(swapchain_pass: { + var pass = sokol.gfx.Pass{ + .swapchain = frame.swapchain, + + .action = .{ + .stencil = .{.load_action = .CLEAR}, + .depth = .{.load_action = .CLEAR}, + }, + }; + + pass.action.colors[0] = .{ + .clear_value = @bitCast(render_frame.clear_color), + .load_action = .CLEAR, + }; + + break: swapchain_pass pass; + }); + + var has_commands = has_commands_head; + + while (has_commands) |commands| : (has_commands = commands.has_next) { + for (commands.submitted_commands()) |command| { + try command.process(&pools, &frame); + } + + if (frame.current_target_texture != .default) { + frame.set_target(&pools, .{ + .clear_color = null, + .clear_depth = null, + .clear_stencil = null, + }); + } + } + + frame.flush(&pools); + sokol.gfx.endPass(); + sokol.gfx.commit(); + ext.SDL_GL_SwapWindow(window); + 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; + }, + + .unload_texture => |unload| { + if (!pools.destroy_texture(unload.handle)) { + @panic("Attempt to unload a non-existent texture"); + } + }, + } + } +} + +var work_thread: std.Thread = undefined; diff --git a/src/ona/gfx/resources.zig b/src/ona/gfx/resources.zig new file mode 100644 index 0000000..6fc9942 --- /dev/null +++ b/src/ona/gfx/resources.zig @@ -0,0 +1,149 @@ +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 Texture = union (enum) { + render: Render, + static: Static, + + const Render = struct { + sampler: sokol.gfx.Sampler, + color_image: sokol.gfx.Image, + depth_image: sokol.gfx.Image, + attachments: sokol.gfx.Attachments, + view_buffer: sokol.gfx.Buffer, + + fn deinit(self: *Render) void { + sokol.gfx.destroyImage(self.color_image); + sokol.gfx.destroyImage(self.depth_image); + sokol.gfx.destroySampler(self.sampler); + sokol.gfx.destroyAttachments(self.attachments); + sokol.gfx.destroyBuffer(self.view_buffer); + + self.* = undefined; + } + + fn init(format: handles.Texture.Desc.Format, access: handles.Texture.Desc.Access.Render) std.mem.Allocator.Error!Render { + const color_image = sokol.gfx.makeImage(.{ + .pixel_format = switch (format) { + .rgba8 => sokol.gfx.PixelFormat.RGBA8, + .bgra8 => sokol.gfx.PixelFormat.BGRA8, + }, + + .width = access.width, + .height = access.height, + .render_target = true, + }); + + const depth_image = sokol.gfx.makeImage(.{ + .width = access.width, + .height = access.height, + .render_target = true, + .pixel_format = .DEPTH_STENCIL, + }); + + const attachments = sokol.gfx.makeAttachments(attachments_desc: { + var desc = sokol.gfx.AttachmentsDesc{ + .depth_stencil = .{ + .image = depth_image, + }, + }; + + desc.colors[0] = .{ + .image = color_image, + }; + + break: attachments_desc desc; + }); + + const sampler = sokol.gfx.makeSampler(.{}); + + const view_buffer = sokol.gfx.makeBuffer(.{ + .data = sokol.gfx.asRange(&lina.orthographic_projection(-1.0, 1.0, .{ + .left = 0, + .top = 0, + .right = @floatFromInt(access.width), + .bottom = @floatFromInt(access.height), + })), + + .type = .STORAGEBUFFER, + .usage = .IMMUTABLE, + }); + + return .{ + .attachments = attachments, + .sampler = sampler, + .color_image = color_image, + .depth_image = depth_image, + .view_buffer = view_buffer, + }; + } + }; + + const Static = struct { + image: sokol.gfx.Image, + sampler: sokol.gfx.Sampler, + + fn deinit(self: *Static) void { + sokol.gfx.destroyImage(self.image); + sokol.gfx.destroySampler(self.sampler); + + self.* = undefined; + } + + fn init(format: handles.Texture.Desc.Format, access: handles.Texture.Desc.Access.Static) std.mem.Allocator.Error!Static { + const image = sokol.gfx.makeImage(image_desc: { + var desc = sokol.gfx.ImageDesc{ + .height = std.math.cast(i32, access.data.len / (access.width * format.byte_size())) orelse { + return error.OutOfMemory; + }, + + .pixel_format = switch (format) { + .rgba8 => sokol.gfx.PixelFormat.RGBA8, + .bgra8 => sokol.gfx.PixelFormat.BGRA8, + }, + + .width = access.width, + }; + + desc.data.subimage[0][0] = sokol.gfx.asRange(access.data); + + break: image_desc desc; + }); + + const sampler = sokol.gfx.makeSampler(.{}); + + errdefer { + sokol.gfx.destroySampler(sampler); + sokol.gfx.destroyImage(image); + } + + return .{ + .image = image, + .sampler = sampler, + }; + } + }; + + pub fn deinit(self: *Texture) void { + switch (self.*) { + .static => |*static| static.deinit(), + .render => |*render| render.deinit(), + } + } + + pub fn init(desc: handles.Texture.Desc) std.mem.Allocator.Error!Texture { + return switch (desc.access) { + .static => |static| .{.static = try Static.init(desc.format, static)}, + .render => |render| .{.render = try Render.init(desc.format, render)}, + }; + } +}; diff --git a/src/ona/gfx/shaders/render_2d.frag b/src/ona/gfx/shaders/2d_default.frag similarity index 78% rename from src/ona/gfx/shaders/render_2d.frag rename to src/ona/gfx/shaders/2d_default.frag index 40e5c20..0ed5a9d 100644 --- a/src/ona/gfx/shaders/render_2d.frag +++ b/src/ona/gfx/shaders/2d_default.frag @@ -7,6 +7,10 @@ layout (location = 1) in vec2 uv; layout (location = 0) out vec4 texel; +layout (binding = 0) readonly buffer View { + mat4 projection_matrix; +}; + void main() { texel = texture(sprite, uv) * color; diff --git a/src/ona/gfx/shaders/render_2d.vert b/src/ona/gfx/shaders/2d_default.vert similarity index 95% rename from src/ona/gfx/shaders/render_2d.vert rename to src/ona/gfx/shaders/2d_default.vert index f7d730e..47cf412 100644 --- a/src/ona/gfx/shaders/render_2d.vert +++ b/src/ona/gfx/shaders/2d_default.vert @@ -13,7 +13,7 @@ layout (location = 7) in vec4 instance_rect; layout (location = 0) out vec4 color; layout (location = 1) out vec2 uv; -layout (binding = 0) uniform Projection { +layout (binding = 0) readonly buffer View { mat4 projection_matrix; }; diff --git a/src/ona/gfx/spirv.zig b/src/ona/gfx/spirv.zig index e47f631..bda5831 100644 --- a/src/ona/gfx/spirv.zig +++ b/src/ona/gfx/spirv.zig @@ -12,14 +12,22 @@ pub const Error = std.mem.Allocator.Error || error { UnsupportedTarget, InvalidSPIRV, UnsupportedSPIRV, + UnsupportedBackend, }; pub const Unit = struct { + arena: std.heap.ArenaAllocator, context: ext.spvc_context, shader_desc: sokol.gfx.ShaderDesc, attrs_used: u32 = 0, - pub fn compile(self: *Unit, spirv: []const u32, stage: Stage) Error!void { + pub fn compile(self: *Unit, spirv_data: []const u8, stage: Stage) Error!void { + if ((spirv_data.len % @alignOf(u32)) != 0) { + return error.InvalidSPIRV; + } + + const spirv_ops: []const u32 = @alignCast(std.mem.bytesAsSlice(u32, spirv_data)); + const execution_model, const stage_desc = switch (stage) { .vertex => .{ext.SpvExecutionModelVertex, &self.shader_desc.vs}, .fragment => .{ext.SpvExecutionModelFragment, &self.shader_desc.fs}, @@ -30,7 +38,7 @@ pub const Unit = struct { option_values: []const struct {ext.spvc_compiler_option, c_uint}, }; - const backend: Backend = switch (sokol.gfx.queryBackend()) { + const backend: Backend = try switch (sokol.gfx.queryBackend()) { .GLCORE => .{ .target = ext.SPVC_BACKEND_GLSL, @@ -43,13 +51,13 @@ pub const Unit = struct { }, }, - else => @panic("Unimplemented"), + else => error.UnsupportedBackend, }; const compiler = parse_and_configure: { var parsed_ir: ext.spvc_parsed_ir = null; - try to_error(ext.spvc_context_parse_spirv(self.context, spirv.ptr, spirv.len, &parsed_ir)); + try to_error(ext.spvc_context_parse_spirv(self.context, spirv_ops.ptr, spirv_ops.len, &parsed_ir)); var compiler: ext.spvc_compiler = null; @@ -60,17 +68,14 @@ pub const Unit = struct { try to_error(ext.spvc_compiler_get_combined_image_samplers(compiler, @ptrCast(&combined_image_samplers.ptr), &combined_image_samplers.len)); + const arena_allocator = self.arena.allocator(); var binding: u32 = 0; for (combined_image_samplers) |combined_image_sampler| { - var name_buffer = [_:0]u8{0} ** 255; - - const name = coral.utf8.print_formatted(&name_buffer, "{image_name}_{sampler_name}", .{ - .image_name = coral.io.slice_sentineled(u8, 0, ext.spvc_compiler_get_name(compiler, combined_image_sampler.image_id)), - .sampler_name = coral.io.slice_sentineled(u8, 0, ext.spvc_compiler_get_name(compiler, combined_image_sampler.sampler_id)), - }) catch { - return error.InvalidSPIRV; - }; + const name = try coral.utf8.alloc_formatted(arena_allocator, "{image_name}_{sampler_name}", .{ + .image_name = std.mem.span(@as([*:0]const u8, ext.spvc_compiler_get_name(compiler, combined_image_sampler.image_id))), + .sampler_name = std.mem.span(@as([*:0]const u8, ext.spvc_compiler_get_name(compiler, combined_image_sampler.sampler_id))), + }); ext.spvc_compiler_set_name(compiler, combined_image_sampler.combined_id, name); ext.spvc_compiler_set_decoration(compiler, combined_image_sampler.combined_id, ext.SpvDecorationBinding, binding); @@ -91,7 +96,8 @@ pub const Unit = struct { break: create resources; }; - try reflect_uniform_blocks(compiler, resources, stage_desc); + try reflect_uniform_buffers(compiler, resources, stage_desc); + try reflect_storage_buffers(compiler, resources, stage_desc); try reflect_image_samplers(compiler, resources, stage_desc); try to_error(ext.spvc_compiler_install_compiler_options(compiler, create: { @@ -109,15 +115,18 @@ pub const Unit = struct { })); try to_error(ext.spvc_compiler_compile(compiler, @ptrCast(&stage_desc.source))); + + std.log.info("{s}", .{stage_desc.source}); } pub fn deinit(self: *Unit) void { ext.spvc_context_destroy(self.context); + self.arena.deinit(); self.* = undefined; } - pub fn init() std.mem.Allocator.Error!Unit { + pub fn init(allocator: std.mem.Allocator) std.mem.Allocator.Error!Unit { var context: ext.spvc_context = null; if (ext.spvc_context_create(&context) != ext.SPVC_SUCCESS) { @@ -129,6 +138,7 @@ pub const Unit = struct { ext.spvc_context_set_error_callback(context, log_context_errors, null); return .{ + .arena = std.heap.ArenaAllocator.init(allocator), .context = context, .shader_desc = .{ @@ -144,44 +154,33 @@ pub const Stage = enum { vertex, }; -pub fn embed_shader(comptime path: []const u8) []const u32 { - const shader = @embedFile(path); - const alignment = @alignOf(u32); - - if ((shader.len % alignment) != 0) { - @compileError("size of file contents at " ++ path ++ " must be aligned to a multiple of 4"); - } - - return @as(*const [shader.len / alignment]u32, @ptrCast(@alignCast(shader))); -} - fn log_context_errors(userdata: ?*anyopaque, error_message: [*c]const u8) callconv(.C) void { std.debug.assert(userdata == null); std.log.err("{s}", .{error_message}); } -fn reflect_image_samplers(compiler: ext.spvc_compiler, resources: ext.spvc_resources, stage: *sokol.gfx.ShaderStageDesc) Error!void { - var reflected_sampled_images: []const ext.spvc_reflected_resource = &.{}; +fn reflect_image_samplers(compiler: ext.spvc_compiler, resources: ext.spvc_resources, stage_desc: *sokol.gfx.ShaderStageDesc) Error!void { + var sampled_images: []const ext.spvc_reflected_resource = &.{}; try to_error(ext.spvc_resources_get_resource_list_for_type( resources, ext.SPVC_RESOURCE_TYPE_SAMPLED_IMAGE, - @ptrCast(&reflected_sampled_images.ptr), - &reflected_sampled_images.len, + @ptrCast(&sampled_images.ptr), + &sampled_images.len, )); - if (reflected_sampled_images.len > stage.image_sampler_pairs.len) { + if (sampled_images.len > stage_desc.image_sampler_pairs.len) { return error.UnsupportedSPIRV; } - for (0 .. reflected_sampled_images.len, reflected_sampled_images) |i, reflected_sampled_image| { - const sampled_image_type = ext.spvc_compiler_get_type_handle(compiler, reflected_sampled_image.type_id); + for (0 .. sampled_images.len, sampled_images) |i, sampled_image| { + const sampled_image_type = ext.spvc_compiler_get_type_handle(compiler, sampled_image.type_id); if (ext.spvc_type_get_basetype(sampled_image_type) != ext.SPVC_BASETYPE_SAMPLED_IMAGE) { return error.InvalidSPIRV; } - stage.images[i] = .{ + stage_desc.images[i] = .{ .multisampled = ext.spvc_type_get_image_multisampled(sampled_image_type) != 0, .image_type = try switch (ext.spvc_type_get_image_dimension(sampled_image_type)) { @@ -197,13 +196,13 @@ fn reflect_image_samplers(compiler: ext.spvc_compiler, resources: ext.spvc_resou .used = true, }; - stage.samplers[i] = .{ + stage_desc.samplers[i] = .{ .sampler_type = .DEFAULT, .used = true, }; - stage.image_sampler_pairs[i] = .{ - .glsl_name = ext.spvc_compiler_get_name(compiler, reflected_sampled_image.id), + stage_desc.image_sampler_pairs[i] = .{ + .glsl_name = ext.spvc_compiler_get_name(compiler, sampled_image.id), .image_slot = @intCast(i), .sampler_slot = @intCast(i), .used = true, @@ -211,85 +210,56 @@ fn reflect_image_samplers(compiler: ext.spvc_compiler, resources: ext.spvc_resou } } -fn reflect_uniform_blocks(compiler: ext.spvc_compiler, resources: ext.spvc_resources, stage: *sokol.gfx.ShaderStageDesc) Error!void { - var reflected_uniform_buffers: []const ext.spvc_reflected_resource = &.{}; +fn reflect_storage_buffers(compiler: ext.spvc_compiler, resources: ext.spvc_resources, stage_desc: *sokol.gfx.ShaderStageDesc) Error!void { + var storage_buffers: []const ext.spvc_reflected_resource = &.{}; + + try to_error(ext.spvc_resources_get_resource_list_for_type( + resources, + ext.SPVC_RESOURCE_TYPE_STORAGE_BUFFER, + @ptrCast(&storage_buffers.ptr), + &storage_buffers.len, + )); + + for (storage_buffers) |storage_buffer| { + const binding = ext.spvc_compiler_get_decoration(compiler, storage_buffer.id, ext.SpvDecorationBinding); + + if (binding >= stage_desc.storage_buffers.len) { + return error.InvalidSPIRV; + } + + var block_decorations: []const ext.SpvDecoration = &.{}; + + try to_error(ext.spvc_compiler_get_buffer_block_decorations( + compiler, + storage_buffer.id, + @ptrCast(&block_decorations.ptr), + &block_decorations.len, + )); + + stage_desc.storage_buffers[binding] = .{ + .readonly = std.mem.indexOfScalar(ext.SpvDecoration, block_decorations, ext.SpvDecorationNonWritable) != null, + .used = true, + }; + } +} + +fn reflect_uniform_buffers(compiler: ext.spvc_compiler, resources: ext.spvc_resources, stage_desc: *sokol.gfx.ShaderStageDesc) Error!void { + var uniform_buffers: []const ext.spvc_reflected_resource = &.{}; try to_error(ext.spvc_resources_get_resource_list_for_type( resources, ext.SPVC_RESOURCE_TYPE_UNIFORM_BUFFER, - @ptrCast(&reflected_uniform_buffers.ptr), - &reflected_uniform_buffers.len, + @ptrCast(&uniform_buffers.ptr), + &uniform_buffers.len, )); - if (reflected_uniform_buffers.len > stage.uniform_blocks.len) { - return error.UnsupportedSPIRV; + if (uniform_buffers.len != 0) { + return error.InvalidSPIRV; } - for (stage.uniform_blocks[0 .. reflected_uniform_buffers.len], reflected_uniform_buffers) |*uniform_block, reflected_uniform_buffer| { - const uniform_buffer_type = ext.spvc_compiler_get_type_handle(compiler, reflected_uniform_buffer.type_id); - - if (ext.spvc_type_get_basetype(uniform_buffer_type) != ext.SPVC_BASETYPE_STRUCT) { - return error.InvalidSPIRV; - } - - const member_count = ext.spvc_type_get_num_member_types(uniform_buffer_type); - - if (member_count > uniform_block.uniforms.len) { - return error.UnsupportedSPIRV; - } - - try to_error(ext.spvc_compiler_get_declared_struct_size(compiler, uniform_buffer_type, &uniform_block.size)); - - var uniform_blocks_used: u32 = 0; - - while (uniform_blocks_used < member_count) : (uniform_blocks_used += 1) { - const member_type_id = ext.spvc_type_get_member_type(uniform_buffer_type, uniform_blocks_used); - const member_type = ext.spvc_compiler_get_type_handle(compiler, member_type_id); - - uniform_block.uniforms[uniform_blocks_used] = .{ - // .name = ext.spvc_compiler_get_member_name(compiler, ext.spvc_type_get_base_type_id(uniform_buffer_type), uniform_blocks_used), - - .array_count = @intCast(try switch (ext.spvc_type_get_num_array_dimensions(member_type)) { - 0 => 0, - - 1 => switch (ext.spvc_type_array_dimension_is_literal(member_type, 1) != 0) { - true => ext.spvc_type_get_array_dimension(member_type, 1), - false => error.InvalidSPIRV, - }, - - else => error.InvalidSPIRV, - }), - - .type = try switch (ext.spvc_type_get_basetype(member_type)) { - ext.SPVC_BASETYPE_INT32 => switch (ext.spvc_type_get_vector_size(member_type)) { - 1 => if (ext.spvc_type_get_columns(member_type) == 1) sokol.gfx.UniformType.INT else error.InvalidSPIRV, - 2 => if (ext.spvc_type_get_columns(member_type) == 1) sokol.gfx.UniformType.INT2 else error.InvalidSPIRV, - 3 => if (ext.spvc_type_get_columns(member_type) == 1) sokol.gfx.UniformType.INT3 else error.InvalidSPIRV, - 4 => if (ext.spvc_type_get_columns(member_type) == 1) sokol.gfx.UniformType.INT4 else error.InvalidSPIRV, - else => error.InvalidSPIRV, - }, - - ext.SPVC_BASETYPE_FP32 => switch (ext.spvc_type_get_vector_size(member_type)) { - 1 => if (ext.spvc_type_get_columns(member_type) == 1) sokol.gfx.UniformType.FLOAT else error.InvalidSPIRV, - 2 => if (ext.spvc_type_get_columns(member_type) == 1) sokol.gfx.UniformType.FLOAT2 else error.InvalidSPIRV, - 3 => if (ext.spvc_type_get_columns(member_type) == 1) sokol.gfx.UniformType.FLOAT3 else error.InvalidSPIRV, - - 4 => switch (ext.spvc_type_get_columns(member_type)) { - 1 => sokol.gfx.UniformType.FLOAT4, - 4 => sokol.gfx.UniformType.MAT4, - else => error.InvalidSPIRV, - }, - - else => error.InvalidSPIRV, - }, - - else => error.InvalidSPIRV, - }, - }; - - // uniform_block.uniforms[uniform_blocks_used].name = ext.spvc_compiler_get_member_name(compiler, ext.spvc_type_get_base_type_id(uniform_buffer_type), uniform_blocks_used); - } - } + // TODO: Support for older APIs? + _ = stage_desc; + _ = compiler; } fn to_error(result: ext.spvc_result) Error!void { From c47b334742a2349f2b3a75f2f16652a492c59728 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sat, 20 Jul 2024 13:46:06 +0100 Subject: [PATCH 25/30] Implement effects pipelining system --- build.zig | 14 +- debug/ca.frag | 21 +- src/main.zig | 32 +-- src/ona/gfx.zig | 69 ++++- src/ona/gfx/handles.zig | 4 + src/ona/gfx/rendering.zig | 192 +++++++------- src/ona/gfx/resources.zig | 134 ++++++++++ src/ona/gfx/shaders/2d_default.frag | 4 - src/ona/gfx/spirv.zig | 381 ++++++++++++++++++---------- 9 files changed, 569 insertions(+), 282 deletions(-) diff --git a/build.zig b/build.zig index 8393b7e..c702a40 100644 --- a/build.zig +++ b/build.zig @@ -194,17 +194,7 @@ pub fn build(b: *std.Build) !void { const vertex_binary_sub_path_utf8 = vertex_binary_sub_path.utf8(); const fragment_binary_sub_path_utf8 = fragment_binary_sub_path.utf8(); - const link_command = b.addSystemCommand(&.{ - "spirv-link", - vertex_binary_sub_path_utf8, - fragment_binary_sub_path_utf8, - "-o", - pending_shader.binary_sub_path.utf8(), - }); - - exe.step.dependOn(&link_command.step); - - link_command.step.dependOn(compile_vertex: { + exe.step.dependOn(compile_vertex: { const compile_command = b.addSystemCommand(&.{ "glslangValidator", "-V", @@ -216,7 +206,7 @@ pub fn build(b: *std.Build) !void { break: compile_vertex &compile_command.step; }); - link_command.step.dependOn(compile_fragment: { + exe.step.dependOn(compile_fragment: { const compile_command = b.addSystemCommand(&.{ "glslangValidator", "-V", diff --git a/debug/ca.frag b/debug/ca.frag index b4218e0..1f579bf 100644 --- a/debug/ca.frag +++ b/debug/ca.frag @@ -7,22 +7,19 @@ layout (location = 1) in vec2 uv; layout (location = 0) out vec4 texel; -layout (binding = 0) readonly buffer Camera { - mat4 projection_matrix; -}; - -layout (binding = 1) readonly buffer Material { +layout (binding = 0) readonly buffer Effect { float effect_magnitude; }; void main() { - const vec2 red_channel_uv = uv + vec2(effect_magnitude, 0.0); - const vec2 blue_channel_uv = uv + vec2(-effect_magnitude, 0.0); - const vec4 original_texel = texture(sprite, uv); + vec4 color1 = texture(sprite, uv) / 3.0; + vec4 color2 = texture(sprite, uv + 0.002 * effect_magnitude) / 3.0; + vec4 color3 = texture(sprite, uv - 0.002 * effect_magnitude) / 3.0; - texel = vec4(texture(sprite, red_channel_uv).r, original_texel.g, texture(sprite, blue_channel_uv).b, original_texel.a) * color; + color1 *= 2.0; + color2.g = 0.0; + color2.b = 0.0; + color3.r = 0.0; - if (texel.a == 0) { - discard; - } + texel = color * (color1 + color2 + color3); } diff --git a/src/main.zig b/src/main.zig index f345239..0a5b230 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4,10 +4,15 @@ const std = @import("std"); const ona = @import("ona"); +const ChromaticAberration = extern struct { + magnitude: f32, +}; + const Actors = struct { instances: coral.stack.Sequential(@Vector(2, f32)) = .{.allocator = coral.heap.allocator}, body_texture: ona.gfx.Texture = .default, render_texture: ona.gfx.Texture = .default, + ca_effect: ona.gfx.Effect = .default, }; const Player = struct { @@ -37,6 +42,8 @@ fn load(config: ona.Write(ona.gfx.Config), actors: ona.Write(Actors), assets: on }, }); + actors.res.ca_effect = try assets.res.load_effect_file(coral.files.bundle, "./ca.frag.spv"); + try actors.res.instances.push_grow(.{0, 0}); } @@ -44,12 +51,9 @@ fn exit(actors: ona.Write(Actors)) void { actors.res.instances.deinit(); } -fn render(commands: ona.gfx.Commands, actors: ona.Write(Actors), config: ona.Read(ona.gfx.Config)) !void { - try commands.set_target(.{ - .texture = actors.res.render_texture, - .clear_color = .{0, 0, 0, 0}, - .clear_depth = 0, - .clear_stencil = 0, +fn render(commands: ona.gfx.Commands, actors: ona.Write(Actors)) !void { + try commands.set_effect(actors.res.ca_effect, ChromaticAberration{ + .magnitude = 15.0, }); for (actors.res.instances.values) |instance| { @@ -63,22 +67,6 @@ fn render(commands: ona.gfx.Commands, actors: ona.Write(Actors), config: ona.Rea }, }); } - - try commands.set_target(.{ - .clear_color = null, - .clear_depth = null, - .clear_stencil = null, - }); - - try commands.draw_texture(.{ - .texture = actors.res.render_texture, - - .transform = .{ - .origin = .{@floatFromInt(config.res.width / 2), @floatFromInt(config.res.height / 2)}, - .xbasis = .{@floatFromInt(config.res.width), 0}, - .ybasis = .{0, @floatFromInt(config.res.height)}, - }, - }); } fn update(player: ona.Read(Player), actors: ona.Write(Actors), mapping: ona.Read(ona.act.Mapping)) !void { diff --git a/src/ona/gfx.zig b/src/ona/gfx.zig index 8a6780f..4f0d7d5 100644 --- a/src/ona/gfx.zig +++ b/src/ona/gfx.zig @@ -25,12 +25,11 @@ const std = @import("std"); pub const Assets = struct { window: *ext.SDL_Window, texture_formats: coral.stack.Sequential(TextureFormat), - staging_arena: std.heap.ArenaAllocator, frame_rendered: std.Thread.ResetEvent = .{}, pub const LoadError = std.mem.Allocator.Error; - pub const LoadFileError = LoadError || coral.files.AccessError || error { + pub const LoadFileError = LoadError || coral.files.ReadAllError || error { FormatUnsupported, }; @@ -41,7 +40,6 @@ pub const Assets = struct { fn deinit(self: *Assets) void { rendering.enqueue_work(.shutdown); - self.staging_arena.deinit(); self.texture_formats.deinit(); } @@ -64,12 +62,40 @@ pub const Assets = struct { try rendering.startup(window); return .{ - .staging_arena = std.heap.ArenaAllocator.init(coral.heap.allocator), .texture_formats = .{.allocator = coral.heap.allocator}, .window = window, }; } + pub fn load_effect_file(_: *Assets, storage: coral.files.Storage, path: []const u8) LoadFileError!Effect { + if (!std.mem.endsWith(u8, path, ".spv")) { + return error.FormatUnsupported; + } + + const fragment_file_stat = try storage.stat(path); + const fragment_spirv = try coral.heap.allocator.alloc(u32, fragment_file_stat.size / @alignOf(u32)); + + defer { + coral.heap.allocator.free(fragment_spirv); + } + + _ = try storage.read_all(path, std.mem.sliceAsBytes(fragment_spirv), .{}); + + var loaded = coral.asyncio.Future(std.mem.Allocator.Error!Effect){}; + + rendering.enqueue_work(.{ + .load_effect = .{ + .desc = .{ + .fragment_spirv = fragment_spirv, + }, + + .loaded = &loaded, + }, + }); + + return loaded.get().*; + } + pub fn load_texture(_: *Assets, desc: Texture.Desc) std.mem.Allocator.Error!Texture { var loaded = coral.asyncio.Future(std.mem.Allocator.Error!Texture){}; @@ -84,12 +110,10 @@ pub const Assets = struct { } pub fn load_texture_file(self: *Assets, storage: coral.files.Storage, path: []const u8) LoadFileError!Texture { - defer { - const max_cache_size = 536870912; + var arena = std.heap.ArenaAllocator.init(coral.heap.allocator); - 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}); - } + defer { + arena.deinit(); } for (self.texture_formats.values) |format| { @@ -97,12 +121,26 @@ pub const Assets = struct { continue; } - return self.load_texture(try format.load_file(&self.staging_arena, storage, path)); + return self.load_texture(try format.load_file(&arena, storage, path)); } return error.FormatUnsupported; } + pub fn update_effect(_: *Assets, effect: Effect, properties: anytype) bool { + var setted = coral.asyncio.Future(bool){}; + + rendering.enqueue_work(.{ + .update_effect = .{ + .properties = std.mem.asBytes(properties), + .effect = effect, + .setted = &setted, + }, + }); + + return setted.get().*; + } + pub const thread_restriction = .main; }; @@ -137,6 +175,15 @@ pub const Commands = struct { try self.list.append(.{.draw_texture = command}); } + pub fn set_effect(self: Commands, effect: handles.Effect, properties: anytype) std.mem.Allocator.Error!void { + try self.list.append(.{ + .set_effect = .{ + .properties = std.mem.asBytes(&properties), + .effect = effect, + }, + }); + } + pub fn set_target(self: Commands, command: rendering.Command.SetTarget) std.mem.Allocator.Error!void { try self.list.append(.{.set_target = command}); } @@ -164,6 +211,8 @@ pub const Input = union (enum) { }; }; +pub const Effect = handles.Effect; + pub const Rect = struct { left: f32, top: f32, diff --git a/src/ona/gfx/handles.zig b/src/ona/gfx/handles.zig index 8c8a014..da4010a 100644 --- a/src/ona/gfx/handles.zig +++ b/src/ona/gfx/handles.zig @@ -2,6 +2,10 @@ const coral = @import("coral"); const std = @import("std"); +pub const Effect = Handle(struct { + fragment_spirv: []const u32, +}); + fn Handle(comptime HandleDesc: type) type { return enum (u32) { default, diff --git a/src/ona/gfx/rendering.zig b/src/ona/gfx/rendering.zig index 6727443..6735fd5 100644 --- a/src/ona/gfx/rendering.zig +++ b/src/ona/gfx/rendering.zig @@ -16,6 +16,7 @@ const std = @import("std"); pub const Command = union (enum) { draw_texture: DrawTexture, + set_effect: SetEffect, set_target: SetTarget, pub const DrawTexture = struct { @@ -23,6 +24,11 @@ pub const Command = union (enum) { transform: lina.Transform2D, }; + pub const SetEffect = struct { + effect: handles.Effect, + properties: []const coral.io.Byte, + }; + pub const SetTarget = struct { texture: ?handles.Texture = null, clear_color: ?lina.Color, @@ -31,10 +37,16 @@ pub const Command = union (enum) { }; fn clone(self: Command, arena: *std.heap.ArenaAllocator) !Command { - _ = arena; - 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}, }; } @@ -42,6 +54,7 @@ pub const Command = union (enum) { 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), }; } @@ -123,6 +136,7 @@ const Frame = struct { flushed_count: usize = 0, current_source_texture: handles.Texture = .default, current_target_texture: ?handles.Texture = null, + current_effect: handles.Effect = .default, const DrawTexture = extern struct { transform: lina.Transform2D, @@ -175,29 +189,29 @@ const Frame = struct { .render => |render| { bindings.fs.images[0] = render.color_image; bindings.fs.samplers[0] = render.sampler; - bindings.vs.images[0] = render.color_image; - bindings.vs.samplers[0] = render.sampler; }, .static => |static| { bindings.fs.images[0] = static.image; bindings.fs.samplers[0] = static.sampler; - bindings.vs.images[0] = static.image; - bindings.vs.samplers[0] = static.sampler; }, } if (self.current_target_texture) |target_texture| { const target_view_buffer = pools.textures.get(@intFromEnum(target_texture)).?.render.view_buffer; - bindings.fs.storage_buffers[0] = target_view_buffer; bindings.vs.storage_buffers[0] = target_view_buffer; } else { - bindings.fs.storage_buffers[0] = view_buffer; bindings.vs.storage_buffers[0] = view_buffer; } - sokol.gfx.applyPipeline(texture_batching_pipeline); + const effect = pools.effects.get(@intFromEnum(self.current_effect)).?; + + sokol.gfx.applyPipeline(effect.pipeline); + + if (effect.has_properties_buffer) |properties_buffer| { + bindings.fs.storage_buffers[0] = properties_buffer; + } while (true) { const buffer_index = self.flushed_count / batches_per_buffer; @@ -217,11 +231,29 @@ const Frame = struct { } } + pub fn set_effect(self: *Frame, pools: *Pools, command: Command.SetEffect) void { + if (command.effect != self.current_effect) { + self.flush(pools); + } + + self.current_effect = command.effect; + + if (command.properties.len != 0) { + if (pools.effects.get(@intFromEnum(self.current_effect)).?.has_properties_buffer) |properties_buffer| { + sokol.gfx.updateBuffer(properties_buffer, sokol.gfx.asRange(command.properties)); + } + } + } + pub fn set_target(self: *Frame, pools: *Pools, command: Command.SetTarget) void { sokol.gfx.endPass(); var pass = sokol.gfx.Pass{ - .action = .{.stencil = .{.load_action = .CLEAR}}, + .action = .{ + .stencil = .{ + .load_action = .CLEAR, + }, + }, }; if (command.clear_color) |color| { @@ -265,15 +297,24 @@ const Frame = struct { var texture_batch_buffers = coral.stack.Sequential(sokol.gfx.Buffer){.allocator = coral.heap.allocator}; const batches_per_buffer = 512; - - var texture_batching_pipeline: sokol.gfx.Pipeline = undefined; }; const Pools = struct { + effects: EffectPool, textures: TexturePool, + const EffectPool = coral.Pool(resources.Effect); + const TexturePool = coral.Pool(resources.Texture); + fn create_effect(self: *Pools, desc: handles.Effect.Desc) !handles.Effect { + var effect = try resources.Effect.init(desc); + + errdefer effect.deinit(); + + return @enumFromInt(try self.effects.insert(effect)); + } + fn create_texture(self: *Pools, desc: handles.Texture.Desc) !handles.Texture { var texture = try resources.Texture.init(desc); @@ -292,12 +333,28 @@ const Pools = struct { self.textures.deinit(); } - fn destroy_texture(self: *Pools, texture_key: handles.Texture) bool { - switch (texture_key) { + fn destroy_effect(self: *Pools, handle: handles.Effect) bool { + switch (handle) { .default => {}, else => { - var texture = self.textures.remove(@intFromEnum(texture_key)) orelse { + var effect = self.effects.remove(@intFromEnum(handle)) orelse { + return false; + }; + + effect.deinit(); + }, + } + + return true; + } + + fn destroy_texture(self: *Pools, handle: handles.Texture) bool { + switch (handle) { + .default => {}, + + else => { + var texture = self.textures.remove(@intFromEnum(handle)) orelse { return false; }; @@ -310,10 +367,17 @@ const Pools = struct { fn init(allocator: std.mem.Allocator) !Pools { var pools = Pools{ + .effects = EffectPool.init(allocator), .textures = TexturePool.init(allocator), }; - errdefer pools.deinit(); + errdefer { + pools.deinit(); + } + + _ = try pools.create_effect(.{ + .fragment_spirv = &spirv.to_ops(@embedFile("./shaders/2d_default.frag.spv")), + }); const default_texture_data = [_]u32{ 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, @@ -343,16 +407,23 @@ 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), @@ -369,6 +440,10 @@ pub const Work = union (enum) { finished: *std.Thread.ResetEvent, }; + pub const UnloadEffectWork = struct { + handle: handles.Effect, + }; + pub const UnloadTextureWork = struct { handle: handles.Texture, }; @@ -395,62 +470,6 @@ const vertex_indices = .{ .instance = 1, }; -const vertex_layout_state = sokol.gfx.VertexLayoutState{ - .attrs = get: { - var attrs = [_]sokol.gfx.VertexAttrState{.{}} ** 16; - - attrs[0] = .{ - .format = .FLOAT2, - .buffer_index = vertex_indices.mesh, - }; - - attrs[1] = .{ - .format = .FLOAT2, - .buffer_index = vertex_indices.mesh, - }; - - attrs[2] = .{ - .format = .FLOAT2, - .buffer_index = vertex_indices.instance, - }; - - attrs[3] = .{ - .format = .FLOAT2, - .buffer_index = vertex_indices.instance, - }; - - attrs[4] = .{ - .format = .FLOAT2, - .buffer_index = vertex_indices.instance, - }; - - attrs[5] = .{ - .format = .UBYTE4N, - .buffer_index = vertex_indices.instance, - }; - - attrs[6] = .{ - .format = .FLOAT, - .buffer_index = vertex_indices.instance, - }; - - attrs[7] = .{ - .format = .FLOAT4, - .buffer_index = vertex_indices.instance, - }; - - break: get attrs; - }, - - .buffers = get: { - var vertex_buffer_layouts = [_]sokol.gfx.VertexBufferLayoutState{.{}} ** 8; - - vertex_buffer_layouts[vertex_indices.instance].step_func = .PER_INSTANCE; - - break: get vertex_buffer_layouts; - }, -}; - fn run(window: *ext.SDL_Window) !void { const context = configure_and_create: { var result = @as(c_int, 0); @@ -530,21 +549,6 @@ fn run(window: *ext.SDL_Window) !void { .usage = .IMMUTABLE, }); - const shader_spirv = @embedFile("./shaders/2d_default.spv"); - var spirv_unit = try spirv.Unit.init(coral.heap.allocator); - - defer spirv_unit.deinit(); - - try spirv_unit.compile(shader_spirv, .vertex); - try spirv_unit.compile(shader_spirv, .fragment); - - Frame.texture_batching_pipeline = sokol.gfx.makePipeline(.{ - .label = "2D drawing pipeline", - .layout = vertex_layout_state, - .shader = sokol.gfx.makeShader(spirv_unit.shader_desc), - .index_type = .UINT16, - }); - var has_commands_head: ?*Commands = null; var has_commands_tail: ?*Commands = null; @@ -575,6 +579,14 @@ fn run(window: *ext.SDL_Window) !void { } }, + .load_effect => |load| { + const effect = try pools.create_effect(load.desc); + + if (!load.loaded.resolve(effect)) { + std.debug.assert(pools.destroy_effect(effect)); + } + }, + .load_texture => |load| { const texture = try pools.create_texture(load.desc); @@ -650,6 +662,12 @@ fn run(window: *ext.SDL_Window) !void { break; }, + .unload_effect => |unload| { + if (!pools.destroy_effect(unload.handle)) { + @panic("Attempt to unload a non-existent effect"); + } + }, + .unload_texture => |unload| { if (!pools.destroy_texture(unload.handle)) { @panic("Attempt to unload a non-existent texture"); diff --git a/src/ona/gfx/resources.zig b/src/ona/gfx/resources.zig index 6fc9942..1bf9c0f 100644 --- a/src/ona/gfx/resources.zig +++ b/src/ona/gfx/resources.zig @@ -10,6 +10,140 @@ const spirv = @import("./spirv.zig"); const std = @import("std"); +pub const Effect = struct { + shader: sokol.gfx.Shader, + pipeline: sokol.gfx.Pipeline, + has_properties_buffer: ?sokol.gfx.Buffer = null, + + pub fn deinit(self: *Effect) void { + sokol.gfx.destroyPipeline(self.pipeline); + sokol.gfx.destroyShader(self.shader); + + if (self.has_properties_buffer) |parameters_buffer| { + sokol.gfx.destroyBuffer(parameters_buffer); + } + + self.* = undefined; + } + + pub fn init(desc: handles.Effect.Desc) !Effect { + var spirv_unit = try spirv.Unit.init(coral.heap.allocator); + + defer spirv_unit.deinit(); + + try spirv_unit.compile(&spirv.to_ops(@embedFile("./shaders/2d_default.vert.spv")), .vertex); + try spirv_unit.compile(desc.fragment_spirv, .fragment); + + const shader = sokol.gfx.makeShader(spirv_unit.shader_desc()); + + 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; + }); + + errdefer { + sokol.gfx.destroyPipeline(pipeline); + sokol.gfx.destroyShader(shader); + } + + if (spirv_unit.shader_stage(.fragment).has_storage_buffer[0]) |storage_buffer| { + const properties_buffer = sokol.gfx.makeBuffer(.{ + .size = storage_buffer.minimum_size, + .type = .STORAGEBUFFER, + .usage = .STREAM, + }); + + errdefer { + sokol.gfx.destroyBuffer(properties_buffer); + } + + return .{ + .shader = shader, + .pipeline = pipeline, + .has_properties_buffer = properties_buffer, + }; + } else { + return .{ + .shader = shader, + .pipeline = pipeline, + }; + } + } + + 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 = union (enum) { render: Render, static: Static, diff --git a/src/ona/gfx/shaders/2d_default.frag b/src/ona/gfx/shaders/2d_default.frag index 0ed5a9d..40e5c20 100644 --- a/src/ona/gfx/shaders/2d_default.frag +++ b/src/ona/gfx/shaders/2d_default.frag @@ -7,10 +7,6 @@ layout (location = 1) in vec2 uv; layout (location = 0) out vec4 texel; -layout (binding = 0) readonly buffer View { - mat4 projection_matrix; -}; - void main() { texel = texture(sprite, uv) * color; diff --git a/src/ona/gfx/spirv.zig b/src/ona/gfx/spirv.zig index bda5831..222ed02 100644 --- a/src/ona/gfx/spirv.zig +++ b/src/ona/gfx/spirv.zig @@ -15,22 +15,225 @@ pub const Error = std.mem.Allocator.Error || error { UnsupportedBackend, }; -pub const Unit = struct { - arena: std.heap.ArenaAllocator, - context: ext.spvc_context, - shader_desc: sokol.gfx.ShaderDesc, - attrs_used: u32 = 0, +pub const Shader = struct { + decompiled_source: [:0]const u8 = "", + has_storage_buffer: [max_storage_buffers]?StorageBuffer = [_]?StorageBuffer{null} ** max_storage_buffers, + has_image: [max_images]?Image = [_]?Image{null} ** max_images, + has_sampler: [max_samplers]?Sampler = [_]?Sampler{null} ** max_samplers, + has_image_sampler_pair: [max_image_sampler_pairs]?ImageSamplerPair = [_]?ImageSamplerPair{null} ** max_image_sampler_pairs, - pub fn compile(self: *Unit, spirv_data: []const u8, stage: Stage) Error!void { - if ((spirv_data.len % @alignOf(u32)) != 0) { + pub const Image = struct { + is_multisampled: bool, + }; + + pub const ImageSamplerPair = struct { + image_slot: usize, + sampler_slot: usize, + name: [:0]const u8, + }; + + pub const Sampler = enum { + filtering, + non_filtering, + comparison, + }; + + pub const StorageBuffer = struct { + minimum_size: usize, + is_readonly: bool, + }; + + pub const max_images = 12; + + pub const max_image_sampler_pairs = 12; + + pub const max_samplers = 8; + + pub const max_storage_buffers = 8; + + fn reflect_image_samplers(self: *Shader, _: Stage, compiler: ext.spvc_compiler, resources: ext.spvc_resources) Error!void { + var sampled_images: []const ext.spvc_reflected_resource = &.{}; + + try to_error(ext.spvc_resources_get_resource_list_for_type( + resources, + ext.SPVC_RESOURCE_TYPE_SAMPLED_IMAGE, + @ptrCast(&sampled_images.ptr), + &sampled_images.len, + )); + + if (sampled_images.len > max_image_sampler_pairs) { + return error.UnsupportedSPIRV; + } + + for (0 .. sampled_images.len, sampled_images) |i, sampled_image| { + const sampled_image_type = ext.spvc_compiler_get_type_handle(compiler, sampled_image.type_id); + + if (ext.spvc_type_get_basetype(sampled_image_type) != ext.SPVC_BASETYPE_SAMPLED_IMAGE) { + return error.InvalidSPIRV; + } + + try switch (ext.spvc_type_get_image_dimension(sampled_image_type)) { + ext.SpvDim2D => {}, + else => error.InvalidSPIRV, + }; + + try switch (ext.spvc_type_get_basetype(ext.spvc_compiler_get_type_handle(compiler, ext.spvc_type_get_image_sampled_type(sampled_image_type)))) { + ext.SPVC_BASETYPE_FP32 => {}, + else => error.InvalidSPIRV, + }; + + self.has_image[i] = .{ + .is_multisampled = ext.spvc_type_get_image_multisampled(sampled_image_type) != 0, + }; + + self.has_sampler[i] = .filtering; + + self.has_image_sampler_pair[i] = .{ + .name = std.mem.span(ext.spvc_compiler_get_name(compiler, sampled_image.id)), + .image_slot = @intCast(i), + .sampler_slot = @intCast(i), + }; + } + } + + fn reflect_storage_buffers(self: *Shader, stage: Stage, compiler: ext.spvc_compiler, resources: ext.spvc_resources) Error!void { + var storage_buffers: []const ext.spvc_reflected_resource = &.{}; + + try to_error(ext.spvc_resources_get_resource_list_for_type( + resources, + ext.SPVC_RESOURCE_TYPE_STORAGE_BUFFER, + @ptrCast(&storage_buffers.ptr), + &storage_buffers.len, + )); + + const binding_offset: c_uint = if (stage == .fragment) max_storage_buffers else 0; + + for (storage_buffers) |storage_buffer| { + const binding = ext.spvc_compiler_get_decoration(compiler, storage_buffer.id, ext.SpvDecorationBinding); + + if (binding >= max_storage_buffers) { + return error.InvalidSPIRV; + } + + ext.spvc_compiler_set_decoration(compiler, storage_buffer.id, ext.SpvDecorationBinding, binding_offset + binding); + + var block_decorations: []const ext.SpvDecoration = &.{}; + + try to_error(ext.spvc_compiler_get_buffer_block_decorations( + compiler, + storage_buffer.id, + @ptrCast(&block_decorations.ptr), + &block_decorations.len, + )); + + var minimum_size: usize = 0; + + try to_error(ext.spvc_compiler_get_declared_struct_size(compiler, ext.spvc_compiler_get_type_handle(compiler, storage_buffer.base_type_id), &minimum_size)); + + self.has_storage_buffer[binding] = .{ + .is_readonly = std.mem.indexOfScalar(ext.SpvDecoration, block_decorations, ext.SpvDecorationNonWritable) != null, + .minimum_size = minimum_size, + }; + } + } + + fn reflect_uniform_buffers(self: *Shader, _: Stage, compiler: ext.spvc_compiler, resources: ext.spvc_resources) Error!void { + var uniform_buffers: []const ext.spvc_reflected_resource = &.{}; + + try to_error(ext.spvc_resources_get_resource_list_for_type( + resources, + ext.SPVC_RESOURCE_TYPE_UNIFORM_BUFFER, + @ptrCast(&uniform_buffers.ptr), + &uniform_buffers.len, + )); + + if (uniform_buffers.len != 0) { return error.InvalidSPIRV; } - const spirv_ops: []const u32 = @alignCast(std.mem.bytesAsSlice(u32, spirv_data)); + // TODO: Support for older APIs? + _ = self; + _ = compiler; + } - const execution_model, const stage_desc = switch (stage) { - .vertex => .{ext.SpvExecutionModelVertex, &self.shader_desc.vs}, - .fragment => .{ext.SpvExecutionModelFragment, &self.shader_desc.fs}, + pub fn stage_desc(self: Shader) sokol.gfx.ShaderStageDesc { + var stage = sokol.gfx.ShaderStageDesc{ + .entry = "main", + .source = self.decompiled_source, + }; + + for (0 .. max_storage_buffers) |slot| { + const storage_buffer = &(self.has_storage_buffer[slot] orelse { + continue; + }); + + stage.storage_buffers[slot] = .{ + .readonly = storage_buffer.is_readonly, + .used = true, + }; + } + + for (0 .. max_images) |slot| { + const image = &(self.has_image[slot] orelse { + continue; + }); + + stage.images[slot] = .{ + .multisampled = image.is_multisampled, + .image_type = ._2D, + .sample_type = .FLOAT, + .used = true, + }; + } + + for (0 .. max_samplers) |slot| { + const sampler = &(self.has_sampler[slot] orelse { + continue; + }); + + stage.samplers[slot] = .{ + .sampler_type = switch (sampler.*) { + .filtering => .FILTERING, + .non_filtering => .NONFILTERING, + .comparison => .COMPARISON, + }, + + .used = true, + }; + } + + for (0 .. max_image_sampler_pairs) |slot| { + const image_sampler_pair = &(self.has_image_sampler_pair[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; + } +}; + +pub const Stage = enum { + vertex, + fragment, +}; + +pub const Unit = struct { + arena: std.heap.ArenaAllocator, + context: ext.spvc_context, + attrs_used: u32 = 0, + stages: [std.enums.values(Stage).len]Shader = .{.{}, .{}}, + + pub fn compile(self: *Unit, spirv_ops: []const u32, stage: Stage) Error!void { + const execution_model, const shader = switch (stage) { + .vertex => .{ext.SpvExecutionModelVertex, &self.stages[0]}, + .fragment => .{ext.SpvExecutionModelFragment, &self.stages[1]}, }; const Backend = struct { @@ -73,8 +276,8 @@ pub const Unit = struct { for (combined_image_samplers) |combined_image_sampler| { const name = try coral.utf8.alloc_formatted(arena_allocator, "{image_name}_{sampler_name}", .{ - .image_name = std.mem.span(@as([*:0]const u8, ext.spvc_compiler_get_name(compiler, combined_image_sampler.image_id))), - .sampler_name = std.mem.span(@as([*:0]const u8, ext.spvc_compiler_get_name(compiler, combined_image_sampler.sampler_id))), + .image_name = std.mem.span(ext.spvc_compiler_get_name(compiler, combined_image_sampler.image_id)), + .sampler_name = std.mem.span(ext.spvc_compiler_get_name(compiler, combined_image_sampler.sampler_id)), }); ext.spvc_compiler_set_name(compiler, combined_image_sampler.combined_id, name); @@ -86,7 +289,7 @@ pub const Unit = struct { break: parse_and_configure compiler; }; - try to_error(ext.spvc_compiler_set_entry_point(compiler, stage_desc.entry, @intCast(execution_model))); + try to_error(ext.spvc_compiler_set_entry_point(compiler, "main", @intCast(execution_model))); const resources = create: { var resources: ext.spvc_resources = null; @@ -96,27 +299,29 @@ pub const Unit = struct { break: create resources; }; - try reflect_uniform_buffers(compiler, resources, stage_desc); - try reflect_storage_buffers(compiler, resources, stage_desc); - try reflect_image_samplers(compiler, resources, stage_desc); + try shader.reflect_uniform_buffers(stage, compiler, resources); + try shader.reflect_storage_buffers(stage, compiler, resources); + try shader.reflect_image_samplers(stage, compiler, resources); try to_error(ext.spvc_compiler_install_compiler_options(compiler, create: { - var options: ext.spvc_compiler_options = null; + var compiler_options: ext.spvc_compiler_options = null; - try to_error(ext.spvc_compiler_create_compiler_options(compiler, &options)); + try to_error(ext.spvc_compiler_create_compiler_options(compiler, &compiler_options)); for (backend.option_values) |option_value| { const entry, const value = option_value; - try to_error(ext.spvc_compiler_options_set_uint(options, entry, value)); + try to_error(ext.spvc_compiler_options_set_uint(compiler_options, entry, value)); } - break: create options; + break: create compiler_options; })); - try to_error(ext.spvc_compiler_compile(compiler, @ptrCast(&stage_desc.source))); + try to_error(ext.spvc_compiler_compile(compiler, @ptrCast(&shader.decompiled_source.ptr))); - std.log.info("{s}", .{stage_desc.source}); + shader.decompiled_source.len = std.mem.span(shader.decompiled_source.ptr).len; + + std.log.info("{s}", .{shader.decompiled_source}); } pub fn deinit(self: *Unit) void { @@ -140,18 +345,19 @@ pub const Unit = struct { return .{ .arena = std.heap.ArenaAllocator.init(allocator), .context = context, - - .shader_desc = .{ - .vs = .{.entry = "main"}, - .fs = .{.entry = "main"}, - }, }; } -}; -pub const Stage = enum { - fragment, - vertex, + pub fn shader_desc(self: Unit) sokol.gfx.ShaderDesc { + return .{ + .vs = self.shader_stage(.vertex).stage_desc(), + .fs = self.shader_stage(.fragment).stage_desc(), + }; + } + + pub fn shader_stage(self: *const Unit, stage: Stage) *const Shader { + return &self.stages[@intFromEnum(stage)]; + } }; fn log_context_errors(userdata: ?*anyopaque, error_message: [*c]const u8) callconv(.C) void { @@ -159,109 +365,6 @@ fn log_context_errors(userdata: ?*anyopaque, error_message: [*c]const u8) callco std.log.err("{s}", .{error_message}); } -fn reflect_image_samplers(compiler: ext.spvc_compiler, resources: ext.spvc_resources, stage_desc: *sokol.gfx.ShaderStageDesc) Error!void { - var sampled_images: []const ext.spvc_reflected_resource = &.{}; - - try to_error(ext.spvc_resources_get_resource_list_for_type( - resources, - ext.SPVC_RESOURCE_TYPE_SAMPLED_IMAGE, - @ptrCast(&sampled_images.ptr), - &sampled_images.len, - )); - - if (sampled_images.len > stage_desc.image_sampler_pairs.len) { - return error.UnsupportedSPIRV; - } - - for (0 .. sampled_images.len, sampled_images) |i, sampled_image| { - const sampled_image_type = ext.spvc_compiler_get_type_handle(compiler, sampled_image.type_id); - - if (ext.spvc_type_get_basetype(sampled_image_type) != ext.SPVC_BASETYPE_SAMPLED_IMAGE) { - return error.InvalidSPIRV; - } - - stage_desc.images[i] = .{ - .multisampled = ext.spvc_type_get_image_multisampled(sampled_image_type) != 0, - - .image_type = try switch (ext.spvc_type_get_image_dimension(sampled_image_type)) { - ext.SpvDim2D => sokol.gfx.ImageType._2D, - else => error.InvalidSPIRV, - }, - - .sample_type = try switch (ext.spvc_type_get_basetype(ext.spvc_compiler_get_type_handle(compiler, ext.spvc_type_get_image_sampled_type(sampled_image_type)))) { - ext.SPVC_BASETYPE_FP32 => sokol.gfx.ImageSampleType.FLOAT, - else => error.InvalidSPIRV, - }, - - .used = true, - }; - - stage_desc.samplers[i] = .{ - .sampler_type = .DEFAULT, - .used = true, - }; - - stage_desc.image_sampler_pairs[i] = .{ - .glsl_name = ext.spvc_compiler_get_name(compiler, sampled_image.id), - .image_slot = @intCast(i), - .sampler_slot = @intCast(i), - .used = true, - }; - } -} - -fn reflect_storage_buffers(compiler: ext.spvc_compiler, resources: ext.spvc_resources, stage_desc: *sokol.gfx.ShaderStageDesc) Error!void { - var storage_buffers: []const ext.spvc_reflected_resource = &.{}; - - try to_error(ext.spvc_resources_get_resource_list_for_type( - resources, - ext.SPVC_RESOURCE_TYPE_STORAGE_BUFFER, - @ptrCast(&storage_buffers.ptr), - &storage_buffers.len, - )); - - for (storage_buffers) |storage_buffer| { - const binding = ext.spvc_compiler_get_decoration(compiler, storage_buffer.id, ext.SpvDecorationBinding); - - if (binding >= stage_desc.storage_buffers.len) { - return error.InvalidSPIRV; - } - - var block_decorations: []const ext.SpvDecoration = &.{}; - - try to_error(ext.spvc_compiler_get_buffer_block_decorations( - compiler, - storage_buffer.id, - @ptrCast(&block_decorations.ptr), - &block_decorations.len, - )); - - stage_desc.storage_buffers[binding] = .{ - .readonly = std.mem.indexOfScalar(ext.SpvDecoration, block_decorations, ext.SpvDecorationNonWritable) != null, - .used = true, - }; - } -} - -fn reflect_uniform_buffers(compiler: ext.spvc_compiler, resources: ext.spvc_resources, stage_desc: *sokol.gfx.ShaderStageDesc) Error!void { - var uniform_buffers: []const ext.spvc_reflected_resource = &.{}; - - try to_error(ext.spvc_resources_get_resource_list_for_type( - resources, - ext.SPVC_RESOURCE_TYPE_UNIFORM_BUFFER, - @ptrCast(&uniform_buffers.ptr), - &uniform_buffers.len, - )); - - if (uniform_buffers.len != 0) { - return error.InvalidSPIRV; - } - - // TODO: Support for older APIs? - _ = stage_desc; - _ = compiler; -} - fn to_error(result: ext.spvc_result) Error!void { return switch (result) { ext.SPVC_SUCCESS => {}, @@ -272,3 +375,11 @@ fn to_error(result: ext.spvc_result) Error!void { else => unreachable, }; } + +pub fn to_ops(raw: anytype) [raw.len / @alignOf(u32)]u32 { + var ops: [raw.len / @alignOf(u32)]u32 = undefined; + + @memcpy(std.mem.sliceAsBytes(&ops), raw); + + return ops; +} From f3efb60c05101d3052288aaaccb443160c04500a Mon Sep 17 00:00:00 2001 From: kayomn Date: Sat, 20 Jul 2024 15:23:47 +0100 Subject: [PATCH 26/30] Update dependencies in readme --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index e8d558e..bf956f6 100644 --- a/readme.md +++ b/readme.md @@ -33,7 +33,7 @@ Ona is also the Catalan word for "wave". Ona currently depends the following third-party tools to build it: * Platform support for SDL2 at version 2.0.20 or above. - * SPIR-V shader compilation system utilities, namely `glslangValidator` and `spirv-link`. + * SPIR-V shader compilation system utilities, namely `glslangValidator`. * Zig compiler toolchain. As the project evolves, dependencies on libraries external to the project codebase will be minimized or removed outright to meet the goals of the project as closely as possible. From 56342d9d8e31088c995e5c61c7c25908471a4ebb Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 21 Jul 2024 00:45:24 +0100 Subject: [PATCH 27/30] Use uniform blocks instead of storage buffers for common data --- debug/ca.frag | 2 +- src/main.zig | 5 +- src/ona/gfx.zig | 31 +- src/ona/gfx/handles.zig | 2 +- src/ona/gfx/lina.zig | 11 +- src/ona/gfx/rendering.zig | 54 ++- src/ona/gfx/resources.zig | 381 ++++++++++++-------- src/ona/gfx/shaders/2d_default.vert | 2 +- src/ona/gfx/spirv.zig | 536 +++++++++++++++------------- 9 files changed, 559 insertions(+), 465 deletions(-) diff --git a/debug/ca.frag b/debug/ca.frag index 1f579bf..74e9519 100644 --- a/debug/ca.frag +++ b/debug/ca.frag @@ -7,7 +7,7 @@ layout (location = 1) in vec2 uv; layout (location = 0) out vec4 texel; -layout (binding = 0) readonly buffer Effect { +layout (binding = 0) uniform Effect { float effect_magnitude; }; diff --git a/src/main.zig b/src/main.zig index 0a5b230..d9139d5 100644 --- a/src/main.zig +++ b/src/main.zig @@ -5,7 +5,8 @@ const std = @import("std"); const ona = @import("ona"); const ChromaticAberration = extern struct { - magnitude: f32, + effect_magnitude: f32, + padding: [12]u8 = undefined, }; const Actors = struct { @@ -53,7 +54,7 @@ fn exit(actors: ona.Write(Actors)) void { fn render(commands: ona.gfx.Commands, actors: ona.Write(Actors)) !void { try commands.set_effect(actors.res.ca_effect, ChromaticAberration{ - .magnitude = 15.0, + .effect_magnitude = 15.0, }); for (actors.res.instances.values) |instance| { diff --git a/src/ona/gfx.zig b/src/ona/gfx.zig index 4f0d7d5..99edf39 100644 --- a/src/ona/gfx.zig +++ b/src/ona/gfx.zig @@ -73,20 +73,22 @@ pub const Assets = struct { } const fragment_file_stat = try storage.stat(path); - const fragment_spirv = try coral.heap.allocator.alloc(u32, fragment_file_stat.size / @alignOf(u32)); + const fragment_spirv_ops = try coral.heap.allocator.alloc(u32, fragment_file_stat.size / @alignOf(u32)); defer { - coral.heap.allocator.free(fragment_spirv); + coral.heap.allocator.free(fragment_spirv_ops); } - _ = try storage.read_all(path, std.mem.sliceAsBytes(fragment_spirv), .{}); + const bytes_read = try storage.read_all(path, std.mem.sliceAsBytes(fragment_spirv_ops), .{}); + + std.debug.assert(bytes_read.len == fragment_file_stat.size); var loaded = coral.asyncio.Future(std.mem.Allocator.Error!Effect){}; rendering.enqueue_work(.{ .load_effect = .{ .desc = .{ - .fragment_spirv = fragment_spirv, + .fragment_spirv_ops = fragment_spirv_ops, }, .loaded = &loaded, @@ -127,20 +129,6 @@ pub const Assets = struct { return error.FormatUnsupported; } - pub fn update_effect(_: *Assets, effect: Effect, properties: anytype) bool { - var setted = coral.asyncio.Future(bool){}; - - rendering.enqueue_work(.{ - .update_effect = .{ - .properties = std.mem.asBytes(properties), - .effect = effect, - .setted = &setted, - }, - }); - - return setted.get().*; - } - pub const thread_restriction = .main; }; @@ -213,13 +201,6 @@ pub const Input = union (enum) { pub const Effect = handles.Effect; -pub const Rect = struct { - left: f32, - top: f32, - right: f32, - bottom: f32, -}; - pub const Texture = handles.Texture; pub fn poll(app: flow.Write(App), inputs: msg.Send(Input)) !void { diff --git a/src/ona/gfx/handles.zig b/src/ona/gfx/handles.zig index da4010a..942134f 100644 --- a/src/ona/gfx/handles.zig +++ b/src/ona/gfx/handles.zig @@ -3,7 +3,7 @@ const coral = @import("coral"); const std = @import("std"); pub const Effect = Handle(struct { - fragment_spirv: []const u32, + fragment_spirv_ops: []const u32, }); fn Handle(comptime HandleDesc: type) type { diff --git a/src/ona/gfx/lina.zig b/src/ona/gfx/lina.zig index 3020f76..5d3a599 100644 --- a/src/ona/gfx/lina.zig +++ b/src/ona/gfx/lina.zig @@ -1,5 +1,3 @@ -const gfx = @import("../gfx.zig"); - pub const Color = @Vector(4, f32); pub fn EvenOrderMatrix(comptime n: usize, comptime Element: type) type { @@ -16,7 +14,14 @@ pub const Transform2D = extern struct { pub const ProjectionMatrix = EvenOrderMatrix(4, f32); -pub fn orthographic_projection(near: f32, far: f32, viewport: gfx.Rect) 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; diff --git a/src/ona/gfx/rendering.zig b/src/ona/gfx/rendering.zig index 6735fd5..4d53f11 100644 --- a/src/ona/gfx/rendering.zig +++ b/src/ona/gfx/rendering.zig @@ -185,7 +185,7 @@ const Frame = struct { bindings.vertex_buffers[vertex_indices.mesh] = quad_vertex_buffer; - switch (pools.textures.get(@intFromEnum(self.current_source_texture)).?.*) { + switch (pools.textures.get(@intFromEnum(self.current_source_texture)).?.access) { .render => |render| { bindings.fs.images[0] = render.color_image; bindings.fs.samplers[0] = render.sampler; @@ -197,22 +197,30 @@ const Frame = struct { }, } - if (self.current_target_texture) |target_texture| { - const target_view_buffer = pools.textures.get(@intFromEnum(target_texture)).?.render.view_buffer; - - bindings.vs.storage_buffers[0] = target_view_buffer; - } else { - bindings.vs.storage_buffers[0] = view_buffer; - } - const effect = pools.effects.get(@intFromEnum(self.current_effect)).?; sokol.gfx.applyPipeline(effect.pipeline); - if (effect.has_properties_buffer) |properties_buffer| { - bindings.fs.storage_buffers[0] = properties_buffer; + if (self.current_target_texture) |target_texture| { + const texture = pools.textures.get(@intFromEnum(target_texture)).?; + + sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&lina.orthographic_projection(-1.0, 1.0, .{ + .left = 0, + .top = 0, + .right = @floatFromInt(texture.width), + .bottom = @floatFromInt(texture.height), + }))); + } else { + sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&lina.orthographic_projection(-1.0, 1.0, .{ + .left = 0, + .top = 0, + .right = @floatFromInt(self.swapchain.width), + .bottom = @floatFromInt(self.swapchain.height), + }))); } + sokol.gfx.applyUniforms(.FS, 0, sokol.gfx.asRange(effect.properties)); + while (true) { const buffer_index = self.flushed_count / batches_per_buffer; const buffer_offset = self.flushed_count % batches_per_buffer; @@ -238,10 +246,8 @@ const Frame = struct { self.current_effect = command.effect; - if (command.properties.len != 0) { - if (pools.effects.get(@intFromEnum(self.current_effect)).?.has_properties_buffer) |properties_buffer| { - sokol.gfx.updateBuffer(properties_buffer, sokol.gfx.asRange(command.properties)); - } + if (pools.effects.get(@intFromEnum(self.current_effect))) |effect| { + @memcpy(effect.properties, command.properties); } } @@ -275,7 +281,7 @@ const Frame = struct { } if (command.texture) |texture| { - pass.attachments = switch (pools.textures.get(@intFromEnum(texture)).?.*) { + pass.attachments = switch (pools.textures.get(@intFromEnum(texture)).?.access) { .static => @panic("Cannot render to static textures"), .render => |render| render.attachments, }; @@ -376,7 +382,7 @@ const Pools = struct { } _ = try pools.create_effect(.{ - .fragment_spirv = &spirv.to_ops(@embedFile("./shaders/2d_default.frag.spv")), + .fragment_spirv_ops = &spirv.to_ops(@embedFile("./shaders/2d_default.frag.spv")), }); const default_texture_data = [_]u32{ @@ -463,8 +469,6 @@ var quad_index_buffer: sokol.gfx.Buffer = undefined; var quad_vertex_buffer: sokol.gfx.Buffer = undefined; -var view_buffer: sokol.gfx.Buffer = undefined; - const vertex_indices = .{ .mesh = 0, .instance = 1, @@ -537,18 +541,6 @@ fn run(window: *ext.SDL_Window) !void { .type = .VERTEXBUFFER, }); - view_buffer = sokol.gfx.makeBuffer(.{ - .data = sokol.gfx.asRange(&lina.orthographic_projection(-1.0, 1.0, .{ - .left = 0, - .top = 0, - .right = @floatFromInt(1280), - .bottom = @floatFromInt(720), - })), - - .type = .STORAGEBUFFER, - .usage = .IMMUTABLE, - }); - var has_commands_head: ?*Commands = null; var has_commands_tail: ?*Commands = null; diff --git a/src/ona/gfx/resources.zig b/src/ona/gfx/resources.zig index 1bf9c0f..7a34da9 100644 --- a/src/ona/gfx/resources.zig +++ b/src/ona/gfx/resources.zig @@ -13,29 +13,50 @@ const std = @import("std"); pub const Effect = struct { shader: sokol.gfx.Shader, pipeline: sokol.gfx.Pipeline, - has_properties_buffer: ?sokol.gfx.Buffer = null, + 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); - if (self.has_properties_buffer) |parameters_buffer| { - sokol.gfx.destroyBuffer(parameters_buffer); - } - self.* = undefined; } - pub fn init(desc: handles.Effect.Desc) !Effect { - var spirv_unit = try spirv.Unit.init(coral.heap.allocator); + pub fn init(desc: handles.Effect.Desc) spirv.Error!Effect { + var spirv_arena = std.heap.ArenaAllocator.init(coral.heap.allocator); - defer spirv_unit.deinit(); + defer { + spirv_arena.deinit(); + } - try spirv_unit.compile(&spirv.to_ops(@embedFile("./shaders/2d_default.vert.spv")), .vertex); - try spirv_unit.compile(desc.fragment_spirv, .fragment); + const spirv_program = try spirv.analyze(&spirv_arena, .{ + .target = try switch (sokol.gfx.queryBackend()) { + .GLCORE => spirv.Target.glsl, + else => error.InvalidSPIRV, + }, - const shader = sokol.gfx.makeShader(spirv_unit.shader_desc()); + .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", @@ -58,33 +79,106 @@ pub const Effect = struct { 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); } - if (spirv_unit.shader_stage(.fragment).has_storage_buffer[0]) |storage_buffer| { - const properties_buffer = sokol.gfx.makeBuffer(.{ - .size = storage_buffer.minimum_size, - .type = .STORAGEBUFFER, - .usage = .STREAM, + 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; }); - errdefer { - sokol.gfx.destroyBuffer(properties_buffer); - } + const stage_uniform_block = &stage.uniform_blocks[slot]; - return .{ - .shader = shader, - .pipeline = pipeline, - .has_properties_buffer = properties_buffer, + stage_uniform_block.layout = switch (uniform_block.layout) { + .std140 => .STD140, }; - } else { - return .{ - .shader = shader, - .pipeline = pipeline, + + 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, + .int => .INT, + .int2 => .INT2, + .int3 => .INT3, + .int4 => .INT4, + .mat4 => .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{ @@ -144,140 +238,135 @@ pub const Effect = struct { }; }; -pub const Texture = union (enum) { - render: Render, - static: Static, +pub const Texture = struct { + width: u16, + height: u16, + access: Access, - const Render = struct { + pub const Access = union (enum) { + render: RenderAccess, + static: StaticAccess, + }; + + pub const RenderAccess = struct { sampler: sokol.gfx.Sampler, color_image: sokol.gfx.Image, depth_image: sokol.gfx.Image, attachments: sokol.gfx.Attachments, - view_buffer: sokol.gfx.Buffer, - - fn deinit(self: *Render) void { - sokol.gfx.destroyImage(self.color_image); - sokol.gfx.destroyImage(self.depth_image); - sokol.gfx.destroySampler(self.sampler); - sokol.gfx.destroyAttachments(self.attachments); - sokol.gfx.destroyBuffer(self.view_buffer); - - self.* = undefined; - } - - fn init(format: handles.Texture.Desc.Format, access: handles.Texture.Desc.Access.Render) std.mem.Allocator.Error!Render { - const color_image = sokol.gfx.makeImage(.{ - .pixel_format = switch (format) { - .rgba8 => sokol.gfx.PixelFormat.RGBA8, - .bgra8 => sokol.gfx.PixelFormat.BGRA8, - }, - - .width = access.width, - .height = access.height, - .render_target = true, - }); - - const depth_image = sokol.gfx.makeImage(.{ - .width = access.width, - .height = access.height, - .render_target = true, - .pixel_format = .DEPTH_STENCIL, - }); - - const attachments = sokol.gfx.makeAttachments(attachments_desc: { - var desc = sokol.gfx.AttachmentsDesc{ - .depth_stencil = .{ - .image = depth_image, - }, - }; - - desc.colors[0] = .{ - .image = color_image, - }; - - break: attachments_desc desc; - }); - - const sampler = sokol.gfx.makeSampler(.{}); - - const view_buffer = sokol.gfx.makeBuffer(.{ - .data = sokol.gfx.asRange(&lina.orthographic_projection(-1.0, 1.0, .{ - .left = 0, - .top = 0, - .right = @floatFromInt(access.width), - .bottom = @floatFromInt(access.height), - })), - - .type = .STORAGEBUFFER, - .usage = .IMMUTABLE, - }); - - return .{ - .attachments = attachments, - .sampler = sampler, - .color_image = color_image, - .depth_image = depth_image, - .view_buffer = view_buffer, - }; - } }; - const Static = struct { - image: sokol.gfx.Image, + pub const StaticAccess = struct { sampler: sokol.gfx.Sampler, - - fn deinit(self: *Static) void { - sokol.gfx.destroyImage(self.image); - sokol.gfx.destroySampler(self.sampler); - - self.* = undefined; - } - - fn init(format: handles.Texture.Desc.Format, access: handles.Texture.Desc.Access.Static) std.mem.Allocator.Error!Static { - const image = sokol.gfx.makeImage(image_desc: { - var desc = sokol.gfx.ImageDesc{ - .height = std.math.cast(i32, access.data.len / (access.width * format.byte_size())) orelse { - return error.OutOfMemory; - }, - - .pixel_format = switch (format) { - .rgba8 => sokol.gfx.PixelFormat.RGBA8, - .bgra8 => sokol.gfx.PixelFormat.BGRA8, - }, - - .width = access.width, - }; - - desc.data.subimage[0][0] = sokol.gfx.asRange(access.data); - - break: image_desc desc; - }); - - const sampler = sokol.gfx.makeSampler(.{}); - - errdefer { - sokol.gfx.destroySampler(sampler); - sokol.gfx.destroyImage(image); - } - - return .{ - .image = image, - .sampler = sampler, - }; - } + image: sokol.gfx.Image, }; pub fn deinit(self: *Texture) void { - switch (self.*) { - .static => |*static| static.deinit(), - .render => |*render| render.deinit(), + switch (self.access) { + .render => |render| { + sokol.gfx.destroyImage(render.color_image); + sokol.gfx.destroyImage(render.depth_image); + sokol.gfx.destroySampler(render.sampler); + sokol.gfx.destroyAttachments(render.attachments); + }, + + .static => |static| { + sokol.gfx.destroyImage(static.image); + sokol.gfx.destroySampler(static.sampler); + }, } + + self.* = undefined; } pub fn init(desc: handles.Texture.Desc) std.mem.Allocator.Error!Texture { - return switch (desc.access) { - .static => |static| .{.static = try Static.init(desc.format, static)}, - .render => |render| .{.render = try Render.init(desc.format, render)}, + const pixel_format = switch (desc.format) { + .rgba8 => sokol.gfx.PixelFormat.RGBA8, + .bgra8 => sokol.gfx.PixelFormat.BGRA8, }; + + switch (desc.access) { + .render => |render| { + 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; + }); + + const sampler = sokol.gfx.makeSampler(.{}); + + return .{ + .width = render.width, + .height = render.height, + + .access = .{ + .render = .{ + .attachments = attachments, + .sampler = sampler, + .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; + }; + + 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; + }); + + const sampler = sokol.gfx.makeSampler(.{}); + + errdefer { + sokol.gfx.destroySampler(sampler); + sokol.gfx.destroyImage(image); + } + + return .{ + .width = static.width, + .height = height, + + .access = .{ + .static = .{ + .image = image, + .sampler = sampler, + }, + }, + }; + }, + } } }; diff --git a/src/ona/gfx/shaders/2d_default.vert b/src/ona/gfx/shaders/2d_default.vert index 47cf412..4c4bb5e 100644 --- a/src/ona/gfx/shaders/2d_default.vert +++ b/src/ona/gfx/shaders/2d_default.vert @@ -13,7 +13,7 @@ layout (location = 7) in vec4 instance_rect; layout (location = 0) out vec4 color; layout (location = 1) out vec2 uv; -layout (binding = 0) readonly buffer View { +layout (binding = 0) uniform View { mat4 projection_matrix; }; diff --git a/src/ona/gfx/spirv.zig b/src/ona/gfx/spirv.zig index 222ed02..6405c68 100644 --- a/src/ona/gfx/spirv.zig +++ b/src/ona/gfx/spirv.zig @@ -9,18 +9,33 @@ const sokol = @import("sokol"); const std = @import("std"); pub const Error = std.mem.Allocator.Error || error { - UnsupportedTarget, InvalidSPIRV, UnsupportedSPIRV, - UnsupportedBackend, }; -pub const Shader = struct { - decompiled_source: [:0]const u8 = "", - has_storage_buffer: [max_storage_buffers]?StorageBuffer = [_]?StorageBuffer{null} ** max_storage_buffers, - has_image: [max_images]?Image = [_]?Image{null} ** max_images, - has_sampler: [max_samplers]?Sampler = [_]?Sampler{null} ** max_samplers, - has_image_sampler_pair: [max_image_sampler_pairs]?ImageSamplerPair = [_]?ImageSamplerPair{null} ** max_image_sampler_pairs, +pub const Options = struct { + target: Target, + vertex_source: Source, + fragment_source: Source, +}; + +pub const Program = struct { + vertex_stage: Stage, + fragment_stage: Stage, +}; + +pub const Source = struct { + entry_point: []const u8 = "main", + ops: []const u32, +}; + +pub const Stage = struct { + entry_point: [:0]const u8, + source: [:0]const u8, + has_image_sampler_pairs: [max_image_sampler_pairs]?ImageSamplerPair = [_]?ImageSamplerPair{null} ** max_image_sampler_pairs, + has_images: [max_images]?Image = [_]?Image{null} ** max_images, + has_samplers: [max_samplers]?Sampler = [_]?Sampler{null} ** max_samplers, + has_uniform_blocks: [max_uniform_blocks]?UniformBlock = [_]?UniformBlock{null} ** max_uniform_blocks, pub const Image = struct { is_multisampled: bool, @@ -32,15 +47,65 @@ pub const Shader = struct { name: [:0]const u8, }; + pub const Layout = enum { + std140, + }; + pub const Sampler = enum { filtering, non_filtering, comparison, }; - pub const StorageBuffer = struct { - minimum_size: usize, - is_readonly: bool, + pub const Uniform = struct { + name: [:0]const u8, + type: Type, + len: u16, + + pub const Type = enum { + float, + float2, + float3, + float4, + int, + int2, + int3, + int4, + mat4, + }; + }; + + pub const UniformBlock = struct { + layout: Layout, + uniforms: []const Uniform, + + pub const max_uniforms = 16; + + pub fn size(self: UniformBlock) usize { + const alignment: usize = switch (self.layout) { + .std140 => 16, + }; + + var accumulated_size: usize = 0; + + for (self.uniforms) |uniform| { + const type_size = @max(1, uniform.len) * @as(usize, switch (uniform.type) { + .float => 4, + .float2 => 8, + .float3 => 12, + .float4 => 16, + .int => 4, + .int2 => 8, + .int3 => 12, + .int4 => 16, + .mat4 => 64, + }); + + accumulated_size += (type_size + (alignment - 1)) & ~(alignment - 1); + } + + return accumulated_size; + } }; pub const max_images = 12; @@ -51,7 +116,10 @@ pub const Shader = struct { pub const max_storage_buffers = 8; - fn reflect_image_samplers(self: *Shader, _: Stage, compiler: ext.spvc_compiler, resources: ext.spvc_resources) Error!void { + pub const max_uniform_blocks = 8; + + fn reflect_images_samplers(self: *Stage, arena: *std.heap.ArenaAllocator, compiler: ext.spvc_compiler, resources: ext.spvc_resources) Error!void { + const arena_allocator = arena.allocator(); var sampled_images: []const ext.spvc_reflected_resource = &.{}; try to_error(ext.spvc_resources_get_resource_list_for_type( @@ -82,289 +150,247 @@ pub const Shader = struct { else => error.InvalidSPIRV, }; - self.has_image[i] = .{ + self.has_images[i] = .{ .is_multisampled = ext.spvc_type_get_image_multisampled(sampled_image_type) != 0, }; - self.has_sampler[i] = .filtering; + self.has_samplers[i] = .filtering; - self.has_image_sampler_pair[i] = .{ - .name = std.mem.span(ext.spvc_compiler_get_name(compiler, sampled_image.id)), + self.has_image_sampler_pairs[i] = .{ + .name = try arena_allocator.dupeZ(u8, std.mem.span(ext.spvc_compiler_get_name(compiler, sampled_image.id))), .image_slot = @intCast(i), .sampler_slot = @intCast(i), }; } } - fn reflect_storage_buffers(self: *Shader, stage: Stage, compiler: ext.spvc_compiler, resources: ext.spvc_resources) Error!void { - var storage_buffers: []const ext.spvc_reflected_resource = &.{}; - - try to_error(ext.spvc_resources_get_resource_list_for_type( - resources, - ext.SPVC_RESOURCE_TYPE_STORAGE_BUFFER, - @ptrCast(&storage_buffers.ptr), - &storage_buffers.len, - )); - - const binding_offset: c_uint = if (stage == .fragment) max_storage_buffers else 0; - - for (storage_buffers) |storage_buffer| { - const binding = ext.spvc_compiler_get_decoration(compiler, storage_buffer.id, ext.SpvDecorationBinding); - - if (binding >= max_storage_buffers) { - return error.InvalidSPIRV; - } - - ext.spvc_compiler_set_decoration(compiler, storage_buffer.id, ext.SpvDecorationBinding, binding_offset + binding); - - var block_decorations: []const ext.SpvDecoration = &.{}; - - try to_error(ext.spvc_compiler_get_buffer_block_decorations( - compiler, - storage_buffer.id, - @ptrCast(&block_decorations.ptr), - &block_decorations.len, - )); - - var minimum_size: usize = 0; - - try to_error(ext.spvc_compiler_get_declared_struct_size(compiler, ext.spvc_compiler_get_type_handle(compiler, storage_buffer.base_type_id), &minimum_size)); - - self.has_storage_buffer[binding] = .{ - .is_readonly = std.mem.indexOfScalar(ext.SpvDecoration, block_decorations, ext.SpvDecorationNonWritable) != null, - .minimum_size = minimum_size, - }; - } - } - - fn reflect_uniform_buffers(self: *Shader, _: Stage, compiler: ext.spvc_compiler, resources: ext.spvc_resources) Error!void { - var uniform_buffers: []const ext.spvc_reflected_resource = &.{}; + fn reflect_uniform_blocks(self: *Stage, arena: *std.heap.ArenaAllocator, compiler: ext.spvc_compiler, resources: ext.spvc_resources) Error!void { + const arena_allocator = arena.allocator(); + var reflected_resources: []const ext.spvc_reflected_resource = &.{}; try to_error(ext.spvc_resources_get_resource_list_for_type( resources, ext.SPVC_RESOURCE_TYPE_UNIFORM_BUFFER, - @ptrCast(&uniform_buffers.ptr), - &uniform_buffers.len, + @ptrCast(&reflected_resources.ptr), + &reflected_resources.len, )); - if (uniform_buffers.len != 0) { + if (reflected_resources.len > max_uniform_blocks) { return error.InvalidSPIRV; } - // TODO: Support for older APIs? - _ = self; - _ = compiler; - } + for (0 .. reflected_resources.len, reflected_resources) |i, reflected_resource| { + const type_handle = ext.spvc_compiler_get_type_handle(compiler, reflected_resource.base_type_id); + const is_std430 = ext.spvc_compiler_has_decoration(compiler, reflected_resource.id, ext.SpvDecorationBufferBlock) == ext.SPVC_TRUE; - pub fn stage_desc(self: Shader) sokol.gfx.ShaderStageDesc { - var stage = sokol.gfx.ShaderStageDesc{ - .entry = "main", - .source = self.decompiled_source, - }; - - for (0 .. max_storage_buffers) |slot| { - const storage_buffer = &(self.has_storage_buffer[slot] orelse { - continue; - }); - - stage.storage_buffers[slot] = .{ - .readonly = storage_buffer.is_readonly, - .used = true, - }; - } - - for (0 .. max_images) |slot| { - const image = &(self.has_image[slot] orelse { - continue; - }); - - stage.images[slot] = .{ - .multisampled = image.is_multisampled, - .image_type = ._2D, - .sample_type = .FLOAT, - .used = true, - }; - } - - for (0 .. max_samplers) |slot| { - const sampler = &(self.has_sampler[slot] orelse { - continue; - }); - - stage.samplers[slot] = .{ - .sampler_type = switch (sampler.*) { - .filtering => .FILTERING, - .non_filtering => .NONFILTERING, - .comparison => .COMPARISON, - }, - - .used = true, - }; - } - - for (0 .. max_image_sampler_pairs) |slot| { - const image_sampler_pair = &(self.has_image_sampler_pair[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; - } -}; - -pub const Stage = enum { - vertex, - fragment, -}; - -pub const Unit = struct { - arena: std.heap.ArenaAllocator, - context: ext.spvc_context, - attrs_used: u32 = 0, - stages: [std.enums.values(Stage).len]Shader = .{.{}, .{}}, - - pub fn compile(self: *Unit, spirv_ops: []const u32, stage: Stage) Error!void { - const execution_model, const shader = switch (stage) { - .vertex => .{ext.SpvExecutionModelVertex, &self.stages[0]}, - .fragment => .{ext.SpvExecutionModelFragment, &self.stages[1]}, - }; - - const Backend = struct { - target: ext.spvc_backend, - option_values: []const struct {ext.spvc_compiler_option, c_uint}, - }; - - const backend: Backend = try switch (sokol.gfx.queryBackend()) { - .GLCORE => .{ - .target = ext.SPVC_BACKEND_GLSL, - - .option_values = &.{ - .{ext.SPVC_COMPILER_OPTION_GLSL_VERSION, 430}, - .{ext.SPVC_COMPILER_OPTION_GLSL_ES, @intFromBool(false)}, - .{ext.SPVC_COMPILER_OPTION_GLSL_VULKAN_SEMANTICS, @intFromBool(false)}, - .{ext.SPVC_COMPILER_OPTION_GLSL_EMIT_UNIFORM_BUFFER_AS_PLAIN_UNIFORMS, @intFromBool(true)}, - .{ext.SPVC_COMPILER_OPTION_FLIP_VERTEX_Y, @intFromBool(true)}, - }, - }, - - else => error.UnsupportedBackend, - }; - - const compiler = parse_and_configure: { - var parsed_ir: ext.spvc_parsed_ir = null; - - try to_error(ext.spvc_context_parse_spirv(self.context, spirv_ops.ptr, spirv_ops.len, &parsed_ir)); - - var compiler: ext.spvc_compiler = null; - - try to_error(ext.spvc_context_create_compiler(self.context, backend.target, parsed_ir, ext.SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, &compiler)); - try to_error(ext.spvc_compiler_build_combined_image_samplers(compiler)); - - var combined_image_samplers: []const ext.spvc_combined_image_sampler = &.{}; - - try to_error(ext.spvc_compiler_get_combined_image_samplers(compiler, @ptrCast(&combined_image_samplers.ptr), &combined_image_samplers.len)); - - const arena_allocator = self.arena.allocator(); - var binding: u32 = 0; - - for (combined_image_samplers) |combined_image_sampler| { - const name = try coral.utf8.alloc_formatted(arena_allocator, "{image_name}_{sampler_name}", .{ - .image_name = std.mem.span(ext.spvc_compiler_get_name(compiler, combined_image_sampler.image_id)), - .sampler_name = std.mem.span(ext.spvc_compiler_get_name(compiler, combined_image_sampler.sampler_id)), - }); - - ext.spvc_compiler_set_name(compiler, combined_image_sampler.combined_id, name); - ext.spvc_compiler_set_decoration(compiler, combined_image_sampler.combined_id, ext.SpvDecorationBinding, binding); - - binding += 1; + if (is_std430) { + return error.UnsupportedSPIRV; } - break: parse_and_configure compiler; - }; + const uniform_count = ext.spvc_type_get_num_member_types(type_handle); - try to_error(ext.spvc_compiler_set_entry_point(compiler, "main", @intCast(execution_model))); - - const resources = create: { - var resources: ext.spvc_resources = null; - - try to_error(ext.spvc_compiler_create_shader_resources(compiler, &resources)); - - break: create resources; - }; - - try shader.reflect_uniform_buffers(stage, compiler, resources); - try shader.reflect_storage_buffers(stage, compiler, resources); - try shader.reflect_image_samplers(stage, compiler, resources); - - try to_error(ext.spvc_compiler_install_compiler_options(compiler, create: { - var compiler_options: ext.spvc_compiler_options = null; - - try to_error(ext.spvc_compiler_create_compiler_options(compiler, &compiler_options)); - - for (backend.option_values) |option_value| { - const entry, const value = option_value; - - try to_error(ext.spvc_compiler_options_set_uint(compiler_options, entry, value)); + if (uniform_count > UniformBlock.max_uniforms) { + return error.UnsupportedSPIRV; } - break: create compiler_options; - })); + const uniforms = try arena_allocator.alloc(Uniform, uniform_count); - try to_error(ext.spvc_compiler_compile(compiler, @ptrCast(&shader.decompiled_source.ptr))); + for (uniforms, 0 .. uniform_count) |*uniform, j| { + const member_index: c_uint = @intCast(j); + const member_type_handle = ext.spvc_compiler_get_type_handle(compiler, ext.spvc_type_get_member_type(type_handle, member_index)); - shader.decompiled_source.len = std.mem.span(shader.decompiled_source.ptr).len; + if (ext.spvc_type_get_num_array_dimensions(member_type_handle) > 1) { + return error.UnsupportedSPIRV; + } - std.log.info("{s}", .{shader.decompiled_source}); - } + uniform.* = .{ + .name = try coral.utf8.alloc_formatted(arena_allocator, "_{id}.{member_name}", .{ + .id = reflected_resource.id, + .member_name = std.mem.span(ext.spvc_compiler_get_member_name(compiler, reflected_resource.base_type_id, member_index)), + }), - pub fn deinit(self: *Unit) void { - ext.spvc_context_destroy(self.context); - self.arena.deinit(); + .type = try switch (ext.spvc_type_get_basetype(member_type_handle)) { + ext.SPVC_BASETYPE_FP32 => switch (ext.spvc_type_get_vector_size(member_type_handle)) { + 4 => switch (ext.spvc_type_get_columns(member_type_handle)) { + 4 => Uniform.Type.mat4, + 1 => Uniform.Type.float4, + else => error.UnsupportedSPIRV, + }, - self.* = undefined; - } + 1 => Uniform.Type.float, + 2 => Uniform.Type.float2, + 3 => Uniform.Type.float3, + else => error.UnsupportedSPIRV, + }, - pub fn init(allocator: std.mem.Allocator) std.mem.Allocator.Error!Unit { - var context: ext.spvc_context = null; + ext.SPVC_BASETYPE_INT32 => try switch (ext.spvc_type_get_vector_size(member_type_handle)) { + 1 => Uniform.Type.int, + 2 => Uniform.Type.int2, + 3 => Uniform.Type.int3, + 4 => Uniform.Type.int4, + else => error.UnsupportedSPIRV, + }, - if (ext.spvc_context_create(&context) != ext.SPVC_SUCCESS) { - return error.OutOfMemory; + else => error.UnsupportedSPIRV, + }, + + .len = std.math.cast(u16, ext.spvc_type_get_array_dimension(member_type_handle, 0)) orelse { + return error.UnsupportedSPIRV; + }, + }; + } + + self.has_uniform_blocks[i] = .{ + .uniforms = uniforms, + .layout = .std140, + }; } - - errdefer ext.spvc_context_destroy(context); - - ext.spvc_context_set_error_callback(context, log_context_errors, null); - - return .{ - .arena = std.heap.ArenaAllocator.init(allocator), - .context = context, - }; - } - - pub fn shader_desc(self: Unit) sokol.gfx.ShaderDesc { - return .{ - .vs = self.shader_stage(.vertex).stage_desc(), - .fs = self.shader_stage(.fragment).stage_desc(), - }; - } - - pub fn shader_stage(self: *const Unit, stage: Stage) *const Shader { - return &self.stages[@intFromEnum(stage)]; } }; -fn log_context_errors(userdata: ?*anyopaque, error_message: [*c]const u8) callconv(.C) void { +pub const Target = enum { + glsl, +}; + +pub fn analyze(arena: *std.heap.ArenaAllocator, options: Options) Error!Program { + var context: ext.spvc_context = null; + + if (ext.spvc_context_create(&context) != ext.SPVC_SUCCESS) { + return error.OutOfMemory; + } + + defer { + ext.spvc_context_destroy(context); + } + + ext.spvc_context_set_error_callback(context, log_errors, null); + + const arena_allocator = arena.allocator(); + + return .{ + .vertex_stage = vertex_stage: { + const compiler = try parse(arena, context, options.target, options.vertex_source); + + var stage = Stage{ + .entry_point = try arena_allocator.dupeZ(u8, options.vertex_source.entry_point), + .source = try compile(arena, compiler), + }; + + const resources = create: { + var resources: ext.spvc_resources = null; + + try to_error(ext.spvc_compiler_create_shader_resources(compiler, &resources)); + + break: create resources; + }; + + try stage.reflect_images_samplers(arena, compiler, resources); + try stage.reflect_uniform_blocks(arena, compiler, resources); + + std.log.info("{s}", .{stage.source}); + + break: vertex_stage stage; + }, + + .fragment_stage = fragment_stage: { + const compiler = try parse(arena, context, options.target, options.fragment_source); + + var stage = Stage{ + .entry_point = try arena_allocator.dupeZ(u8, options.fragment_source.entry_point), + .source = try compile(arena, compiler), + }; + + const resources = create: { + var resources: ext.spvc_resources = null; + + try to_error(ext.spvc_compiler_create_shader_resources(compiler, &resources)); + + break: create resources; + }; + + try stage.reflect_images_samplers(arena, compiler, resources); + try stage.reflect_uniform_blocks(arena, compiler, resources); + + std.log.info("{s}", .{stage.source}); + + break: fragment_stage stage; + }, + }; +} + +fn compile(arena: *std.heap.ArenaAllocator, compiler: ext.spvc_compiler) Error![:0]const u8 { + const arena_allocator = arena.allocator(); + var source: [*:0]const u8 = ""; + + try to_error(ext.spvc_compiler_compile(compiler, @ptrCast(&source))); + + return arena_allocator.dupeZ(u8, std.mem.span(source)); +} + +fn log_errors(userdata: ?*anyopaque, error_message: [*c]const u8) callconv(.C) void { std.debug.assert(userdata == null); std.log.err("{s}", .{error_message}); } +fn parse(arena: *std.heap.ArenaAllocator, context: ext.spvc_context, target: Target, source: Source) Error!ext.spvc_compiler { + var parsed_ir: ext.spvc_parsed_ir = null; + + try to_error(ext.spvc_context_parse_spirv(context, source.ops.ptr, source.ops.len, &parsed_ir)); + + var compiler: ext.spvc_compiler = null; + + const spvc_target = switch (target) { + .glsl => ext.SPVC_BACKEND_GLSL, + }; + + try to_error(ext.spvc_context_create_compiler(context, spvc_target, parsed_ir, ext.SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, &compiler)); + + try to_error(ext.spvc_compiler_build_combined_image_samplers(compiler)); + + var combined_image_samplers: []const ext.spvc_combined_image_sampler = &.{}; + + try to_error(ext.spvc_compiler_get_combined_image_samplers(compiler, @ptrCast(&combined_image_samplers.ptr), &combined_image_samplers.len)); + + const arena_allocator = arena.allocator(); + var binding: u32 = 0; + + for (combined_image_samplers) |combined_image_sampler| { + const name = try coral.utf8.alloc_formatted(arena_allocator, "{image_name}_{sampler_name}", .{ + .image_name = std.mem.span(ext.spvc_compiler_get_name(compiler, combined_image_sampler.image_id)), + .sampler_name = std.mem.span(ext.spvc_compiler_get_name(compiler, combined_image_sampler.sampler_id)), + }); + + ext.spvc_compiler_set_name(compiler, combined_image_sampler.combined_id, name); + ext.spvc_compiler_set_decoration(compiler, combined_image_sampler.combined_id, ext.SpvDecorationBinding, binding); + + binding += 1; + } + + const option_values: []const struct {ext.spvc_compiler_option, c_uint} = switch (target) { + .glsl => &.{ + .{ext.SPVC_COMPILER_OPTION_GLSL_VERSION, 430}, + .{ext.SPVC_COMPILER_OPTION_GLSL_ES, @intFromBool(false)}, + .{ext.SPVC_COMPILER_OPTION_GLSL_VULKAN_SEMANTICS, @intFromBool(false)}, + .{ext.SPVC_COMPILER_OPTION_GLSL_EMIT_UNIFORM_BUFFER_AS_PLAIN_UNIFORMS, @intFromBool(true)}, + .{ext.SPVC_COMPILER_OPTION_FLIP_VERTEX_Y, @intFromBool(true)}, + }, + }; + + var compiler_options: ext.spvc_compiler_options = null; + + try to_error(ext.spvc_compiler_create_compiler_options(compiler, &compiler_options)); + + for (option_values) |option_value| { + const entry, const value = option_value; + + try to_error(ext.spvc_compiler_options_set_uint(compiler_options, entry, value)); + } + + try to_error(ext.spvc_compiler_install_compiler_options(compiler, compiler_options)); + + return compiler; +} + fn to_error(result: ext.spvc_result) Error!void { return switch (result) { ext.SPVC_SUCCESS => {}, From 67dee07f8e13cc5b129b8185f9099d9315e481e3 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 21 Jul 2024 18:59:28 +0100 Subject: [PATCH 28/30] Implement CRT shader effect support --- debug/crt.frag | 52 +++++++ src/main.zig | 60 ++++++-- src/ona/App.zig | 1 + src/ona/gfx/handles.zig | 57 +++---- src/ona/gfx/rendering.zig | 222 ++++++++++++++++++---------- src/ona/gfx/resources.zig | 42 +++--- src/ona/gfx/shaders/2d_default.vert | 3 +- src/ona/gfx/spirv.zig | 44 +++--- src/ona/ona.zig | 5 +- 9 files changed, 332 insertions(+), 154 deletions(-) create mode 100644 debug/crt.frag diff --git a/debug/crt.frag b/debug/crt.frag new file mode 100644 index 0000000..682f44e --- /dev/null +++ b/debug/crt.frag @@ -0,0 +1,52 @@ +#version 430 + +layout (binding = 0) uniform sampler2D sprite; + +layout (location = 0) in vec4 color; +layout (location = 1) in vec2 uv; + +layout (location = 0) out vec4 texel; + +layout (binding = 0) uniform Effect { + float screen_width; + float screen_height; + float time; +}; + +vec3 scanline(vec2 coord, vec3 screen) +{ + screen.rgb -= sin((coord.y + (time * 29.0))) * 0.02; + return screen; +} + +vec2 crt(vec2 coord, float bend) +{ + // put in symmetrical coords + coord = (coord - 0.5) * 2.0; + + coord *= 1.0; + + // deform coords + coord.x *= 1.0 + pow((abs(coord.y) / bend), 2.0); + coord.y *= 1.0 + pow((abs(coord.x) / bend), 2.0); + + // transform back to 0.0 - 1.0 space + coord = (coord / 2.0) + 0.5; + + return coord; +} + +void main() +{ + vec2 crtCoords = crt(uv, 4.8); + + // Split the color channels + texel.rgb = texture(sprite, crtCoords).rgb; + texel.a = 1; + + // HACK: this bend produces a shitty moire pattern. + // Up the bend for the scanline + vec2 screenSpace = crtCoords * vec2(screen_width, screen_height); + texel.rgb = scanline(screenSpace, texel.rgb); +} + diff --git a/src/main.zig b/src/main.zig index d9139d5..4d84891 100644 --- a/src/main.zig +++ b/src/main.zig @@ -9,11 +9,20 @@ const ChromaticAberration = extern struct { padding: [12]u8 = undefined, }; +const CRT = extern struct { + width: f32, + height: f32, + time: f32, + padding: [4]u8 = undefined, +}; + const Actors = struct { instances: coral.stack.Sequential(@Vector(2, f32)) = .{.allocator = coral.heap.allocator}, body_texture: ona.gfx.Texture = .default, render_texture: ona.gfx.Texture = .default, ca_effect: ona.gfx.Effect = .default, + crt_effect: ona.gfx.Effect = .default, + staging_texture: ona.gfx.Texture = .default, }; const Player = struct { @@ -44,6 +53,7 @@ fn load(config: ona.Write(ona.gfx.Config), actors: ona.Write(Actors), assets: on }); actors.res.ca_effect = try assets.res.load_effect_file(coral.files.bundle, "./ca.frag.spv"); + actors.res.crt_effect = try assets.res.load_effect_file(coral.files.bundle, "./crt.frag.spv"); try actors.res.instances.push_grow(.{0, 0}); } @@ -52,22 +62,46 @@ fn exit(actors: ona.Write(Actors)) void { actors.res.instances.deinit(); } -fn render(commands: ona.gfx.Commands, actors: ona.Write(Actors)) !void { - try commands.set_effect(actors.res.ca_effect, ChromaticAberration{ - .effect_magnitude = 15.0, +fn render(commands: ona.gfx.Commands, actors: ona.Write(Actors), app: ona.Read(ona.App)) !void { + try commands.set_target(.{ + .texture = actors.res.render_texture, + .clear_color = ona.gfx.colors.black, + .clear_depth = 0, + .clear_stencil = 0, }); - for (actors.res.instances.values) |instance| { - try commands.draw_texture(.{ - .texture = actors.res.body_texture, + try commands.draw_texture(.{ + .texture = .default, - .transform = .{ - .origin = instance, - .xbasis = .{64, 0}, - .ybasis = .{0, 64}, - }, - }); - } + .transform = .{ + .origin = .{1280 / 2, 720 / 2}, + .xbasis = .{1280, 0}, + .ybasis = .{0, 720}, + }, + }); + + try commands.set_effect(actors.res.crt_effect, CRT{ + .width = 1280, + .height = 720, + .time = @floatCast(app.res.elapsed_time), + }); + + try commands.set_target(.{ + .texture = .backbuffer, + .clear_color = null, + .clear_depth = null, + .clear_stencil = null, + }); + + try commands.draw_texture(.{ + .texture = actors.res.render_texture, + + .transform = .{ + .origin = .{1280 / 2, 720 / 2}, + .xbasis = .{1280, 0}, + .ybasis = .{0, 720}, + }, + }); } fn update(player: ona.Read(Player), actors: ona.Write(Actors), mapping: ona.Read(ona.act.Mapping)) !void { diff --git a/src/ona/App.zig b/src/ona/App.zig index e985626..ab428d2 100644 --- a/src/ona/App.zig +++ b/src/ona/App.zig @@ -4,6 +4,7 @@ const flow = @import("flow"); events: *const Events, target_frame_time: f64, +elapsed_time: f64, is_running: bool, pub const Events = struct { diff --git a/src/ona/gfx/handles.zig b/src/ona/gfx/handles.zig index 942134f..ac1680d 100644 --- a/src/ona/gfx/handles.zig +++ b/src/ona/gfx/handles.zig @@ -17,33 +17,40 @@ fn Handle(comptime HandleDesc: type) type { }; } -pub const Texture = Handle(struct { - format: Format, - access: Access, +pub const Texture = enum (u32) { + default, + backbuffer, + _, - pub const Access = union (enum) { - static: Static, - render: Render, + pub const Desc = struct { + format: Format, + access: Access, - pub const Static = struct { - width: u16, - data: []const coral.io.Byte, - }; + pub const Access = union (enum) { + static: Static, + render: Render, - 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, + 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/rendering.zig b/src/ona/gfx/rendering.zig index 4d53f11..95f19fb 100644 --- a/src/ona/gfx/rendering.zig +++ b/src/ona/gfx/rendering.zig @@ -30,7 +30,7 @@ pub const Command = union (enum) { }; pub const SetTarget = struct { - texture: ?handles.Texture = null, + texture: handles.Texture, clear_color: ?lina.Color, clear_depth: ?f32, clear_stencil: ?u8, @@ -135,7 +135,7 @@ const Frame = struct { drawn_count: usize = 0, flushed_count: usize = 0, current_source_texture: handles.Texture = .default, - current_target_texture: ?handles.Texture = null, + current_target_texture: handles.Texture = .backbuffer, current_effect: handles.Effect = .default, const DrawTexture = extern struct { @@ -185,42 +185,39 @@ const Frame = struct { bindings.vertex_buffers[vertex_indices.mesh] = quad_vertex_buffer; - switch (pools.textures.get(@intFromEnum(self.current_source_texture)).?.access) { + switch (pools.get_texture(self.current_source_texture).?.access) { .render => |render| { bindings.fs.images[0] = render.color_image; - bindings.fs.samplers[0] = render.sampler; + bindings.fs.samplers[0] = default_sampler; }, .static => |static| { bindings.fs.images[0] = static.image; - bindings.fs.samplers[0] = static.sampler; + bindings.fs.samplers[0] = default_sampler; + }, + + .empty => { + @panic("Cannot render empty textures"); }, } - const effect = pools.effects.get(@intFromEnum(self.current_effect)).?; + const effect = pools.get_effect(self.current_effect).?; sokol.gfx.applyPipeline(effect.pipeline); - if (self.current_target_texture) |target_texture| { - const texture = pools.textures.get(@intFromEnum(target_texture)).?; + const texture = pools.get_texture(self.current_target_texture).?; - sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&lina.orthographic_projection(-1.0, 1.0, .{ - .left = 0, - .top = 0, - .right = @floatFromInt(texture.width), - .bottom = @floatFromInt(texture.height), - }))); - } else { - sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&lina.orthographic_projection(-1.0, 1.0, .{ - .left = 0, - .top = 0, - .right = @floatFromInt(self.swapchain.width), - .bottom = @floatFromInt(self.swapchain.height), - }))); + sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&lina.orthographic_projection(-1.0, 1.0, .{ + .left = 0, + .top = 0, + .right = @floatFromInt(texture.width), + .bottom = @floatFromInt(texture.height), + }))); + + if (effect.properties.len != 0) { + sokol.gfx.applyUniforms(.FS, 0, sokol.gfx.asRange(effect.properties)); } - sokol.gfx.applyUniforms(.FS, 0, sokol.gfx.asRange(effect.properties)); - while (true) { const buffer_index = self.flushed_count / batches_per_buffer; const buffer_offset = self.flushed_count % batches_per_buffer; @@ -246,7 +243,7 @@ const Frame = struct { self.current_effect = command.effect; - if (pools.effects.get(@intFromEnum(self.current_effect))) |effect| { + if (pools.get_effect(self.current_effect)) |effect| { @memcpy(effect.properties, command.properties); } } @@ -280,17 +277,13 @@ const Frame = struct { pass.action.depth = .{.load_action = .LOAD}; } - if (command.texture) |texture| { - pass.attachments = switch (pools.textures.get(@intFromEnum(texture)).?.access) { - .static => @panic("Cannot render to static textures"), - .render => |render| render.attachments, - }; + pass.attachments = switch (pools.get_texture(self.current_target_texture).?.access) { + .static => @panic("Cannot render to static textures"), + .empty => @panic("Cannot render to empty textures"), + .render => |render| render.attachments, + }; - self.current_target_texture = command.texture; - } else { - pass.swapchain = self.swapchain; - self.current_target_texture = null; - } + self.current_target_texture = command.texture; sokol.gfx.beginPass(pass); } @@ -313,7 +306,7 @@ const Pools = struct { const TexturePool = coral.Pool(resources.Texture); - fn create_effect(self: *Pools, desc: handles.Effect.Desc) !handles.Effect { + pub fn create_effect(self: *Pools, desc: handles.Effect.Desc) !handles.Effect { var effect = try resources.Effect.init(desc); errdefer effect.deinit(); @@ -321,7 +314,7 @@ const Pools = struct { return @enumFromInt(try self.effects.insert(effect)); } - fn create_texture(self: *Pools, desc: handles.Texture.Desc) !handles.Texture { + pub fn create_texture(self: *Pools, desc: handles.Texture.Desc) !handles.Texture { var texture = try resources.Texture.init(desc); errdefer texture.deinit(); @@ -329,7 +322,7 @@ const Pools = struct { return @enumFromInt(try self.textures.insert(texture)); } - fn deinit(self: *Pools) void { + pub fn deinit(self: *Pools) void { var textures = self.textures.values(); while (textures.next()) |texture| { @@ -339,7 +332,7 @@ const Pools = struct { self.textures.deinit(); } - fn destroy_effect(self: *Pools, handle: handles.Effect) bool { + pub fn destroy_effect(self: *Pools, handle: handles.Effect) bool { switch (handle) { .default => {}, @@ -355,7 +348,7 @@ const Pools = struct { return true; } - fn destroy_texture(self: *Pools, handle: handles.Texture) bool { + pub fn destroy_texture(self: *Pools, handle: handles.Texture) bool { switch (handle) { .default => {}, @@ -371,7 +364,15 @@ const Pools = struct { return true; } - fn init(allocator: std.mem.Allocator) !Pools { + pub fn get_effect(self: *Pools, handle: handles.Effect) ?*resources.Effect { + return self.effects.get(@intFromEnum(handle)); + } + + pub fn get_texture(self: *Pools, handle: handles.Texture) ?*resources.Texture { + return self.textures.get(@intFromEnum(handle)); + } + + pub fn init(allocator: std.mem.Allocator) !Pools { var pools = Pools{ .effects = EffectPool.init(allocator), .textures = TexturePool.init(allocator), @@ -381,31 +382,47 @@ const Pools = struct { pools.deinit(); } - _ = try pools.create_effect(.{ - .fragment_spirv_ops = &spirv.to_ops(@embedFile("./shaders/2d_default.frag.spv")), - }); - - const default_texture_data = [_]u32{ - 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, - 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, - 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, - 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, - 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, - 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, - 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, - 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, + const assert = struct { + fn is_handle(expected: anytype, actual: @TypeOf(expected)) void { + std.debug.assert(actual == expected); + } }; - _ = try pools.create_texture(.{ + assert.is_handle(handles.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(.{ .format = .rgba8, .access = .{ .static = .{ - .data = std.mem.asBytes(&default_texture_data), + .data = std.mem.asBytes(&[_]u32{ + 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, + 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, + 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, + 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, + 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, + 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, + 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, + 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, + }), + .width = 8, }, }, - }); + })); + + assert.is_handle(handles.Texture.backbuffer, try pools.create_texture(.{ + .format = .rgba8, + + .access = .{ + .render = .{ + .width = 0, + .height = 0, + }, + } + })); return pools; } @@ -457,6 +474,8 @@ pub const Work = union (enum) { 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); } @@ -541,6 +560,8 @@ fn run(window: *ext.SDL_Window) !void { .type = .VERTEXBUFFER, }); + default_sampler = sokol.gfx.makeSampler(.{}); + var has_commands_head: ?*Commands = null; var has_commands_tail: ?*Commands = null; @@ -588,6 +609,23 @@ fn run(window: *ext.SDL_Window) !void { }, .render_frame => |render_frame| { + const backbuffer = pools.get_texture(.backbuffer).?; + + if (backbuffer.width != render_frame.width or backbuffer.height != render_frame.height) { + backbuffer.deinit(); + + backbuffer.* = try resources.Texture.init(.{ + .format = .rgba8, + + .access = .{ + .render = .{ + .width = render_frame.width, + .height = render_frame.height, + }, + }, + }); + } + var frame = Frame{ .swapchain = .{ .width = render_frame.width, @@ -599,6 +637,51 @@ fn run(window: *ext.SDL_Window) !void { } }; + sokol.gfx.beginPass(pass: { + var pass = sokol.gfx.Pass{ + .action = .{ + .stencil = .{ + .load_action = .CLEAR, + }, + + .depth = .{ + .load_action = .CLEAR, + .clear_value = 0, + } + }, + }; + + pass.action.colors[0] = .{ + .load_action = .CLEAR, + .clear_value = @bitCast(render_frame.clear_color), + }; + + pass.attachments = pools.get_texture(.backbuffer).?.access.render.attachments; + + break: pass pass; + }); + + var has_commands = has_commands_head; + + while (has_commands) |commands| : (has_commands = commands.has_next) { + for (commands.submitted_commands()) |command| { + try command.process(&pools, &frame); + } + + frame.flush(&pools); + + if (frame.current_target_texture != .backbuffer) { + frame.set_target(&pools, .{ + .texture = .backbuffer, + .clear_color = null, + .clear_depth = null, + .clear_stencil = null, + }); + } + } + + sokol.gfx.endPass(); + sokol.gfx.beginPass(swapchain_pass: { var pass = sokol.gfx.Pass{ .swapchain = frame.swapchain, @@ -609,29 +692,20 @@ fn run(window: *ext.SDL_Window) !void { }, }; - pass.action.colors[0] = .{ - .clear_value = @bitCast(render_frame.clear_color), - .load_action = .CLEAR, - }; + pass.action.colors[0] = .{.load_action = .CLEAR}; break: swapchain_pass pass; }); - var has_commands = has_commands_head; + try frame.draw_texture(&pools, .{ + .texture = .backbuffer, - while (has_commands) |commands| : (has_commands = commands.has_next) { - for (commands.submitted_commands()) |command| { - try command.process(&pools, &frame); - } - - if (frame.current_target_texture != .default) { - frame.set_target(&pools, .{ - .clear_color = null, - .clear_depth = null, - .clear_stencil = null, - }); - } - } + .transform = .{ + .origin = .{@as(f32, @floatFromInt(render_frame.width)) / 2, @as(f32, @floatFromInt(render_frame.height)) / 2}, + .xbasis = .{@floatFromInt(render_frame.width), 0}, + .ybasis = .{0, @floatFromInt(render_frame.height)}, + }, + }); frame.flush(&pools); sokol.gfx.endPass(); diff --git a/src/ona/gfx/resources.zig b/src/ona/gfx/resources.zig index 7a34da9..ca1e3e1 100644 --- a/src/ona/gfx/resources.zig +++ b/src/ona/gfx/resources.zig @@ -123,11 +123,11 @@ pub const Effect = struct { .float2 => .FLOAT2, .float3 => .FLOAT3, .float4 => .FLOAT4, - .int => .INT, - .int2 => .INT2, - .int3 => .INT3, - .int4 => .INT4, - .mat4 => .MAT4, + .integer => .INT, + .integer2 => .INT2, + .integer3 => .INT3, + .integer4 => .INT4, + .matrix4 => .MAT4, }, .name = uniform.name, @@ -244,19 +244,18 @@ pub const Texture = struct { access: Access, pub const Access = union (enum) { + empty, render: RenderAccess, static: StaticAccess, }; pub const RenderAccess = struct { - sampler: sokol.gfx.Sampler, color_image: sokol.gfx.Image, depth_image: sokol.gfx.Image, attachments: sokol.gfx.Attachments, }; pub const StaticAccess = struct { - sampler: sokol.gfx.Sampler, image: sokol.gfx.Image, }; @@ -265,14 +264,14 @@ pub const Texture = struct { .render => |render| { sokol.gfx.destroyImage(render.color_image); sokol.gfx.destroyImage(render.depth_image); - sokol.gfx.destroySampler(render.sampler); sokol.gfx.destroyAttachments(render.attachments); }, .static => |static| { sokol.gfx.destroyImage(static.image); - sokol.gfx.destroySampler(static.sampler); }, + + .empty => {}, } self.* = undefined; @@ -286,6 +285,14 @@ pub const Texture = struct { 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, @@ -314,8 +321,6 @@ pub const Texture = struct { break: attachments_desc attachments_desc; }); - const sampler = sokol.gfx.makeSampler(.{}); - return .{ .width = render.width, .height = render.height, @@ -323,11 +328,10 @@ pub const Texture = struct { .access = .{ .render = .{ .attachments = attachments, - .sampler = sampler, .color_image = color_image, .depth_image = depth_image, }, - } + }, }; }, @@ -336,6 +340,14 @@ pub const Texture = struct { 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, @@ -348,10 +360,7 @@ pub const Texture = struct { break: image_desc image_desc; }); - const sampler = sokol.gfx.makeSampler(.{}); - errdefer { - sokol.gfx.destroySampler(sampler); sokol.gfx.destroyImage(image); } @@ -362,7 +371,6 @@ pub const Texture = struct { .access = .{ .static = .{ .image = image, - .sampler = sampler, }, }, }; diff --git a/src/ona/gfx/shaders/2d_default.vert b/src/ona/gfx/shaders/2d_default.vert index 4c4bb5e..d5045cb 100644 --- a/src/ona/gfx/shaders/2d_default.vert +++ b/src/ona/gfx/shaders/2d_default.vert @@ -21,8 +21,9 @@ void main() { const vec2 world_position = instance_origin + mesh_xy.x * instance_xbasis + mesh_xy.y * instance_ybasis; const vec2 projected_position = (projection_matrix * vec4(world_position, 0, 1)).xy; const vec2 rect_size = instance_rect.zw - instance_rect.xy; + const vec4 screen_position = vec4(projected_position, instance_depth, 1.0); - gl_Position = vec4(projected_position, instance_depth, 1.0); + gl_Position = screen_position; color = instance_tint; uv = instance_rect.xy + (mesh_uv * rect_size); } diff --git a/src/ona/gfx/spirv.zig b/src/ona/gfx/spirv.zig index 6405c68..def2040 100644 --- a/src/ona/gfx/spirv.zig +++ b/src/ona/gfx/spirv.zig @@ -67,11 +67,11 @@ pub const Stage = struct { float2, float3, float4, - int, - int2, - int3, - int4, - mat4, + integer, + integer2, + integer3, + integer4, + matrix4, }; }; @@ -82,29 +82,27 @@ pub const Stage = struct { pub const max_uniforms = 16; pub fn size(self: UniformBlock) usize { - const alignment: usize = switch (self.layout) { - .std140 => 16, - }; - var accumulated_size: usize = 0; for (self.uniforms) |uniform| { - const type_size = @max(1, uniform.len) * @as(usize, switch (uniform.type) { + accumulated_size += @max(1, uniform.len) * @as(usize, switch (uniform.type) { .float => 4, .float2 => 8, .float3 => 12, .float4 => 16, - .int => 4, - .int2 => 8, - .int3 => 12, - .int4 => 16, - .mat4 => 64, + .integer => 4, + .integer2 => 8, + .integer3 => 12, + .integer4 => 16, + .matrix4 => 64, }); - - accumulated_size += (type_size + (alignment - 1)) & ~(alignment - 1); } - return accumulated_size; + const alignment: usize = switch (self.layout) { + .std140 => 16, + }; + + return (accumulated_size + (alignment - 1)) & ~(alignment - 1); } }; @@ -212,7 +210,7 @@ pub const Stage = struct { .type = try switch (ext.spvc_type_get_basetype(member_type_handle)) { ext.SPVC_BASETYPE_FP32 => switch (ext.spvc_type_get_vector_size(member_type_handle)) { 4 => switch (ext.spvc_type_get_columns(member_type_handle)) { - 4 => Uniform.Type.mat4, + 4 => Uniform.Type.matrix4, 1 => Uniform.Type.float4, else => error.UnsupportedSPIRV, }, @@ -224,10 +222,10 @@ pub const Stage = struct { }, ext.SPVC_BASETYPE_INT32 => try switch (ext.spvc_type_get_vector_size(member_type_handle)) { - 1 => Uniform.Type.int, - 2 => Uniform.Type.int2, - 3 => Uniform.Type.int3, - 4 => Uniform.Type.int4, + 1 => Uniform.Type.integer, + 2 => Uniform.Type.integer2, + 3 => Uniform.Type.integer3, + 4 => Uniform.Type.integer4, else => error.UnsupportedSPIRV, }, diff --git a/src/ona/ona.zig b/src/ona/ona.zig index c3b51ea..da5b237 100644 --- a/src/ona/ona.zig +++ b/src/ona/ona.zig @@ -75,6 +75,7 @@ pub fn start_app(setup: Setup, options: Options) anyerror!void { const app = try world.set_get_resource(App{ .events = &events, .target_frame_time = 1.0 / @as(f64, @floatFromInt(options.tick_rate)), + .elapsed_time = 0, .is_running = true, }); @@ -85,7 +86,8 @@ pub fn start_app(setup: Setup, options: Options) anyerror!void { try setup(&world, events); try world.run_event(events.load); - var ticks_previous = std.time.milliTimestamp(); + const ticks_initial = std.time.milliTimestamp(); + var ticks_previous = ticks_initial; var accumulated_time = @as(f64, 0); while (app.is_running) { @@ -93,6 +95,7 @@ pub fn start_app(setup: Setup, options: Options) anyerror!void { const milliseconds_per_second = 1000.0; const delta_time = @as(f64, @floatFromInt(ticks_current - ticks_previous)) / milliseconds_per_second; + app.elapsed_time = @as(f64, @floatFromInt(ticks_current - ticks_initial)) / milliseconds_per_second; ticks_previous = ticks_current; accumulated_time += delta_time; From 5f94d1ca5f3d040a26376c35e72af03e3adf21da Mon Sep 17 00:00:00 2001 From: kayomn Date: Mon, 22 Jul 2024 00:03:24 +0100 Subject: [PATCH 29/30] Tidy up logical flow --- src/coral/coral.zig | 4 +- src/coral/{vectors.zig => lina.zig} | 26 + src/flow/World.zig | 22 +- src/flow/flow.zig | 196 ++++--- src/flow/system.zig | 34 +- src/main.zig | 16 +- src/ona/act.zig | 2 +- src/ona/gfx.zig | 294 +++++++--- src/ona/gfx/bmp.zig | 4 +- src/ona/gfx/colors.zig | 6 +- src/ona/gfx/handles.zig | 56 -- src/ona/gfx/lina.zig | 34 -- src/ona/gfx/resources.zig | 380 ------------- src/ona/gfx/{rendering.zig => sokol.zig} | 656 ++++++++++++++--------- src/ona/gfx/textures.zig | 38 ++ src/ona/msg.zig | 30 +- src/ona/ona.zig | 2 +- 17 files changed, 899 insertions(+), 901 deletions(-) rename src/coral/{vectors.zig => lina.zig} (66%) delete mode 100644 src/ona/gfx/handles.zig delete mode 100644 src/ona/gfx/lina.zig delete mode 100644 src/ona/gfx/resources.zig rename src/ona/gfx/{rendering.zig => sokol.zig} (52%) create mode 100644 src/ona/gfx/textures.zig 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, From d27f7735cbd2e19c71d91855a63ee37127268325 Mon Sep 17 00:00:00 2001 From: kayomn Date: Mon, 22 Jul 2024 00:46:13 +0100 Subject: [PATCH 30/30] Fix memory leaks --- src/flow/World.zig | 8 +- src/flow/flow.zig | 2 +- src/ona/gfx.zig | 93 +++- src/ona/gfx/Resources.zig | 518 ++++++++++++++++++++++ src/ona/gfx/bmp.zig | 79 ---- src/ona/gfx/rendering.zig | 428 ++++++++++++++++++ src/ona/gfx/sokol.zig | 908 -------------------------------------- src/ona/gfx/spirv.zig | 2 - src/ona/gfx/textures.zig | 38 -- 9 files changed, 1037 insertions(+), 1039 deletions(-) create mode 100644 src/ona/gfx/Resources.zig delete mode 100644 src/ona/gfx/bmp.zig create mode 100644 src/ona/gfx/rendering.zig delete mode 100644 src/ona/gfx/sokol.zig delete mode 100644 src/ona/gfx/textures.zig diff --git a/src/flow/World.zig b/src/flow/World.zig index 71db4e9..9041cc9 100644 --- a/src/flow/World.zig +++ b/src/flow/World.zig @@ -31,14 +31,14 @@ pub fn create_event(self: *Self, label: []const u8) std.mem.Allocator.Error!Even } pub fn deinit(self: *Self) void { - for (&self.thread_restricted_resources) |*resources| { - resources.deinit(); - } - for (self.event_systems.values) |*schedule| { schedule.deinit(self); } + for (&self.thread_restricted_resources) |*resources| { + resources.deinit(); + } + if (self.thread_pool) |thread_pool| { thread_pool.deinit(); coral.heap.allocator.destroy(thread_pool); diff --git a/src/flow/flow.zig b/src/flow/flow.zig index 566de79..c9fd36d 100644 --- a/src/flow/flow.zig +++ b/src/flow/flow.zig @@ -255,7 +255,7 @@ fn parameter_type(comptime Value: type) *const system.Info.Parameter { } } - Value.unbind(node); + Value.unbind(&node.param, context); allocator.destroy(node); } } diff --git a/src/ona/gfx.zig b/src/ona/gfx.zig index d393cff..d0567c9 100644 --- a/src/ona/gfx.zig +++ b/src/ona/gfx.zig @@ -4,15 +4,13 @@ pub const colors = @import("./gfx/colors.zig"); const coral = @import("coral"); -const bmp = @import("./gfx/bmp.zig"); - const ext = @import("./ext.zig"); const flow = @import("flow"); const msg = @import("./msg.zig"); -const sokol = @import("./gfx/sokol.zig"); +const rendering = @import("./gfx/rendering.zig"); const std = @import("std"); @@ -261,9 +259,9 @@ pub const Commands = struct { }; } - pub fn init(state: *Param) Commands { + pub fn init(param: *Param) Commands { return .{ - .pending = state.pending_list(), + .pending = param.pending_list(), }; } @@ -280,6 +278,10 @@ pub const Commands = struct { }); } + pub fn unbind(param: *Param, _: flow.system.UnbindContext) void { + param.deinit(); + } + pub fn set_target(self: Commands, command: SetTargetCommand) std.mem.Allocator.Error!void { try self.pending.stack.push_grow(.{.set_target = command}); } @@ -361,6 +363,80 @@ pub const Transform2D = extern struct { const Vector = @Vector(2, f32); }; +fn load_bmp_texture(arena: *std.heap.ArenaAllocator, storage: coral.files.Storage, path: []const u8) !Texture.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), + 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.FormatUnsupported; + }; + + if (!std.mem.eql(u8, &header.type, "BM")) { + return error.FormatUnsupported; + } + + const pixel_width = std.math.cast(u16, header.pixel_width) orelse { + return error.FormatUnsupported; + }; + + 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); + + switch (header.bits_per_pixel) { + 32 => { + while (buffer_offset < pixels.len) { + const line = pixels[buffer_offset .. buffer_offset + byte_stride]; + + if (try storage.read(path, line, file_offset) != byte_stride) { + return error.FormatUnsupported; + } + + 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 .{ + .format = .rgba8, + + .access = .{ + .static = .{ + .width = pixel_width, + .data = pixels, + }, + }, + }; +} + pub fn poll(app: flow.Write(App), inputs: msg.Send(Input)) !void { var event = @as(ext.SDL_Event, undefined); @@ -395,12 +471,15 @@ 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}); + assets.has_worker_thread = try std.Thread.spawn(.{}, rendering.process_work, .{ + &assets.pending_work, + assets.window, + }); const builtin_texture_formats = [_]Assets.TextureFormat{ .{ .extension = "bmp", - .load_file = bmp.load_file, + .load_file = load_bmp_texture, }, }; diff --git a/src/ona/gfx/Resources.zig b/src/ona/gfx/Resources.zig new file mode 100644 index 0000000..b4718de --- /dev/null +++ b/src/ona/gfx/Resources.zig @@ -0,0 +1,518 @@ +const coral = @import("coral"); + +const gfx = @import("../gfx.zig"); + +const sokol = @import("sokol"); + +const spirv = @import("./spirv.zig"); + +const std = @import("std"); + +effects: EffectPool, +textures: TexturePool, + +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: 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; + }, + }; +}; + +const EffectPool = coral.Pool(Effect); + +const Self = @This(); + +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 TexturePool = coral.Pool(Texture); + +pub fn create_effect(self: *Self, 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: *Self, 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: *Self) void { + var textures = self.textures.values(); + + while (textures.next()) |texture| { + texture.deinit(); + } + + self.textures.deinit(); + + var effects = self.effects.values(); + + while (effects.next()) |effect| { + effect.deinit(); + } + + self.effects.deinit(); + + self.* = undefined; +} + +pub fn destroy_effect(self: *Self, handle: gfx.Effect) bool { + switch (handle) { + .default => {}, + + else => { + var effect = self.effects.remove(@intFromEnum(handle)) orelse { + return false; + }; + + effect.deinit(); + }, + } + + return true; +} + +pub fn destroy_texture(self: *Self, handle: gfx.Texture) bool { + switch (handle) { + .default => {}, + + else => { + var texture = self.textures.remove(@intFromEnum(handle)) orelse { + return false; + }; + + texture.deinit(); + }, + } + + return true; +} + +pub fn get_effect(self: *Self, handle: gfx.Effect) ?*Effect { + return self.effects.get(@intFromEnum(handle)); +} + +pub fn get_texture(self: *Self, handle: gfx.Texture) ?*Texture { + return self.textures.get(@intFromEnum(handle)); +} + +pub fn init() !Self { + var pools = Self{ + .effects = EffectPool.init(coral.heap.allocator), + .textures = TexturePool.init(coral.heap.allocator), + }; + + errdefer { + pools.deinit(); + } + + const assert = struct { + fn is_handle(expected: anytype, actual: @TypeOf(expected)) void { + std.debug.assert(actual == expected); + } + }; + + 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(gfx.Texture.default, try pools.create_texture(.{ + .format = .rgba8, + + .access = .{ + .static = .{ + .data = std.mem.asBytes(&[_]u32{ + 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, + 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, + 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, + 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, + 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, + 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, + 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, + 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, + }), + + .width = 8, + }, + }, + })); + + assert.is_handle(gfx.Texture.backbuffer, try pools.create_texture(.{ + .format = .rgba8, + + .access = .{ + .render = .{ + .width = 0, + .height = 0, + }, + } + })); + + return pools; +} diff --git a/src/ona/gfx/bmp.zig b/src/ona/gfx/bmp.zig deleted file mode 100644 index 8bd194a..0000000 --- a/src/ona/gfx/bmp.zig +++ /dev/null @@ -1,79 +0,0 @@ -const coral = @import("coral"); - -const gfx = @import("../gfx.zig"); - -const std = @import("std"); - -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), - 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.FormatUnsupported; - }; - - if (!std.mem.eql(u8, &header.type, "BM")) { - return error.FormatUnsupported; - } - - const pixel_width = std.math.cast(u16, header.pixel_width) orelse { - return error.FormatUnsupported; - }; - - 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); - - switch (header.bits_per_pixel) { - 32 => { - while (buffer_offset < pixels.len) { - const line = pixels[buffer_offset .. buffer_offset + byte_stride]; - - if (try storage.read(path, line, file_offset) != byte_stride) { - return error.FormatUnsupported; - } - - 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 .{ - .format = .rgba8, - - .access = .{ - .static = .{ - .width = pixel_width, - .data = pixels, - }, - }, - }; -} diff --git a/src/ona/gfx/rendering.zig b/src/ona/gfx/rendering.zig new file mode 100644 index 0000000..bba5fa6 --- /dev/null +++ b/src/ona/gfx/rendering.zig @@ -0,0 +1,428 @@ +const Resources = @import("./Resources.zig"); + +const coral = @import("coral"); + +const ext = @import("../ext.zig"); + +const gfx = @import("../gfx.zig"); + +const spirv = @import("./spirv.zig"); + +const sokol = @import("sokol"); + +const std = @import("std"); + +const Frame = struct { + texture_batch_buffers: coral.stack.Sequential(sokol.gfx.Buffer), + quad_index_buffer: sokol.gfx.Buffer, + quad_vertex_buffer: sokol.gfx.Buffer, + drawn_count: usize = 0, + flushed_count: usize = 0, + current_source_texture: gfx.Texture = .default, + current_target_texture: gfx.Texture = .backbuffer, + current_effect: gfx.Effect = .default, + + const DrawTexture = extern struct { + 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), + }; + + const batches_per_buffer = 512; + + pub fn deinit(self: *Frame) void { + for (self.texture_batch_buffers.values) |buffer| { + sokol.gfx.destroyBuffer(buffer); + } + + self.texture_batch_buffers.deinit(); + + self.* = undefined; + } + + pub fn init() !Frame { + const Vertex = struct { + xy: @Vector(2, f32), + uv: @Vector(2, f32), + }; + + const quad_index_buffer = sokol.gfx.makeBuffer(.{ + .data = sokol.gfx.asRange(&[_]u16{0, 1, 2, 0, 2, 3}), + .type = .INDEXBUFFER, + }); + + const quad_vertex_buffer = sokol.gfx.makeBuffer(.{ + .data = sokol.gfx.asRange(&[_]Vertex{ + .{.xy = .{-0.5, -0.5}, .uv = .{0, 1}}, + .{.xy = .{0.5, -0.5}, .uv = .{1, 1}}, + .{.xy = .{0.5, 0.5}, .uv = .{1, 0}}, + .{.xy = .{-0.5, 0.5}, .uv = .{0, 0}}, + }), + + .type = .VERTEXBUFFER, + }); + + return .{ + .texture_batch_buffers = .{.allocator = coral.heap.allocator}, + .quad_index_buffer = quad_index_buffer, + .quad_vertex_buffer = quad_vertex_buffer, + }; + } + + pub fn draw_texture(self: *Frame, pools: *Resources, command: gfx.Commands.DrawTextureCommand) !void { + if (command.texture != self.current_source_texture) { + self.flush(pools); + } + + self.current_source_texture = command.texture; + + const has_filled_current_buffer = (self.drawn_count % batches_per_buffer) == 0; + const buffer_count = self.drawn_count / batches_per_buffer; + + if (has_filled_current_buffer and buffer_count == self.texture_batch_buffers.len()) { + const instance_buffer = sokol.gfx.makeBuffer(.{ + .size = @sizeOf(DrawTexture) * batches_per_buffer, + .usage = .STREAM, + }); + + errdefer sokol.gfx.destroyBuffer(instance_buffer); + + try self.texture_batch_buffers.push_grow(instance_buffer); + } + + _ = sokol.gfx.appendBuffer(self.texture_batch_buffers.get().?, sokol.gfx.asRange(&DrawTexture{ + .transform = command.transform, + })); + + self.drawn_count += 1; + } + + pub fn finish(self: *Frame, pools: *Resources) void { + self.flush(pools); + + self.drawn_count = 0; + self.flushed_count = 0; + self.current_source_texture = .default; + self.current_target_texture = .backbuffer; + self.current_effect = .default; + } + + pub fn flush(self: *Frame, pools: *Resources) void { + if (self.flushed_count == self.drawn_count) { + return; + } + + var bindings = sokol.gfx.Bindings{ + .index_buffer = self.quad_index_buffer, + }; + + bindings.vertex_buffers[vertex_indices.mesh] = self.quad_vertex_buffer; + + switch (pools.get_texture(self.current_source_texture).?.access) { + .render => |render| { + bindings.fs.images[0] = render.color_image; + bindings.fs.samplers[0] = default_sampler; + }, + + .static => |static| { + bindings.fs.images[0] = static.image; + bindings.fs.samplers[0] = default_sampler; + }, + + .empty => { + @panic("Cannot render empty textures"); + }, + } + + const effect = pools.get_effect(self.current_effect).?; + + sokol.gfx.applyPipeline(effect.pipeline); + + const texture = pools.get_texture(self.current_target_texture).?; + + sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&coral.lina.orthographic_projection(-1.0, 1.0, .{ + .left = 0, + .top = 0, + .right = @floatFromInt(texture.width), + .bottom = @floatFromInt(texture.height), + }))); + + if (effect.properties.len != 0) { + sokol.gfx.applyUniforms(.FS, 0, sokol.gfx.asRange(effect.properties)); + } + + while (true) { + const buffer_index = self.flushed_count / batches_per_buffer; + const buffer_offset = self.flushed_count % batches_per_buffer; + const instances_to_flush = @min(batches_per_buffer - buffer_offset, self.drawn_count - self.flushed_count); + + self.flushed_count += instances_to_flush; + bindings.vertex_buffers[vertex_indices.instance] = self.texture_batch_buffers.values[buffer_index]; + bindings.vertex_buffer_offsets[vertex_indices.instance] = @intCast(@sizeOf(DrawTexture) * buffer_offset); + + sokol.gfx.applyBindings(bindings); + sokol.gfx.draw(0, 6, @intCast(instances_to_flush)); + + if (self.flushed_count == self.drawn_count) { + break; + } + } + } + + pub fn set_effect(self: *Frame, pools: *Resources, command: gfx.Commands.SetEffectCommand) void { + if (command.effect != self.current_effect) { + self.flush(pools); + } + + self.current_effect = command.effect; + + if (pools.get_effect(self.current_effect)) |effect| { + @memcpy(effect.properties, command.properties); + } + } + + pub fn set_target(self: *Frame, pools: *Resources, command: gfx.Commands.SetTargetCommand) void { + sokol.gfx.endPass(); + + var pass = sokol.gfx.Pass{ + .action = .{ + .stencil = .{ + .load_action = .CLEAR, + }, + }, + }; + + if (command.clear_color) |color| { + pass.action.colors[0] = .{ + .load_action = .CLEAR, + .clear_value = @bitCast(color), + }; + } else { + pass.action.colors[0] = .{.load_action = .LOAD}; + } + + if (command.clear_depth) |depth| { + pass.action.depth = .{ + .load_action = .CLEAR, + .clear_value = depth, + }; + } else { + pass.action.depth = .{.load_action = .LOAD}; + } + + pass.attachments = switch (pools.get_texture(self.current_target_texture).?.access) { + .static => @panic("Cannot render to static textures"), + .empty => @panic("Cannot render to empty textures"), + .render => |render| render.attachments, + }; + + self.current_target_texture = command.texture; + + sokol.gfx.beginPass(pass); + } +}; + +var default_sampler: sokol.gfx.Sampler = undefined; + +const vertex_indices = .{ + .mesh = 0, + .instance = 1, +}; + +pub fn process_work(pending_work: *gfx.Assets.WorkQueue, 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); + }; + + defer ext.SDL_GL_DeleteContext(context); + + sokol.gfx.setup(.{ + .environment = .{ + .defaults = .{ + .color_format = .RGBA8, + .depth_format = .DEPTH_STENCIL, + .sample_count = 1, + }, + }, + + .logger = .{ + .func = sokol.log.func, + }, + }); + + defer { + sokol.gfx.shutdown(); + } + + var resources = try Resources.init(); + + defer { + resources.deinit(); + } + + var frame = try Frame.init(); + + defer { + frame.deinit(); + } + + default_sampler = sokol.gfx.makeSampler(.{}); + + while (true) { + switch (pending_work.dequeue()) { + .load_effect => |load| { + const effect = try resources.create_effect(load.desc); + + if (!load.loaded.resolve(effect)) { + std.debug.assert(resources.destroy_effect(effect)); + } + }, + + .load_texture => |load| { + const texture = try resources.create_texture(load.desc); + + if (!load.loaded.resolve(texture)) { + std.debug.assert(resources.destroy_texture(texture)); + } + }, + + .render_frame => |render_frame| { + const backbuffer = resources.get_texture(.backbuffer).?; + + if (backbuffer.width != render_frame.width or backbuffer.height != render_frame.height) { + backbuffer.deinit(); + + backbuffer.* = try Resources.Texture.init(.{ + .format = .rgba8, + + .access = .{ + .render = .{ + .width = render_frame.width, + .height = render_frame.height, + }, + }, + }); + } + + sokol.gfx.beginPass(pass: { + var pass = sokol.gfx.Pass{ + .action = .{ + .stencil = .{ + .load_action = .CLEAR, + }, + + .depth = .{ + .load_action = .CLEAR, + .clear_value = 0, + } + }, + }; + + pass.action.colors[0] = .{ + .load_action = .CLEAR, + .clear_value = @bitCast(render_frame.clear_color), + }; + + pass.attachments = resources.get_texture(.backbuffer).?.access.render.attachments; + + break: pass pass; + }); + + var has_command_params = render_frame.has_command_params; + + 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(&resources, draw_texture), + .set_effect => |set_effect| frame.set_effect(&resources, set_effect), + .set_target => |set_target| frame.set_target(&resources, set_target), + }; + } + + frame.flush(&resources); + + if (frame.current_target_texture != .backbuffer) { + frame.set_target(&resources, .{ + .texture = .backbuffer, + .clear_color = null, + .clear_depth = null, + .clear_stencil = null, + }); + } + } + + sokol.gfx.endPass(); + + sokol.gfx.beginPass(swapchain_pass: { + var pass = sokol.gfx.Pass{ + .swapchain = .{ + .width = render_frame.width, + .height = render_frame.height, + .sample_count = 1, + .color_format = .RGBA8, + .depth_format = .DEPTH_STENCIL, + .gl = .{.framebuffer = 0}, + }, + + .action = .{ + .stencil = .{.load_action = .CLEAR}, + .depth = .{.load_action = .CLEAR}, + }, + }; + + pass.action.colors[0] = .{.load_action = .CLEAR}; + + break: swapchain_pass pass; + }); + + try frame.draw_texture(&resources, .{ + .texture = .backbuffer, + + .transform = .{ + .origin = .{@as(f32, @floatFromInt(render_frame.width)) / 2, @as(f32, @floatFromInt(render_frame.height)) / 2}, + .xbasis = .{@floatFromInt(render_frame.width), 0}, + .ybasis = .{0, @floatFromInt(render_frame.height)}, + }, + }); + + frame.finish(&resources); + sokol.gfx.endPass(); + sokol.gfx.commit(); + ext.SDL_GL_SwapWindow(window); + render_frame.finished.set(); + }, + + .shutdown => { + break; + }, + + .unload_effect => |unload| { + if (!resources.destroy_effect(unload.handle)) { + @panic("Attempt to unload a non-existent effect"); + } + }, + + .unload_texture => |unload| { + if (!resources.destroy_texture(unload.handle)) { + @panic("Attempt to unload a non-existent texture"); + } + }, + } + } +} + +var work_thread: std.Thread = undefined; diff --git a/src/ona/gfx/sokol.zig b/src/ona/gfx/sokol.zig deleted file mode 100644 index 815fcf1..0000000 --- a/src/ona/gfx/sokol.zig +++ /dev/null @@ -1,908 +0,0 @@ -const coral = @import("coral"); - -const ext = @import("../ext.zig"); - -const gfx = @import("../gfx.zig"); - -const spirv = @import("./spirv.zig"); - -const sokol = @import("sokol"); - -const std = @import("std"); - -const Frame = struct { - swapchain: sokol.gfx.Swapchain, - drawn_count: usize = 0, - flushed_count: usize = 0, - current_source_texture: gfx.Texture = .default, - current_target_texture: gfx.Texture = .backbuffer, - current_effect: gfx.Effect = .default, - - const DrawTexture = extern struct { - 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: *Resources, command: gfx.Commands.DrawTextureCommand) !void { - if (command.texture != self.current_source_texture) { - self.flush(pools); - } - - self.current_source_texture = command.texture; - - const has_filled_current_buffer = (self.drawn_count % batches_per_buffer) == 0; - const buffer_count = self.drawn_count / batches_per_buffer; - - if (has_filled_current_buffer and buffer_count == texture_batch_buffers.len()) { - const instance_buffer = sokol.gfx.makeBuffer(.{ - .size = @sizeOf(DrawTexture) * batches_per_buffer, - .usage = .STREAM, - }); - - errdefer sokol.gfx.destroyBuffer(instance_buffer); - - try texture_batch_buffers.push_grow(instance_buffer); - } - - _ = sokol.gfx.appendBuffer(texture_batch_buffers.get().?, sokol.gfx.asRange(&DrawTexture{ - .transform = command.transform, - })); - - self.drawn_count += 1; - } - - pub fn flush(self: *Frame, pools: *Resources) void { - if (self.flushed_count == self.drawn_count) { - return; - } - - var bindings = sokol.gfx.Bindings{ - .index_buffer = quad_index_buffer, - }; - - bindings.vertex_buffers[vertex_indices.mesh] = quad_vertex_buffer; - - switch (pools.get_texture(self.current_source_texture).?.access) { - .render => |render| { - bindings.fs.images[0] = render.color_image; - bindings.fs.samplers[0] = default_sampler; - }, - - .static => |static| { - bindings.fs.images[0] = static.image; - bindings.fs.samplers[0] = default_sampler; - }, - - .empty => { - @panic("Cannot render empty textures"); - }, - } - - const effect = pools.get_effect(self.current_effect).?; - - sokol.gfx.applyPipeline(effect.pipeline); - - const texture = pools.get_texture(self.current_target_texture).?; - - sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&coral.lina.orthographic_projection(-1.0, 1.0, .{ - .left = 0, - .top = 0, - .right = @floatFromInt(texture.width), - .bottom = @floatFromInt(texture.height), - }))); - - if (effect.properties.len != 0) { - sokol.gfx.applyUniforms(.FS, 0, sokol.gfx.asRange(effect.properties)); - } - - while (true) { - const buffer_index = self.flushed_count / batches_per_buffer; - const buffer_offset = self.flushed_count % batches_per_buffer; - const instances_to_flush = @min(batches_per_buffer - buffer_offset, self.drawn_count - self.flushed_count); - - self.flushed_count += instances_to_flush; - bindings.vertex_buffers[vertex_indices.instance] = texture_batch_buffers.values[buffer_index]; - bindings.vertex_buffer_offsets[vertex_indices.instance] = @intCast(@sizeOf(DrawTexture) * buffer_offset); - - sokol.gfx.applyBindings(bindings); - sokol.gfx.draw(0, 6, @intCast(instances_to_flush)); - - if (self.flushed_count == self.drawn_count) { - break; - } - } - } - - pub fn set_effect(self: *Frame, pools: *Resources, command: gfx.Commands.SetEffectCommand) void { - if (command.effect != self.current_effect) { - self.flush(pools); - } - - self.current_effect = command.effect; - - if (pools.get_effect(self.current_effect)) |effect| { - @memcpy(effect.properties, command.properties); - } - } - - pub fn set_target(self: *Frame, pools: *Resources, command: gfx.Commands.SetTargetCommand) void { - sokol.gfx.endPass(); - - var pass = sokol.gfx.Pass{ - .action = .{ - .stencil = .{ - .load_action = .CLEAR, - }, - }, - }; - - if (command.clear_color) |color| { - pass.action.colors[0] = .{ - .load_action = .CLEAR, - .clear_value = @bitCast(color), - }; - } else { - pass.action.colors[0] = .{.load_action = .LOAD}; - } - - if (command.clear_depth) |depth| { - pass.action.depth = .{ - .load_action = .CLEAR, - .clear_value = depth, - }; - } else { - pass.action.depth = .{.load_action = .LOAD}; - } - - pass.attachments = switch (pools.get_texture(self.current_target_texture).?.access) { - .static => @panic("Cannot render to static textures"), - .empty => @panic("Cannot render to empty textures"), - .render => |render| render.attachments, - }; - - self.current_target_texture = command.texture; - - sokol.gfx.beginPass(pass); - } - - const storage_bindings = .{ - .engine = 0, - .material = 1, - }; - - var texture_batch_buffers = coral.stack.Sequential(sokol.gfx.Buffer){.allocator = coral.heap.allocator}; - - const batches_per_buffer = 512; -}; - -const Resources = struct { - effects: EffectPool, - textures: TexturePool, - - 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: 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: *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: *Resources) void { - var textures = self.textures.values(); - - while (textures.next()) |texture| { - texture.deinit(); - } - - self.textures.deinit(); - } - - pub fn destroy_effect(self: *Resources, handle: gfx.Effect) bool { - switch (handle) { - .default => {}, - - else => { - var effect = self.effects.remove(@intFromEnum(handle)) orelse { - return false; - }; - - effect.deinit(); - }, - } - - return true; - } - - pub fn destroy_texture(self: *Resources, handle: gfx.Texture) bool { - switch (handle) { - .default => {}, - - else => { - var texture = self.textures.remove(@intFromEnum(handle)) orelse { - return false; - }; - - texture.deinit(); - }, - } - - return true; - } - - pub fn get_effect(self: *Resources, handle: gfx.Effect) ?*Effect { - return self.effects.get(@intFromEnum(handle)); - } - - pub fn get_texture(self: *Resources, handle: gfx.Texture) ?*Texture { - return self.textures.get(@intFromEnum(handle)); - } - - pub fn init(allocator: std.mem.Allocator) !Resources { - var pools = Resources{ - .effects = EffectPool.init(allocator), - .textures = TexturePool.init(allocator), - }; - - errdefer { - pools.deinit(); - } - - const assert = struct { - fn is_handle(expected: anytype, actual: @TypeOf(expected)) void { - std.debug.assert(actual == expected); - } - }; - - 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(gfx.Texture.default, try pools.create_texture(.{ - .format = .rgba8, - - .access = .{ - .static = .{ - .data = std.mem.asBytes(&[_]u32{ - 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, - 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, - 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, - 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, - 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, - 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, - 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, - 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, - }), - - .width = 8, - }, - }, - })); - - assert.is_handle(gfx.Texture.backbuffer, try pools.create_texture(.{ - .format = .rgba8, - - .access = .{ - .render = .{ - .width = 0, - .height = 0, - }, - } - })); - - return pools; - } -}; - -var default_sampler: sokol.gfx.Sampler = undefined; - -var quad_index_buffer: sokol.gfx.Buffer = undefined; - -var quad_vertex_buffer: sokol.gfx.Buffer = undefined; - -const vertex_indices = .{ - .mesh = 0, - .instance = 1, -}; - -pub fn run(pending_work: *gfx.Assets.WorkQueue, 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); - }; - - defer ext.SDL_GL_DeleteContext(context); - - sokol.gfx.setup(.{ - .environment = .{ - .defaults = .{ - .color_format = .RGBA8, - .depth_format = .DEPTH_STENCIL, - .sample_count = 1, - }, - }, - - .logger = .{ - .func = sokol.log.func, - }, - }); - - defer { - sokol.gfx.shutdown(); - } - - var pools = try Resources.init(coral.heap.allocator); - - defer { - pools.deinit(); - } - - const Vertex = struct { - xy: @Vector(2, f32), - uv: @Vector(2, f32), - }; - - const quad_indices = [_]u16{0, 1, 2, 0, 2, 3}; - - const quad_vertices = [_]Vertex{ - .{.xy = .{-0.5, -0.5}, .uv = .{0, 1}}, - .{.xy = .{0.5, -0.5}, .uv = .{1, 1}}, - .{.xy = .{0.5, 0.5}, .uv = .{1, 0}}, - .{.xy = .{-0.5, 0.5}, .uv = .{0, 0}}, - }; - - quad_index_buffer = sokol.gfx.makeBuffer(.{ - .data = sokol.gfx.asRange(&quad_indices), - .type = .INDEXBUFFER, - }); - - quad_vertex_buffer = sokol.gfx.makeBuffer(.{ - .data = sokol.gfx.asRange(&quad_vertices), - .type = .VERTEXBUFFER, - }); - - default_sampler = sokol.gfx.makeSampler(.{}); - - while (true) { - switch (pending_work.dequeue()) { - .load_effect => |load| { - const effect = try pools.create_effect(load.desc); - - if (!load.loaded.resolve(effect)) { - std.debug.assert(pools.destroy_effect(effect)); - } - }, - - .load_texture => |load| { - const texture = try pools.create_texture(load.desc); - - if (!load.loaded.resolve(texture)) { - std.debug.assert(pools.destroy_texture(texture)); - } - }, - - .render_frame => |render_frame| { - const backbuffer = pools.get_texture(.backbuffer).?; - - if (backbuffer.width != render_frame.width or backbuffer.height != render_frame.height) { - backbuffer.deinit(); - - backbuffer.* = try Resources.Texture.init(.{ - .format = .rgba8, - - .access = .{ - .render = .{ - .width = render_frame.width, - .height = render_frame.height, - }, - }, - }); - } - - var frame = Frame{ - .swapchain = .{ - .width = render_frame.width, - .height = render_frame.height, - .sample_count = 1, - .color_format = .RGBA8, - .depth_format = .DEPTH_STENCIL, - .gl = .{.framebuffer = 0}, - } - }; - - sokol.gfx.beginPass(pass: { - var pass = sokol.gfx.Pass{ - .action = .{ - .stencil = .{ - .load_action = .CLEAR, - }, - - .depth = .{ - .load_action = .CLEAR, - .clear_value = 0, - } - }, - }; - - pass.action.colors[0] = .{ - .load_action = .CLEAR, - .clear_value = @bitCast(render_frame.clear_color), - }; - - pass.attachments = pools.get_texture(.backbuffer).?.access.render.attachments; - - break: pass pass; - }); - - var has_command_params = render_frame.has_command_params; - - 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); - - if (frame.current_target_texture != .backbuffer) { - frame.set_target(&pools, .{ - .texture = .backbuffer, - .clear_color = null, - .clear_depth = null, - .clear_stencil = null, - }); - } - } - - sokol.gfx.endPass(); - - sokol.gfx.beginPass(swapchain_pass: { - var pass = sokol.gfx.Pass{ - .swapchain = frame.swapchain, - - .action = .{ - .stencil = .{.load_action = .CLEAR}, - .depth = .{.load_action = .CLEAR}, - }, - }; - - pass.action.colors[0] = .{.load_action = .CLEAR}; - - break: swapchain_pass pass; - }); - - try frame.draw_texture(&pools, .{ - .texture = .backbuffer, - - .transform = .{ - .origin = .{@as(f32, @floatFromInt(render_frame.width)) / 2, @as(f32, @floatFromInt(render_frame.height)) / 2}, - .xbasis = .{@floatFromInt(render_frame.width), 0}, - .ybasis = .{0, @floatFromInt(render_frame.height)}, - }, - }); - - frame.flush(&pools); - sokol.gfx.endPass(); - sokol.gfx.commit(); - ext.SDL_GL_SwapWindow(window); - render_frame.finished.set(); - }, - - .shutdown => { - break; - }, - - .unload_effect => |unload| { - if (!pools.destroy_effect(unload.handle)) { - @panic("Attempt to unload a non-existent effect"); - } - }, - - .unload_texture => |unload| { - if (!pools.destroy_texture(unload.handle)) { - @panic("Attempt to unload a non-existent texture"); - } - }, - } - } -} - -var work_thread: std.Thread = undefined; diff --git a/src/ona/gfx/spirv.zig b/src/ona/gfx/spirv.zig index def2040..6972e8a 100644 --- a/src/ona/gfx/spirv.zig +++ b/src/ona/gfx/spirv.zig @@ -4,8 +4,6 @@ const ext = @cImport({ @cInclude("spirv-cross/spirv_cross_c.h"); }); -const sokol = @import("sokol"); - const std = @import("std"); pub const Error = std.mem.Allocator.Error || error { diff --git a/src/ona/gfx/textures.zig b/src/ona/gfx/textures.zig deleted file mode 100644 index 206a49f..0000000 --- a/src/ona/gfx/textures.zig +++ /dev/null @@ -1,38 +0,0 @@ -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, - _, -};