From a7afe41a700da46c19418de24b17d003c91085fd Mon Sep 17 00:00:00 2001 From: duck Date: Fri, 23 May 2025 18:57:14 +0500 Subject: [PATCH] Meshes, mesh drawing and debug scene --- src/debug_scene.zig | 91 +++++++++++++++++ src/game.zig | 51 +++++++--- src/graph.zig | 5 +- src/graph/system.zig | 2 + src/graphics.zig | 237 ++++++++++++++++++++++++++++--------------- 5 files changed, 287 insertions(+), 99 deletions(-) create mode 100644 src/debug_scene.zig diff --git a/src/debug_scene.zig b/src/debug_scene.zig new file mode 100644 index 0000000..374a903 --- /dev/null +++ b/src/debug_scene.zig @@ -0,0 +1,91 @@ +const std = @import("std"); +const sdl = @import("sdl"); +const Controller = @import("graph/controller.zig"); +const Graphics = @import("graphics.zig"); +const Game = @import("game.zig"); + +const CUBE_MESH_DATA = [_]f32{ + -1, 1, -1, + 1, 1, -1, + -1, -1, -1, + 1, -1, -1, + -1, -1, -1, + 1, 1, -1, + 1, 1, -1, + 1, 1, 1, + 1, -1, -1, + 1, -1, 1, + 1, -1, -1, + 1, 1, 1, + 1, 1, 1, + -1, 1, 1, + 1, -1, 1, + -1, -1, 1, + 1, -1, 1, + -1, 1, 1, + -1, 1, 1, + -1, 1, -1, + -1, -1, 1, + -1, -1, -1, + -1, -1, 1, + -1, 1, -1, + -1, 1, 1, + 1, 1, 1, + -1, 1, -1, + 1, 1, -1, + -1, 1, -1, + 1, 1, 1, + -1, -1, -1, + 1, -1, -1, + -1, -1, 1, + 1, -1, 1, + -1, -1, 1, + 1, -1, -1, +}; + +const OFFSETS = [_]@Vector(3, f32){ + .{ -3, 3, 0 }, + .{ 0, 3, 0 }, + .{ 3, 3, 0 }, + .{ -3, 0, 0 }, + .{ 0, 0, 0 }, + .{ 3, 0, 0 }, + .{ -3, -3, 0 }, + .{ 0, -3, 0 }, + .{ 3, -3, 0 }, +}; + +pub const Cube = struct { + mesh: Graphics.Mesh, + transform: Graphics.Transform, +}; + +pub fn init(controller: *Controller, graphics: *Graphics) !void { + controller.addResource(Cube{ + .mesh = try graphics.loadMesh(@ptrCast(&CUBE_MESH_DATA)), + .transform = Graphics.Transform{}, + }); +} + +pub fn deinit() void {} + +pub fn update(cube: *Cube, mouse: *Game.Mouse, graphics: *Graphics) void { + if (@abs(mouse.dx) < 0.01 and @abs(mouse.dy) < 0.01) return; + + const delta, const length = Graphics.Transform.extractNormal(.{ -mouse.dy, -mouse.dx, 0.0 }); + const rot = Graphics.Transform.rotationByAxis( + delta, + length * std.math.pi / @as(f32, @floatFromInt(graphics.window_size[1])) * 2.0, + ); + cube.transform.rotate(rot); +} + +pub fn draw(cube: *Cube, graphics: *Graphics) !void { + for (OFFSETS) |offset| { + try graphics.drawMesh(cube.mesh, Graphics.Transform{ + .position = cube.transform.position + offset, + .rotation = cube.transform.rotation, + .scale = cube.transform.scale, + }); + } +} diff --git a/src/game.zig b/src/game.zig index 2b8e3e6..ccb5999 100644 --- a/src/game.zig +++ b/src/game.zig @@ -1,6 +1,8 @@ const std = @import("std"); const builtin = @import("builtin"); const sdl = @import("sdl"); + +const debug_scene = @import("debug_scene.zig"); const Graph = @import("graph.zig"); const Graphics = @import("graphics.zig"); @@ -8,6 +10,7 @@ const Graphics = @import("graphics.zig"); // - Do something about deallocating `Resource`s when `Graph` fails const RunInfo = struct { running: bool }; +pub const Mouse = struct { x: f32, y: f32, dx: f32, dy: f32 }; alloc: std.mem.Allocator, graph: Graph, @@ -21,8 +24,18 @@ pub fn init(alloc: std.mem.Allocator) GameError!Self { var controller = try graph.getController(); controller.addResource(graphics); + controller.addResource(Mouse{ + .x = 0.0, + .y = 0.0, + .dx = 0.0, + .dy = 0.0, + }); + controller.queue(debug_scene.init); try graph.freeController(controller); + defer graph.reset(); + try graph.runAllSystems(); + return Self{ .alloc = alloc, .graph = graph, @@ -44,7 +57,9 @@ pub fn run(self: *Self) GameError!void { var controller = try self.graph.getController(); controller.queue(.{ .events = processEvents, - .draw = draw, + .update = debug_scene.update, + .begin_draw = beginDraw, + .end_draw = endDraw, .ordered = true, }); try self.graph.freeController(controller); @@ -54,12 +69,13 @@ pub fn run(self: *Self) GameError!void { } } -fn draw(graphics: *Graphics) GameError!void { - try graphics.beginDraw(); - { - errdefer graphics.endDraw() catch {}; - try graphics.drawDebug(); +fn beginDraw(graphics: *Graphics, controller: *Graph.Controller) GameError!void { + if (try graphics.beginDraw()) { + controller.queue(debug_scene.draw); } +} + +fn endDraw(graphics: *Graphics) GameError!void { try graphics.endDraw(); } @@ -68,7 +84,10 @@ fn clean(graphics: *Graphics) !void { // TODO: Also remove the resource } -fn processEvents(graphics: *Graphics, run_info: *RunInfo) GameError!void { +fn processEvents(graphics: *Graphics, run_info: *RunInfo, mouse: *Mouse) GameError!void { + mouse.dx = 0.0; + mouse.dy = 0.0; + sdl.PumpEvents(); while (true) { var buffer: [16]sdl.Event = undefined; @@ -85,14 +104,10 @@ fn processEvents(graphics: *Graphics, run_info: *RunInfo) GameError!void { }, sdl.EVENT_MOUSE_MOTION => { if (event.motion.windowID != sdl.GetWindowID(graphics.window)) continue; - if (@abs(event.motion.xrel) < 0.01 and @abs(event.motion.yrel) < 0.01) continue; - const Transform = @import("graphics/transform.zig"); - const delta, const length = Transform.extractNormal(.{ -event.motion.yrel, -event.motion.xrel, 0.0 }); - const rot = Transform.rotationByAxis( - delta, - length * std.math.pi / @as(f32, @floatFromInt(graphics.window_size[1])) * 2.0, - ); - graphics.mesh_transform.rotate(rot); + mouse.x = event.motion.x; + mouse.y = event.motion.y; + mouse.dx += event.motion.xrel; + mouse.dy += event.motion.yrel; }, else => {}, } @@ -106,7 +121,11 @@ fn processEvents(graphics: *Graphics, run_info: *RunInfo) GameError!void { pub fn deinit(self: *Self) void { var controller = self.graph.getController() catch unreachable; - controller.queue(clean); + controller.queue(.{ + .deinit = debug_scene.deinit, + .clean = clean, + .ordered = true, + }); self.graph.freeController(controller) catch unreachable; self.graph.runAllSystems() catch unreachable; diff --git a/src/graph.zig b/src/graph.zig index 438208e..1ea4641 100644 --- a/src/graph.zig +++ b/src/graph.zig @@ -149,7 +149,10 @@ pub fn runAllSystems(self: *Self) GraphError!void { const next_system = self.system_queue.pop().?; defer next_system.deinit(self.alloc); - try self.runSystem(next_system); + self.runSystem(next_system) catch |err| { + std.debug.print("System run error: {} while running {s}\n", .{ err, next_system.label }); + return err; + }; } } diff --git a/src/graph/system.zig b/src/graph/system.zig index fad1829..9233e3d 100644 --- a/src/graph/system.zig +++ b/src/graph/system.zig @@ -6,6 +6,7 @@ function_runner: *const fn ([]const *anyopaque) void, requested_types: []const Request, requires_dud: ?Dud.Id, submit_dud: ?Dud.Id, +label: []const u8, pub const Dud = struct { pub const Id = usize; @@ -37,6 +38,7 @@ pub fn fromFunction(comptime function: anytype, alloc: std.mem.Allocator) !Self .function_runner = utils.generateRunner(function), .requires_dud = null, .submit_dud = null, + .label = @typeName(@TypeOf(function)), }; } diff --git a/src/graphics.zig b/src/graphics.zig index f26ed40..47a4fe5 100644 --- a/src/graphics.zig +++ b/src/graphics.zig @@ -1,20 +1,33 @@ const std = @import("std"); const sdl = @import("sdl"); const presets = @import("graphics/presets.zig"); -const Transform = @import("graphics/transform.zig"); -const Camera = @import("graphics/camera.zig"); const GameError = @import("game.zig").GameError; +pub const Transform = @import("graphics/transform.zig"); +pub const Camera = @import("graphics/camera.zig"); + +pub const Mesh = struct { + vertex_start: usize, + vertex_count: usize, +}; + window: *sdl.Window, renderer: *sdl.Renderer, device: *sdl.GPUDevice, /// Only available while drawing command_buffer: ?*sdl.GPUCommandBuffer, +render_pass: ?*sdl.GPURenderPass, shader_vert: *sdl.GPUShader, shader_frag: *sdl.GPUShader, vertex_buffer: *sdl.GPUBuffer, +vertex_buffer_capacity: usize, +vertex_buffer_used: usize, + +transfer_buffer: *sdl.GPUTransferBuffer, +transfer_buffer_capacity: usize, + depth_texture: *sdl.GPUTexture, msaa_resolve: *sdl.GPUTexture, pipeline: *sdl.GPUGraphicsPipeline, @@ -22,50 +35,13 @@ pipeline: *sdl.GPUGraphicsPipeline, window_size: [2]u32, camera: Camera, -mesh_transform: Transform, to_resize: ?[2]u32 = null, -const MESH_BYTES = MESH.len * 4; -const MESH_VERTS = @divExact(MESH.len, 3); -const MESH = [_]f32{ - -1, 1, -1, - 1, 1, -1, - -1, -1, -1, - 1, -1, -1, - -1, -1, -1, - 1, 1, -1, - 1, 1, -1, - 1, 1, 1, - 1, -1, -1, - 1, -1, 1, - 1, -1, -1, - 1, 1, 1, - 1, 1, 1, - -1, 1, 1, - 1, -1, 1, - -1, -1, 1, - 1, -1, 1, - -1, 1, 1, - -1, 1, 1, - -1, 1, -1, - -1, -1, 1, - -1, -1, -1, - -1, -1, 1, - -1, 1, -1, - -1, 1, 1, - 1, 1, 1, - -1, 1, -1, - 1, 1, -1, - -1, 1, -1, - 1, 1, 1, - -1, -1, -1, - 1, -1, -1, - -1, -1, 1, - 1, -1, 1, - -1, -1, 1, - 1, -1, -1, -}; +const VERTEX_BUFFER_DEFAULT_CAPACITY = 1024; +const VERTEX_BUFFER_GROWTH_MULTIPLIER = 2; +const TRANSFER_BUFFER_DEFAULT_CAPACITY = 1024; +const BYTES_PER_VERTEX = 3 * 4; const Self = @This(); pub fn create() GameError!Self { @@ -126,35 +102,15 @@ pub fn create() GameError!Self { const vertex_buffer = sdl.CreateGPUBuffer(device, &.{ .usage = sdl.GPU_BUFFERUSAGE_VERTEX, - // Vertices * 3 Coordinates * 4 Bytes - .size = MESH_BYTES, + .size = VERTEX_BUFFER_DEFAULT_CAPACITY, }) orelse return GameError.SdlError; errdefer sdl.ReleaseGPUBuffer(device, vertex_buffer); const transfer_buffer = sdl.CreateGPUTransferBuffer(device, &.{ - .size = MESH_BYTES, - .usage = sdl.GPU_TRANSFERBUFFERUSAGE_UPLOAD, + .size = TRANSFER_BUFFER_DEFAULT_CAPACITY, + .usage = sdl.GPU_TRANSFERBUFFERUSAGE_UPLOAD | sdl.GPU_TRANSFERBUFFERUSAGE_DOWNLOAD, }) orelse return GameError.SdlError; - defer sdl.ReleaseGPUTransferBuffer(device, transfer_buffer); - - { // Filling up transfer buffer - const mapped_buffer: [*c]f32 = @alignCast(@ptrCast(sdl.MapGPUTransferBuffer(device, transfer_buffer, false) orelse return GameError.SdlError)); - defer sdl.UnmapGPUTransferBuffer(device, transfer_buffer); - std.mem.copyForwards(f32, mapped_buffer[0..MESH.len], &MESH); - } - - { // Copying data over from transfer buffer to vertex buffer - const command_buffer = sdl.AcquireGPUCommandBuffer(device) orelse return GameError.SdlError; - const copy_pass = sdl.BeginGPUCopyPass(command_buffer) orelse return GameError.SdlError; - - sdl.UploadToGPUBuffer(copy_pass, &.{ .transfer_buffer = transfer_buffer }, &.{ - .size = MESH_BYTES, - .buffer = vertex_buffer, - }, false); - - sdl.EndGPUCopyPass(copy_pass); - if (!sdl.SubmitGPUCommandBuffer(command_buffer)) return GameError.SdlError; - } + errdefer sdl.ReleaseGPUTransferBuffer(device, transfer_buffer); const target_format = sdl.GetGPUSwapchainTextureFormat(device, window); if (target_format == sdl.GPU_TEXTUREFORMAT_INVALID) return GameError.SdlError; @@ -211,24 +167,33 @@ pub fn create() GameError!Self { .window = window.?, .renderer = renderer.?, .device = device, + .command_buffer = null, + .render_pass = null, + .shader_vert = shader_vert, .shader_frag = shader_frag, + .vertex_buffer = vertex_buffer, + .vertex_buffer_capacity = VERTEX_BUFFER_DEFAULT_CAPACITY, + .vertex_buffer_used = 0, + + .transfer_buffer = transfer_buffer, + .transfer_buffer_capacity = TRANSFER_BUFFER_DEFAULT_CAPACITY, + .depth_texture = depth_texture, .msaa_resolve = msaa_resolve, .pipeline = pipeline, + .window_size = .{ 1600, 900 }, + .camera = Camera{ .transform = Transform{ - .position = .{ 0.0, 0.0, -6.0 }, + .position = .{ 0.0, 0.0, -4.0 }, }, .near = 1.0, .far = 1024.0, - .lens = .{ 0.5 * 16.0 / 9.0, 0.5 }, - }, - .mesh_transform = Transform{ - .scale = .{ 1.0, 1.5, 2.0 }, + .lens = .{ 1.5 * 16.0 / 9.0, 1.5 }, }, }; } @@ -242,6 +207,7 @@ pub fn destroy(self: *Self) void { sdl.ReleaseGPUTexture(self.device, self.msaa_resolve); sdl.ReleaseGPUTexture(self.device, self.depth_texture); sdl.ReleaseGPUBuffer(self.device, self.vertex_buffer); + sdl.ReleaseGPUTransferBuffer(self.device, self.transfer_buffer); sdl.ReleaseGPUShader(self.device, self.shader_vert); sdl.ReleaseGPUShader(self.device, self.shader_frag); @@ -253,7 +219,107 @@ pub fn destroy(self: *Self) void { sdl.DestroyGPUDevice(self.device); } -pub fn beginDraw(self: *Self) GameError!void { +pub fn loadMesh(self: *Self, mesh_bytes: []const u8) GameError!Mesh { + std.debug.assert(mesh_bytes.len < self.transfer_buffer_capacity); + + var size_mult: usize = 1; + while (self.vertex_buffer_used + mesh_bytes.len > self.vertex_buffer_capacity) { + size_mult *= VERTEX_BUFFER_GROWTH_MULTIPLIER; + } + if (size_mult > 1) { + try self.growVertexBuffer(self.vertex_buffer_capacity * size_mult); + } + + const map = sdl.MapGPUTransferBuffer(self.device, self.transfer_buffer, false) orelse return GameError.SdlError; + @memcpy(@as([*]u8, @ptrCast(map)), mesh_bytes); + sdl.UnmapGPUTransferBuffer(self.device, self.transfer_buffer); + + const command_buffer = sdl.AcquireGPUCommandBuffer(self.device) orelse return GameError.SdlError; + const fence = blk: { + errdefer _ = sdl.CancelGPUCommandBuffer(command_buffer); + + const copy_pass = sdl.BeginGPUCopyPass(command_buffer) orelse return GameError.SdlError; + sdl.UploadToGPUBuffer(copy_pass, &.{ + .transfer_buffer = self.transfer_buffer, + .offset = 0, + }, &.{ + .buffer = self.vertex_buffer, + .offset = @intCast(self.vertex_buffer_used), + .size = @intCast(mesh_bytes.len), + }, false); + sdl.EndGPUCopyPass(copy_pass); + + break :blk sdl.SubmitGPUCommandBufferAndAcquireFence(command_buffer) orelse return GameError.SdlError; + }; + defer sdl.ReleaseGPUFence(self.device, fence); + + if (!sdl.WaitForGPUFences(self.device, true, &fence, 1)) return GameError.SdlError; + + const vertex_start = self.vertex_buffer_used; + self.vertex_buffer_used += mesh_bytes.len; + + return Mesh{ + .vertex_start = vertex_start / BYTES_PER_VERTEX, + .vertex_count = mesh_bytes.len / BYTES_PER_VERTEX, + }; +} + +pub fn unloadMesh(self: *Self, mesh: Mesh) void { + // TODO: free some memory + _ = self; + _ = &mesh; +} + +fn growVertexBuffer(self: *Self, new_size: usize) GameError!void { + const new_buffer = sdl.CreateGPUBuffer(self.device, &.{ + .size = @intCast(new_size), + .usage = sdl.GPU_BUFFERUSAGE_VERTEX, + }) orelse return GameError.SdlError; + errdefer sdl.ReleaseGPUBuffer(self.device, new_buffer); + + const command_buffer = sdl.AcquireGPUCommandBuffer(self.device) orelse return GameError.SdlError; + + const fence = blk: { + errdefer _ = sdl.CancelGPUCommandBuffer(command_buffer); + + const copy_pass = sdl.BeginGPUCopyPass(command_buffer); + var copied: usize = 0; + while (copied < self.vertex_buffer_used) { + const to_transer = @min(self.vertex_buffer_used - copied, self.transfer_buffer_capacity); + sdl.DownloadFromGPUBuffer(copy_pass, &.{ + .buffer = self.vertex_buffer, + .offset = @intCast(copied), + .size = @intCast(to_transer), + }, &.{ + .transfer_buffer = self.transfer_buffer, + .offset = 0, + }); + sdl.UploadToGPUBuffer(copy_pass, &.{ + .transfer_buffer = self.transfer_buffer, + .offset = 0, + }, &.{ + .buffer = new_buffer, + .offset = @intCast(copied), + .size = @intCast(to_transer), + }, false); + copied += to_transer; + } + sdl.EndGPUCopyPass(copy_pass); + + break :blk sdl.SubmitGPUCommandBufferAndAcquireFence(command_buffer) orelse return GameError.SdlError; + }; + defer sdl.ReleaseGPUFence(self.device, fence); + + if (!sdl.WaitForGPUFences(self.device, true, &fence, 1)) return GameError.SdlError; + + sdl.ReleaseGPUBuffer(self.device, self.vertex_buffer); + self.vertex_buffer = new_buffer; + self.vertex_buffer_capacity = new_size; +} + +/// If window is minimized returns `false`, `render_pass` remains null +/// Otherwise `command_buffer` and `render_pass` are both set +pub fn beginDraw(self: *Self) GameError!bool { self.command_buffer = sdl.AcquireGPUCommandBuffer(self.device) orelse return GameError.SdlError; if (self.to_resize) |new_size| { try self.resetTextures(new_size[0], new_size[1]); @@ -261,17 +327,15 @@ pub fn beginDraw(self: *Self) GameError!void { self.window_size = new_size; self.to_resize = null; } -} -pub fn drawDebug(self: *Self) GameError!void { var render_target: ?*sdl.GPUTexture = null; var width: u32 = 0; var height: u32 = 0; if (!sdl.WaitAndAcquireGPUSwapchainTexture(self.command_buffer, self.window, &render_target, &width, &height)) return GameError.SdlError; // Hidden - if (render_target == null) return; + if (render_target == null) return false; - const render_pass = sdl.BeginGPURenderPass(self.command_buffer, &.{ + self.render_pass = sdl.BeginGPURenderPass(self.command_buffer, &.{ .clear_color = .{ .r = 0.0, .g = 0.0, .b = 0.0, .a = 1.0 }, .cycle = false, .load_op = sdl.GPU_LOADOP_CLEAR, @@ -289,17 +353,26 @@ pub fn drawDebug(self: *Self) GameError!void { .texture = self.depth_texture, }) orelse return GameError.SdlError; - sdl.BindGPUGraphicsPipeline(render_pass, self.pipeline); - sdl.BindGPUVertexBuffers(render_pass, 0, &.{ .offset = 0, .buffer = self.vertex_buffer }, 1); + sdl.BindGPUGraphicsPipeline(self.render_pass, self.pipeline); + sdl.BindGPUVertexBuffers(self.render_pass, 0, &.{ .offset = 0, .buffer = self.vertex_buffer }, 1); sdl.PushGPUVertexUniformData(self.command_buffer, 0, &self.camera.matrix(), 16 * 4); - sdl.PushGPUVertexUniformData(self.command_buffer, 1, &self.mesh_transform.matrix(), 16 * 4); - sdl.DrawGPUPrimitives(render_pass, MESH_VERTS, 1, 0, 0); - sdl.EndGPURenderPass(render_pass); + return true; +} + +pub fn drawMesh(self: *Self, mesh: Mesh, transform: Transform) GameError!void { + if (self.render_pass == null) return; + + sdl.PushGPUVertexUniformData(self.command_buffer, 1, &transform.matrix(), 16 * 4); + sdl.DrawGPUPrimitives(self.render_pass, @intCast(mesh.vertex_count), 1, @intCast(mesh.vertex_start), 0); } pub fn endDraw(self: *Self) GameError!void { defer self.command_buffer = null; + defer self.render_pass = null; + if (self.render_pass) |render_pass| { + sdl.EndGPURenderPass(render_pass); + } if (!sdl.SubmitGPUCommandBuffer(self.command_buffer)) return GameError.SdlError; }