diff --git a/src/main.zig b/src/main.zig index 1c4e197..697e3ac 100644 --- a/src/main.zig +++ b/src/main.zig @@ -8,6 +8,10 @@ const zm = @import("zmath"); const vec = zm.f32x4; const Mat = zm.Mat; +const primitives = @import("./primitives.zig"); +const VertexData = primitives.VertexData; +const PrimitiveData = primitives.PrimitiveData; + /// Holds information about how a perticular scene should be rendered. const SceneUniformBuffer = struct { view_proj_matrix: zm.Mat, @@ -25,30 +29,8 @@ const ObjectData = struct { uniform_buffer: *gpu.Buffer, /// Bind group used to associate the buffer to the `object` shader parameter. uniform_bind_group: *gpu.BindGroup, - /// Index into `primitives` to specify which primitive to render. - primitive_index: usize, -}; - -/// Describes the layout of each vertex that a primitive is made of. -const VertexData = struct { - position: [3]f32, -}; - -/// Contains the data to render a primitive (3D shape or model). -const PrimitiveData = struct { - /// Vertices describe the "points" that a primitive is made out of. - /// This buffer is of type `[]VertexData`. - vertex_buffer: *gpu.Buffer, - vertex_count: u32, - - /// Indices describe what vertices make up the triangles in a primitive. - /// This buffer is of type `[]u32`. - index_buffer: *gpu.Buffer, - index_count: u32, - - // For example, `vertex_buffer` may have 4 points defining a square, but - // since it needs to be rendered using 2 triangles, `index_buffer` will - // contain 6 entries, `0, 1, 2` and `3, 2, 1` making up one triangle each. + /// Reference to the primitive (shape or model) to render for this object. + primitive: *PrimitiveData, }; pub const App = @This(); @@ -65,7 +47,7 @@ depth_texture_view: *gpu.TextureView, pipeline: *gpu.RenderPipeline, scene_uniform_buffer: *gpu.Buffer, scene_uniform_bind_group: *gpu.BindGroup, -primitives: []PrimitiveData, +primitive_data: [2]PrimitiveData, object_data: []ObjectData, pub fn init(app: *App) !void { @@ -146,48 +128,10 @@ pub fn init(app: *App) !void { } // Set up the primitives we want to render. - app.primitives = try app.allocator.alloc(PrimitiveData, 2); - // Triangle - app.primitives[0] = createPrimitive( - &.{ - // zig fmt: off - .{ .position = .{ 0.0, 0.5, 0.0 } }, - .{ .position = .{ 0.5, -0.5, 0.0 } }, - .{ .position = .{ -0.5, -0.5, 0.0 } }, - // zig fmt: on - }, - // Note that the back faces of triangles are "culled", and thus not visible. - // We need to take care to specify the vertices in counter-clock orientation. - &.{ - 0, 1, 2, - }, - ); - // Square - app.primitives[1] = createPrimitive( - // A square is made up of 4 vertices. - // 0--2 - // | | - // | | - // 1--3 - &.{ - // zig fmt: off - .{ .position = .{ -0.5, -0.5, 0.0 } }, - .{ .position = .{ -0.5, 0.5, 0.0 } }, - .{ .position = .{ 0.5, -0.5, 0.0 } }, - .{ .position = .{ 0.5, 0.5, 0.0 } }, - // zig fmt: on - }, - // But it has to be split up into 2 triangles, - // specified in a counter-clockwise orientation. - // 0--2 4 - // | / /| - // |/ / | - // 1 5--3 - &.{ - 0, 1, 2, - 3, 2, 1, - }, - ); + app.primitive_data = .{ + primitives.createTrianglePrimitive(1.0), + primitives.createSquarePrimitive(1.0), + }; // Set up object related uniform buffers and bind groups. // This uploads data to the GPU about all the object we @@ -230,12 +174,16 @@ pub fn init(app: *App) !void { }, ); + // Pick a "random" primitive to use for this object. + const primitive_index = app.random.int(usize) % app.primitive_data.len; + const primitive = &app.primitive_data[primitive_index]; + // The `*object` syntax gets us a pointer to each element in the // `object_data` slice, allowing us to override it within the loop. object.* = .{ .uniform_buffer = result.buffer, .uniform_bind_group = result.bind_group, - .primitive_index = app.random.int(usize) % app.primitives.len, + .primitive = primitive, }; } } @@ -244,7 +192,7 @@ pub fn init(app: *App) !void { /// Creates a buffer on the GPU to store uniform parameter information as /// well as a bind group with the specified layout pointing to that buffer. /// Additionally, immediately fills the buffer with the provided data. -fn createAndWriteUniformBuffer( +pub fn createAndWriteUniformBuffer( layout: *gpu.BindGroupLayout, data: anytype, ) struct { @@ -267,7 +215,7 @@ fn createAndWriteUniformBuffer( /// Creates a buffer on the GPU with the specified usage /// flags and immediately fills it with the provided data. -fn createAndWriteBuffer( +pub fn createAndWriteBuffer( comptime T: type, data: []const T, usage: gpu.Buffer.UsageFlags, @@ -281,20 +229,6 @@ fn createAndWriteBuffer( return buffer; } -/// Creates a primitive from the provided vertices and indices, -/// and uploads the buffers necessary to render it to the GPU. -fn createPrimitive( - vertices: []const VertexData, - indices: []const u32, -) PrimitiveData { - return .{ - .vertex_buffer = createAndWriteBuffer(VertexData, vertices, .{ .vertex = true, .copy_dst = true }), - .vertex_count = @intCast(vertices.len), - .index_buffer = createAndWriteBuffer(u32, indices, .{ .index = true, .copy_dst = true }), - .index_count = @intCast(indices.len), - }; -} - pub fn deinit(app: *App) void { // Using `defer` here, so we can specify them // in the order they were created in `init`. @@ -305,8 +239,7 @@ pub fn deinit(app: *App) void { defer app.pipeline.release(); defer app.scene_uniform_buffer.release(); defer app.scene_uniform_bind_group.release(); - defer app.allocator.free(app.primitives); - defer for (app.primitives) |p| { + defer for (app.primitive_data) |p| { p.vertex_buffer.release(); p.index_buffer.release(); }; @@ -389,16 +322,15 @@ pub fn update(app: *App) !bool { for (app.object_data) |object| { // Set the vertex and index buffer used to render this object // to the primitive it wants to use (either triangle or square). - const primitive_index = object.primitive_index; - const primitive = app.primitives[primitive_index]; - pass.setVertexBuffer(0, primitive.vertex_buffer, 0, primitive.vertex_count * @sizeOf(VertexData)); - pass.setIndexBuffer(primitive.index_buffer, .uint32, 0, primitive.index_count * @sizeOf(u32)); + const prim = object.primitive; + pass.setVertexBuffer(0, prim.vertex_buffer, 0, prim.vertex_count * @sizeOf(VertexData)); + pass.setIndexBuffer(prim.index_buffer, .uint32, 0, prim.index_count * @sizeOf(u32)); // Set the bind group for an object we want to render. pass.setBindGroup(1, object.uniform_bind_group, &.{}); // Draw a number of triangles as specified in the index buffer. - pass.drawIndexed(primitive.index_count, 1, 0, 0, 0); + pass.drawIndexed(prim.index_count, 1, 0, 0, 0); } } diff --git a/src/primitives.zig b/src/primitives.zig new file mode 100644 index 0000000..dd909c3 --- /dev/null +++ b/src/primitives.zig @@ -0,0 +1,102 @@ +const std = @import("std"); + +const core = @import("mach-core"); +const gpu = core.gpu; + +const main = @import("./main.zig"); +const createAndWriteBuffer = main.createAndWriteBuffer; + +/// Describes the layout of each vertex that a primitive is made of. +pub const VertexData = struct { + position: [3]f32, +}; + +/// Contains the data to render a primitive (3D shape or model). +pub const PrimitiveData = struct { + /// Vertices describe the "points" that a primitive is made out of. + /// This buffer is of type `[]VertexData`. + vertex_buffer: *gpu.Buffer, + vertex_count: u32, + + /// Indices describe what vertices make up the triangles in a primitive. + /// This buffer is of type `[]u32`. + index_buffer: *gpu.Buffer, + index_count: u32, + + // For example, `vertex_buffer` may have 4 points defining a square, but + // since it needs to be rendered using 2 triangles, `index_buffer` will + // contain 6 entries, `0, 1, 2` and `3, 2, 1` making up one triangle each. +}; + +/// Creates a primitive from the provided vertices and indices, +/// and uploads the buffers necessary to render it to the GPU. +pub fn createPrimitive( + vertices: []const VertexData, + indices: []const u32, +) PrimitiveData { + return .{ + .vertex_buffer = createAndWriteBuffer(VertexData, vertices, .{ .vertex = true, .copy_dst = true }), + .vertex_count = @intCast(vertices.len), + .index_buffer = createAndWriteBuffer(u32, indices, .{ .index = true, .copy_dst = true }), + .index_count = @intCast(indices.len), + }; +} + +fn vert(x: f32, y: f32, z: f32) VertexData { + return .{ .position = .{ x, y, z } }; +} + +pub fn createTrianglePrimitive(length: f32) PrimitiveData { + const radius = length / @sqrt(3.0); + const a0 = 0.0; + const a1 = std.math.tau / 3.0; + const a2 = std.math.tau / 3.0 * 2.0; + return createPrimitive( + // A triangle is made up of 3 vertices. + // + // 0 + // / \ + // / \ + // 1-----2 + &.{ + vert(@sin(a0) * radius, @cos(a0) * radius, 0.0), + vert(@sin(a1) * radius, @cos(a1) * radius, 0.0), + vert(@sin(a2) * radius, @cos(a2) * radius, 0.0), + }, + // Vertices have to be specified in counter-clockwise, + // so the "front" of the triangle is facing the right way. + &.{ + 0, 1, 2, + }, + ); +} + +pub fn createSquarePrimitive(width: f32) PrimitiveData { + const half_width = width / 2.0; + return createPrimitive( + // A square is made up of 4 vertices, ... + // + // 0---2 + // | | + // | | + // 1---3 + &.{ + // zig fmt: off + vert(-half_width, -half_width, 0.0), + vert(-half_width, half_width, 0.0), + vert( half_width, -half_width, 0.0), + vert( half_width, half_width, 0.0), + // zig fmt: on + }, + // ... but it has to be split up into 2 triangles. + // + // 0--2 4 + // | / /| + // |/ / | + // 1 5--3 + &.{ + 0, 1, 2, + 3, 2, 1, + }, + ); +}