From da82690c8a4085b71d00e16e6538556ca0ecf182 Mon Sep 17 00:00:00 2001 From: copygirl Date: Wed, 27 Mar 2024 11:49:46 +0100 Subject: [PATCH] Don't use structs for uniforms --- src/renderer.zig | 110 ++++++++++++++++++----------------------------- src/shader.wgsl | 17 +++----- 2 files changed, 47 insertions(+), 80 deletions(-) diff --git a/src/renderer.zig b/src/renderer.zig index 5460130..d38396d 100644 --- a/src/renderer.zig +++ b/src/renderer.zig @@ -13,23 +13,11 @@ 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: Mat, -}; - -/// Holds information about where and how an object should be rendered. -const ObjectUniformBuffer = struct { - model_matrix: Mat, - color: [3]f32, -}; - /// Holds data needed to render an object in a rendering pass. const ObjectData = struct { - /// Reference to data stored on the GPU of type `ObjectUniformBuffer`. - uniform_buffer: *gpu.Buffer, - /// Bind group used to associate the buffer to the `object` shader parameter. - uniform_bind_group: *gpu.BindGroup, + /// Bind group which associates model-related buffers with parameters + /// in our shader. This one is accessible via `@group(1)` in our shader. + model_bind_group: *gpu.BindGroup, /// Reference to the primitive (shape or model) to render for this object. primitive: *PrimitiveData, }; @@ -39,8 +27,8 @@ const Renderer = @This(); app: *App, pipeline: *gpu.RenderPipeline, -scene_uniform_buffer: *gpu.Buffer, -scene_uniform_bind_group: *gpu.BindGroup, +view_proj_buffer: *gpu.Buffer, +camera_bind_group: *gpu.BindGroup, depth_texture: ?*gpu.Texture = null, depth_texture_view: ?*gpu.TextureView = null, @@ -85,10 +73,16 @@ pub fn init(app: *App) !*Renderer { }); // Set up scene related uniform buffers and bind groups. - const scene_uniform = createAndWriteUniformBuffer( - pipeline.getBindGroupLayout(0), - SceneUniformBuffer{ .view_proj_matrix = zm.identity() }, - ); + const view_proj_buffer = createAndWriteBuffer(zm.Mat, &.{zm.identity()}, .{ .copy_dst = true, .uniform = true }); + + // "Bind groups" are used to associate data from buffers with shader parameters. + // So for example the `camera_bind_group` is accessible via `@group(0)` in our shader. + const camera_bind_group = core.device.createBindGroup(&gpu.BindGroup.Descriptor.init(.{ + .layout = pipeline.getBindGroupLayout(0), + .entries = &.{ + gpu.BindGroup.Entry.buffer(0, view_proj_buffer, 0, @sizeOf(zm.Mat)), + }, + })); // Set up the primitives we want to render. // @@ -128,19 +122,25 @@ pub fn init(app: *App) !*Renderer { // Make the object have a color depending on its location in the grid. // These values are layed out so each corner is red, green, blue and black. - const color = .{ + const model_color = .{ std.math.clamp(1.0 - x - z, 0.0, 1.0), std.math.clamp(x - z, 0.0, 1.0), std.math.clamp(z - x, 0.0, 1.0), }; - const object_uniform = createAndWriteUniformBuffer( - pipeline.getBindGroupLayout(1), - ObjectUniformBuffer{ - .model_matrix = zm.transpose(model_matrix), - .color = color, + const model_matrix_buffer = createAndWriteBuffer(zm.Mat, &.{zm.transpose(model_matrix)}, .{ .copy_dst = true, .uniform = true }); + defer model_matrix_buffer.release(); + + const model_color_buffer = createAndWriteBuffer([3]f32, &.{model_color}, .{ .copy_dst = true, .uniform = true }); + defer model_color_buffer.release(); + + const model_bind_group = core.device.createBindGroup(&gpu.BindGroup.Descriptor.init(.{ + .layout = pipeline.getBindGroupLayout(1), + .entries = &.{ + gpu.BindGroup.Entry.buffer(0, model_matrix_buffer, 0, @sizeOf(zm.Mat)), + gpu.BindGroup.Entry.buffer(1, model_color_buffer, 0, @sizeOf([3]f32)), }, - ); + })); // Pick a "random" primitive to use for this object. const primitive_index = app.random.int(usize) % primitive_data.len; @@ -149,8 +149,7 @@ pub fn init(app: *App) !*Renderer { // 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 = object_uniform.buffer, - .uniform_bind_group = object_uniform.bind_group, + .model_bind_group = model_bind_group, .primitive = primitive, }; } @@ -159,8 +158,8 @@ pub fn init(app: *App) !*Renderer { result.* = .{ .app = app, .pipeline = pipeline, - .scene_uniform_buffer = scene_uniform.buffer, - .scene_uniform_bind_group = scene_uniform.bind_group, + .view_proj_buffer = view_proj_buffer, + .camera_bind_group = camera_bind_group, .primitive_data = primitive_data, .object_data = object_data, }; @@ -178,8 +177,8 @@ pub fn deinit(self: *Renderer) void { defer self.app.allocator.destroy(self); defer self.pipeline.release(); - defer self.scene_uniform_buffer.release(); - defer self.scene_uniform_bind_group.release(); + defer self.view_proj_buffer.release(); + defer self.camera_bind_group.release(); defer self.app.allocator.free(self.primitive_data); defer for (self.primitive_data) |p| { @@ -188,8 +187,7 @@ pub fn deinit(self: *Renderer) void { }; defer self.app.allocator.free(self.object_data); defer for (self.object_data) |o| { - o.uniform_buffer.release(); - o.uniform_bind_group.release(); + o.model_bind_group.release(); }; defer if (self.depth_texture) |t| t.release(); @@ -249,11 +247,11 @@ pub fn update(self: *Renderer) void { defer encoder.release(); // Write to the scene uniform buffer for this set of commands. - encoder.writeBuffer(self.scene_uniform_buffer, 0, &[_]SceneUniformBuffer{.{ + encoder.writeBuffer(self.view_proj_buffer, 0, &[_]zm.Mat{ // All matrices the GPU has to work with need to be transposed, // because WebGPU uses column-major matrices while zmath is row-major. - .view_proj_matrix = zm.transpose(view_proj_matrix), - }}); + zm.transpose(view_proj_matrix), + }); { const pass = encoder.beginRenderPass(&render_pass_info); @@ -261,17 +259,17 @@ pub fn update(self: *Renderer) void { defer pass.end(); pass.setPipeline(self.pipeline); - pass.setBindGroup(0, self.scene_uniform_bind_group, &.{}); + pass.setBindGroup(0, self.camera_bind_group, &.{}); for (self.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). + // Set the vertex and index buffer used to render this + // object to the ones from the primitive it wants to use. 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, &.{}); + // Set the bind group for the object we want to render. + pass.setBindGroup(1, object.model_bind_group, &.{}); // Draw a number of triangles as specified in the index buffer. pass.drawIndexed(prim.index_count, 1, 0, 0, 0); @@ -302,30 +300,6 @@ pub fn recreateDepthTexture(self: *Renderer) void { self.depth_texture_view = self.depth_texture.?.createView(null); } -/// 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. -pub fn createAndWriteUniformBuffer( - layout: *gpu.BindGroupLayout, - data: anytype, -) struct { - buffer: *gpu.Buffer, - bind_group: *gpu.BindGroup, -} { - const T = @TypeOf(data); - const usage = gpu.Buffer.UsageFlags{ .copy_dst = true, .uniform = true }; - const buffer = createAndWriteBuffer(T, &.{data}, usage); - - // "Bind groups" are used to associate data from buffers with shader parameters. - // So for example the `scene_uniform_bind_group` is accessible via `scene` in our shader. - // Essentially, buffer = data, and bind group = binding parameter to that data. - const bind_group_entry = gpu.BindGroup.Entry.buffer(0, buffer, 0, @sizeOf(T)); - const bind_group_desc = gpu.BindGroup.Descriptor.init(.{ .layout = layout, .entries = &.{bind_group_entry} }); - const bind_group = core.device.createBindGroup(&bind_group_desc); - - return .{ .buffer = buffer, .bind_group = bind_group }; -} - /// Creates a buffer on the GPU with the specified usage /// flags and immediately fills it with the provided data. pub fn createAndWriteBuffer( diff --git a/src/shader.wgsl b/src/shader.wgsl index 536c2c1..30463f5 100644 --- a/src/shader.wgsl +++ b/src/shader.wgsl @@ -1,14 +1,7 @@ -struct SceneUniformBuffer { - view_proj_matrix: mat4x4, -}; - -struct ObjectUniformBuffer { - model_matrix: mat4x4, - color: vec3, -}; +@group(0) @binding(0) var view_proj_matrix: mat4x4; -@group(0) @binding(0) var scene: SceneUniformBuffer; -@group(1) @binding(0) var object: ObjectUniformBuffer; +@group(1) @binding(0) var model_matrix: mat4x4; +@group(1) @binding(1) var model_color: vec3; struct VertexInput { @location(0) position: vec3, @@ -25,9 +18,9 @@ struct FragmentOutput { @vertex fn vertex_main(in: VertexInput) -> VertexOutput { var out: VertexOutput; - let mvp = object.model_matrix * scene.view_proj_matrix; + let mvp = model_matrix * view_proj_matrix; out.position = vec4(in.position, 1.0) * mvp; - out.color = vec4(object.color, 1.0); + out.color = vec4(model_color, 1.0); return out; }