render-zig

A 3D rendering engine written in Zig
git clone git://git.christianermann.dev/render-zig
Log | Files | Refs

commit eef47ff65e16f23f208037a3d3e77e0b550b93ee
parent 52a2e2dbe5582a34abbde0eeacd445f0054c2f38
Author: Christian Ermann <christianermann@gmail.com>
Date:   Tue, 30 Apr 2024 18:37:18 -0400

Prototype mesh buffers

Diffstat:
Msrc/load_obj.zig | 35++++++++++++++++++++++++++++-------
Msrc/main.zig | 180+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
2 files changed, 172 insertions(+), 43 deletions(-)

diff --git a/src/load_obj.zig b/src/load_obj.zig @@ -1,6 +1,8 @@ const std = @import("std"); +const gpu = @import("mach_gpu"); const Mesh = @import("main.zig").Mesh; +const ArenaBuffer = @import("main.zig").ArenaBuffer; const f32x2 = @Vector(2, f32); const f32x3 = @Vector(3, f32); @@ -97,7 +99,18 @@ fn parseVertex(line: []const u8) !Vertex { return vertex; } -pub fn loadFile(allocator: std.mem.Allocator, path: []const u8) !Mesh { +pub const LoadFileOptions = struct { + allocator: std.mem.Allocator, + vtx_buffer: *ArenaBuffer, + idx_buffer: *ArenaBuffer, + path: []const u8, + queue: *gpu.Queue, +}; + +pub fn loadFile(options: LoadFileOptions) !Mesh { + const allocator = options.allocator; + const path = options.path; + var file = try std.fs.cwd().openFile(path, .{ .mode = .read_only }); defer file.close(); @@ -172,10 +185,18 @@ pub fn loadFile(allocator: std.mem.Allocator, path: []const u8) !Mesh { std.log.info("NUM NORMALS: {}", .{normals_indexed.items.len}); std.log.info("NUM TEXCOORDS: {}", .{tex_coords_indexed.items.len}); - return Mesh{ - .indices = try indices.toOwnedSlice(), - .positions = try positions_indexed.toOwnedSlice(), - .normals = try normals_indexed.toOwnedSlice(), - .tex_coords = try tex_coords_indexed.toOwnedSlice(), - }; + const pslice = try positions_indexed.toOwnedSlice(); + const nslice = try normals_indexed.toOwnedSlice(); + const tslice = try tex_coords_indexed.toOwnedSlice(); + const islice = try indices.toOwnedSlice(); + + return Mesh.init(.{ + .queue = options.queue, + .vtx_buffer = options.vtx_buffer, + .idx_buffer = options.idx_buffer, + .positions = pslice, + .normals = nslice, + .tex_coords = tslice, + .indices = islice, + }); } diff --git a/src/main.zig b/src/main.zig @@ -332,45 +332,139 @@ const DefaultRenderPipeline = struct { const f32x3 = @Vector(3, f32); const f32x4 = @Vector(4, f32); -pub const Mesh = struct { - indices: []u32, - positions: []f32x4, - normals: ?[]f32x3, - tex_coords: ?[]f32x3, -}; - -const UnlitRenderPipeline = struct { - gpu_pipeline: *gpu.RenderPipeline, - vtx_buffer: *gpu.Buffer, - idx_buffer: *gpu.Buffer, - mesh: *const Mesh, - - pub fn init(app: *App, mesh: *const Mesh) UnlitRenderPipeline { - const vtx_buffer_descriptor = gpu.Buffer.Descriptor{ - .size = mesh.positions.len * @sizeOf(f32x4), +pub const ArenaBuffer = struct { + buffer: *gpu.Buffer, + size: u32, + offset: u32 = 0, + + pub fn init_vtx(size: u32, device: *gpu.Device) ArenaBuffer { + const descriptor = gpu.Buffer.Descriptor{ + .size = size, .usage = .{ .vertex = true, .copy_dst = true }, .mapped_at_creation = .false, }; - const vtx_buffer = app.device.createBuffer(&vtx_buffer_descriptor); - app.queue.writeBuffer(vtx_buffer, 0, mesh.positions); + const buffer = device.createBuffer(&descriptor); + return .{ + .buffer = buffer, + .size = size, + }; + } - const vtx_attributes = [_]gpu.VertexAttribute{ - .{ .format = .float32x4, .shader_location = 0, .offset = 0 }, + pub fn init_idx(size: u32, device: *gpu.Device) ArenaBuffer { + const descriptor = gpu.Buffer.Descriptor{ + .size = size, + .usage = .{ .index = true, .copy_dst = true }, + .mapped_at_creation = .false, + }; + const buffer = device.createBuffer(&descriptor); + return .{ + .buffer = buffer, + .size = size, }; - const vtx_buffer_layout = gpu.VertexBufferLayout.init(.{ + } + + pub fn alloc(self: *ArenaBuffer, size: u32) !u32 { + const offset_start = self.offset; + const offset_after = self.offset + size; + if (offset_after > self.size) { + std.log.err("arena buffer out of memory", .{}); + return error.ArenaBufferEmpty; + } + std.log.info("arena buffer alloc: {}", .{size}); + self.offset = offset_after; + return offset_start; + } +}; + +pub const MeshInitOptions = struct { + queue: *gpu.Queue, + vtx_buffer: *ArenaBuffer, + idx_buffer: *ArenaBuffer, + positions: []f32x4, + normals: []f32x3, + tex_coords: []f32x3, + indices: []u32, +}; + +pub const Mesh = struct { + vtx_buffer: *ArenaBuffer, + idx_buffer: *ArenaBuffer, + + num_vertices: usize, + offset_positions: usize, + offset_normals: ?usize, + offset_tex_coords: ?usize, + + num_indices: usize, + offset_indices: usize, + + pub fn vertex_buffer_layouts() [3]gpu.VertexBufferLayout { + const p_buffer_layout = gpu.VertexBufferLayout.init(.{ .array_stride = @sizeOf(f32x4), .step_mode = .vertex, - .attributes = &vtx_attributes, + .attributes = &.{ + .{ .format = .float32x4, .shader_location = 0, .offset = 0 }, + }, }); + const n_buffer_layout = gpu.VertexBufferLayout.init(.{ + .array_stride = @sizeOf(f32x4), + .step_mode = .vertex, + .attributes = &.{ + .{ .format = .float32x3, .shader_location = 1, .offset = 0 }, + }, + }); + const t_buffer_layout = gpu.VertexBufferLayout.init(.{ + .array_stride = @sizeOf(f32x4), + .step_mode = .vertex, + .attributes = &.{ + .{ .format = .float32x3, .shader_location = 2, .offset = 0 }, + }, + }); + return .{ p_buffer_layout, n_buffer_layout, t_buffer_layout }; + } - const idx_buffer_descriptor = gpu.Buffer.Descriptor{ - .size = mesh.indices.len * @sizeOf(u32), - .usage = .{ .index = true, .copy_dst = true }, - .mapped_at_creation = .false, + pub fn init(options: MeshInitOptions) !Mesh { + const num_vertices = options.positions.len; + const num_indices = options.indices.len; + const vtx_buffer = options.vtx_buffer; + const idx_buffer = options.idx_buffer; + + const size_p = num_vertices * @sizeOf(f32x4); + const size_n = num_vertices * @sizeOf(f32x3); + const size_t = num_vertices * @sizeOf(f32x3); + const size_vtx = size_p + size_n + size_t; + + const offset_p = try vtx_buffer.alloc(@intCast(size_vtx)); + const offset_n = offset_p + size_p; + const offset_t = offset_n + size_n; + + const size_i = num_indices * @sizeOf(u32); + const offset_i = try idx_buffer.alloc(@intCast(size_i)); + + const queue = options.queue; + queue.writeBuffer(vtx_buffer.buffer, offset_p, options.positions); + queue.writeBuffer(vtx_buffer.buffer, offset_n, options.normals); + queue.writeBuffer(vtx_buffer.buffer, offset_t, options.tex_coords); + queue.writeBuffer(idx_buffer.buffer, offset_i, options.indices); + + return .{ + .vtx_buffer = vtx_buffer, + .idx_buffer = idx_buffer, + .num_vertices = num_vertices, + .offset_positions = offset_p, + .offset_normals = offset_n, + .offset_tex_coords = offset_t, + .num_indices = num_indices, + .offset_indices = offset_i, }; - const idx_buffer = app.device.createBuffer(&idx_buffer_descriptor); - app.queue.writeBuffer(idx_buffer, 0, mesh.indices); + } +}; + +const UnlitRenderPipeline = struct { + gpu_pipeline: *gpu.RenderPipeline, + mesh: *const Mesh, + pub fn init(app: *App, mesh: *const Mesh) UnlitRenderPipeline { const vs = \\ struct VertexInput { \\ @location(0) position: vec4<f32>, @@ -383,10 +477,11 @@ const UnlitRenderPipeline = struct { const vs_module = app.device.createShaderModuleWGSL("default vertex shader", vs); defer vs_module.release(); + const buffer_layouts = Mesh.vertex_buffer_layouts(); const vertex = gpu.VertexState.init(.{ .module = vs_module, .entry_point = "main", - .buffers = &.{vtx_buffer_layout}, + .buffers = &buffer_layouts, }); const fs = @@ -425,8 +520,6 @@ const UnlitRenderPipeline = struct { return .{ .gpu_pipeline = app.device.createRenderPipeline(&pipeline_descriptor), - .vtx_buffer = vtx_buffer, - .idx_buffer = idx_buffer, .mesh = mesh, }; } @@ -435,9 +528,15 @@ const UnlitRenderPipeline = struct { const self: *UnlitRenderPipeline = @ptrCast(@alignCast(ptr)); pass.setPipeline(self.gpu_pipeline); - pass.setVertexBuffer(0, self.vtx_buffer, 0, @sizeOf(f32x4) * self.mesh.positions.len); - pass.setIndexBuffer(self.idx_buffer, .uint32, 0, @sizeOf(u32) * self.mesh.indices.len); - pass.drawIndexed(@intCast(self.mesh.indices.len), 1, 0, 0, 0); + pass.setVertexBuffer(0, self.mesh.vtx_buffer.buffer, self.mesh.offset_positions, @sizeOf(f32x4) * self.mesh.num_vertices); + pass.setVertexBuffer(1, self.mesh.vtx_buffer.buffer, self.mesh.offset_normals.?, @sizeOf(f32x3) * self.mesh.num_vertices); + pass.setVertexBuffer(2, self.mesh.vtx_buffer.buffer, self.mesh.offset_normals.?, @sizeOf(f32x3) * self.mesh.num_vertices); + pass.setIndexBuffer(self.mesh.idx_buffer.buffer, .uint32, self.mesh.offset_indices, @sizeOf(u32) * self.mesh.num_indices); + pass.drawIndexed(@intCast(self.mesh.num_indices), 1, 0, 0, 0); + + //pass.setVertexBuffer(0, self.vtx_buffer, 0, @sizeOf(f32x4) * self.mesh.positions.len); + //pass.setIndexBuffer(self.idx_buffer, .uint32, 0, @sizeOf(u32) * self.mesh.indices.len); + //pass.drawIndexed(@intCast(self.mesh.indices.len), 1, 0, 0, 0); } pub fn pipeline(self: *UnlitRenderPipeline) RenderPipeline { @@ -452,12 +551,21 @@ pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; const allocator = gpa.allocator(); - const mesh = try load_obj.loadFile(allocator, "bunny-fixed.obj"); - var app: App = undefined; try app.init(allocator); defer app.deinit(); + var vtx_buffer = ArenaBuffer.init_vtx(1000000, app.device); + var idx_buffer = ArenaBuffer.init_idx(500000, app.device); + + const mesh = try load_obj.loadFile(.{ + .queue = app.queue, + .allocator = allocator, + .vtx_buffer = &vtx_buffer, + .idx_buffer = &idx_buffer, + .path = "bunny-fixed.obj", + }); + var drp = UnlitRenderPipeline.init(&app, &mesh); const pipelines = [_]RenderPipeline{drp.pipeline()};