commit ab98ceef3563a133a5a8366e6c2f63058789df53
parent bb734a86d55b2aa1e960d3d8e8b1af428e6f1fe1
Author: Christian Ermann <christianermann@gmail.com>
Date: Wed, 8 May 2024 19:42:17 -0400
Add instance buffer for meshes
Diffstat:
2 files changed, 123 insertions(+), 30 deletions(-)
diff --git a/src/load_obj.zig b/src/load_obj.zig
@@ -3,12 +3,13 @@ const gpu = @import("mach_gpu");
const Mesh = @import("main.zig").Mesh;
const MeshBuffer = @import("main.zig").MeshBuffer;
+const MeshVertexData = @import("main.zig").MeshVertexData;
const f32x2 = @Vector(2, f32);
const f32x3 = @Vector(3, f32);
const f32x4 = @Vector(4, f32);
-fn parsePosition(line: []u8, scale: f32) !f32x4 {
+fn parsePosition(line: []u8) !f32x4 {
var iter = std.mem.tokenize(u8, line[2..], " ");
var position = f32x4{ 0, 0, 0, 1 };
var idx: u32 = 0;
@@ -16,7 +17,7 @@ fn parsePosition(line: []u8, scale: f32) !f32x4 {
if (idx >= 4) {
return error.InvalidFormat;
}
- position[idx] = try std.fmt.parseFloat(f32, part) * scale;
+ position[idx] = try std.fmt.parseFloat(f32, part);
}
if (idx < 3) {
return error.InvalidFormat;
@@ -104,6 +105,7 @@ pub const LoadFileOptions = struct {
mesh_buffer: *MeshBuffer,
path: []const u8,
scale: f32 = 1.0,
+ offset: f32x3 = .{ 0, 0, 0 },
};
pub fn loadFile(options: LoadFileOptions) !Mesh {
@@ -129,7 +131,7 @@ pub fn loadFile(options: LoadFileOptions) !Mesh {
var vtx_idx: u32 = 0;
while (try stream.readUntilDelimiterOrEof(&buf, '\n')) |line| {
if (std.mem.startsWith(u8, line, "v ")) {
- try positions.append(try parsePosition(line, options.scale));
+ try positions.append(try parsePosition(line));
} else if (std.mem.startsWith(u8, line, "vt ")) {
try tex_coords.append(try parseTexCoord(line));
} else if (std.mem.startsWith(u8, line, "vn ")) {
@@ -187,10 +189,15 @@ pub fn loadFile(options: LoadFileOptions) !Mesh {
const tslice = try tex_coords_indexed.toOwnedSlice();
const islice = try indices.toOwnedSlice();
- return options.mesh_buffer.initMesh(&.{
+ const mesh_data = MeshVertexData{
.positions = pslice,
.normals = nslice,
.tex_coords = tslice,
.indices = islice,
+ };
+ return options.mesh_buffer.initMesh(&.{
+ .scale = options.scale,
+ .offset = options.offset,
+ .data = &mesh_data,
});
}
diff --git a/src/main.zig b/src/main.zig
@@ -88,7 +88,16 @@ const App = struct {
props.driver_description,
});
- app.device = app.adapter.createDevice(null) orelse {
+ const features = [_]gpu.FeatureName{
+ gpu.FeatureName.indirect_first_instance,
+ };
+ const device_descriptor = gpu.Device.Descriptor{
+ .required_features_count = 1,
+ .required_features = &features,
+ .device_lost_callback = undefined,
+ .device_lost_userdata = undefined,
+ };
+ app.device = app.adapter.createDevice(&device_descriptor) orelse {
std.log.err("failed to create GPU device", .{});
std.process.exit(1);
};
@@ -331,6 +340,11 @@ const DefaultRenderPipeline = struct {
const f32x3 = @Vector(3, f32);
const f32x4 = @Vector(4, f32);
+const mat4 = [4]@Vector(4, f32);
+
+const InstanceData = struct {
+ model_matrix: mat4,
+};
pub const Buffer = struct {
data: *gpu.Buffer,
@@ -376,6 +390,24 @@ pub const Buffer = struct {
};
}
+ pub fn init_instance(n_instances: u32, device: *gpu.Device) Buffer {
+ std.log.info("buffer init: {} instances", .{n_instances});
+ const attrib_size = @sizeOf(InstanceData);
+ const size = n_instances * attrib_size;
+ const descriptor = gpu.Buffer.Descriptor{
+ .size = size,
+ .usage = .{ .storage = true, .copy_dst = true },
+ .mapped_at_creation = .false,
+ };
+ const data = device.createBuffer(&descriptor);
+ return .{
+ .data = data,
+ .size = size,
+ .offset = 0,
+ .attrib_size = attrib_size,
+ };
+ }
+
pub fn alloc(self: *Buffer, n_elements: u32) !u32 {
const size = n_elements * self.attrib_size;
const offset_start = self.offset;
@@ -413,8 +445,9 @@ pub const MeshVertexData = struct {
};
pub const MeshOptions = struct {
- queue: *gpu.Queue,
- mesh_data: *MeshVertexData,
+ scale: f32 = 1.0,
+ offset: f32x3 = .{ 0, 0, 0 },
+ data: *const MeshVertexData,
};
pub const MeshBuffer = struct {
@@ -425,6 +458,7 @@ pub const MeshBuffer = struct {
normals: Buffer,
tex_coords: Buffer,
indices: Buffer,
+ instances: Buffer,
buf_indirect: *gpu.Buffer,
meshes: std.ArrayList(Mesh),
@@ -448,12 +482,14 @@ pub const MeshBuffer = struct {
.normals = Buffer.init_vtx(n_vertices, device),
.tex_coords = Buffer.init_vtx(n_vertices, device),
.indices = Buffer.init_idx(n_indices, device),
+ .instances = Buffer.init_instance(options.n_meshes, device),
.buf_indirect = buf_indirect,
.meshes = std.ArrayList(Mesh).init(options.allocator),
};
}
- pub fn initMesh(self: *MeshBuffer, mesh_data: *const MeshVertexData) !Mesh {
+ pub fn initMesh(self: *MeshBuffer, options: *const MeshOptions) !Mesh {
+ const mesh_data = options.data;
const num_vertices = mesh_data.positions.len;
const num_indices = mesh_data.indices.len;
std.log.info("init mesh with {} vertices, {} indices", .{ num_vertices, num_indices });
@@ -466,26 +502,31 @@ pub const MeshBuffer = struct {
const offset_i = try self.indices.allocWrite(mesh_data.indices, self.queue);
std.log.info("idx offset: {} bytes", .{offset_i});
+ const mesh = Mesh{
+ .num_vertices = num_vertices,
+ .num_indices = num_indices,
+ .scale = options.scale,
+ .offset = options.offset,
+ };
+
+ const instances = [_]InstanceData{
+ mesh.instanceData(),
+ };
+ const offset_u = try self.instances.allocWrite(&instances, self.queue);
+ std.log.info("uniform offset: {} bytes", .{offset_u});
+
const mesh_id: u32 = @intCast(self.meshes.items.len);
const indirect_args = [_]u32{
@intCast(num_indices),
1,
offset_i / self.indices.attrib_size,
offset_p / self.positions.attrib_size,
- 0,
+ mesh_id,
};
std.log.info("indirect: {any}", .{indirect_args});
const offset = mesh_id * 5 * @sizeOf(u32);
self.queue.writeBuffer(self.buf_indirect, offset, &indirect_args);
- const mesh = .{
- .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,
- };
try self.meshes.append(mesh);
return mesh;
}
@@ -493,41 +534,61 @@ pub const MeshBuffer = struct {
pub const Mesh = struct {
num_vertices: usize,
- offset_positions: usize,
- offset_normals: ?usize,
- offset_tex_coords: ?usize,
num_indices: usize,
- offset_indices: usize,
+ scale: f32 = 1.0,
+ offset: f32x3 = .{ 0.0, 0.0, 0.0 },
+
+ pub fn instanceData(self: *const Mesh) InstanceData {
+ return InstanceData{ .model_matrix = .{
+ .{ self.scale, 0, 0, 0 },
+ .{ 0, self.scale, 0, 0 },
+ .{ 0, 0, self.scale, 0 },
+ .{ self.offset[0], self.offset[1], self.offset[2], 1 },
+ } };
+ }
- pub fn layouts() [3]gpu.VertexBufferLayout {
- const p_buffer_layout = gpu.VertexBufferLayout.init(.{
+ pub fn bufferLayouts() [3]gpu.VertexBufferLayout {
+ const positions = gpu.VertexBufferLayout.init(.{
.array_stride = @sizeOf(f32x4),
.step_mode = .vertex,
.attributes = &.{
.{ .format = .float32x4, .shader_location = 0, .offset = 0 },
},
});
- const n_buffer_layout = gpu.VertexBufferLayout.init(.{
+ const normals = 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(.{
+ const tex_coords = 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 };
+ return .{ positions, normals, tex_coords };
+ }
+
+ pub fn bindGroupLayout(device: *gpu.Device) *gpu.BindGroupLayout {
+ const descriptor = gpu.BindGroupLayout.Descriptor.init(.{
+ .entries = &.{
+ gpu.BindGroupLayout.Entry.buffer(0, .{
+ .vertex = true,
+ .fragment = false,
+ }, .read_only_storage, false, 0),
+ },
+ });
+ return device.createBindGroupLayout(&descriptor);
}
};
const UnlitRenderPipeline = struct {
gpu_pipeline: *gpu.RenderPipeline,
mesh_buffer: *const MeshBuffer,
+ bind_group: *gpu.BindGroup,
pub fn init(app: *App, mesh_buffer: *const MeshBuffer, _: std.mem.Allocator) !UnlitRenderPipeline {
const vs =
@@ -535,14 +596,21 @@ const UnlitRenderPipeline = struct {
\\ @location(0) position: vec4<f32>,
\\ };
\\
- \\ @vertex fn main(in: VertexInput) -> @builtin(position) vec4<f32> {
- \\ return in.position;
+ \\ @group(0) @binding(0)
+ \\ var<storage, read> instances: array<Instance>;
+ \\ struct Instance {
+ \\ model_matrix: mat4x4<f32>,
+ \\ };
+ \\
+ \\ @vertex fn main(in: VertexInput, @builtin(instance_index) idx: u32) -> @builtin(position) vec4<f32> {
+ \\ let model_matrix = instances[idx].model_matrix;
+ \\ return model_matrix * in.position;
\\ }
;
const vs_module = app.device.createShaderModuleWGSL("default vertex shader", vs);
defer vs_module.release();
- const layouts = Mesh.layouts();
+ const layouts = Mesh.bufferLayouts();
const vertex = gpu.VertexState.init(.{
.module = vs_module,
.entry_point = "main",
@@ -569,10 +637,25 @@ const UnlitRenderPipeline = struct {
.targets = &.{color_target},
});
+ const bind_group_layout = Mesh.bindGroupLayout(app.device);
+ const bind_group_descriptor = gpu.BindGroup.Descriptor.init(.{
+ .layout = bind_group_layout,
+ .entries = &.{
+ gpu.BindGroup.Entry.buffer(0, mesh_buffer.instances.data, 0, mesh_buffer.instances.size),
+ },
+ });
+ const bind_group = app.device.createBindGroup(&bind_group_descriptor);
+
+ const pipeline_layout_descriptor = gpu.PipelineLayout.Descriptor.init(.{
+ .bind_group_layouts = &.{bind_group_layout},
+ });
+ const pipeline_layout = app.device.createPipelineLayout(&pipeline_layout_descriptor);
+ defer pipeline_layout.release();
+
const pipeline_descriptor = gpu.RenderPipeline.Descriptor{
.label = "default render pipeline",
.fragment = &fragment,
- .layout = null,
+ .layout = pipeline_layout,
.depth_stencil = null,
.vertex = vertex,
.multisample = .{},
@@ -586,6 +669,7 @@ const UnlitRenderPipeline = struct {
return .{
.gpu_pipeline = app.device.createRenderPipeline(&pipeline_descriptor),
.mesh_buffer = mesh_buffer,
+ .bind_group = bind_group,
};
}
@@ -596,6 +680,7 @@ const UnlitRenderPipeline = struct {
pass.setVertexBuffer(0, self.mesh_buffer.positions.data, 0, self.mesh_buffer.positions.size);
pass.setVertexBuffer(1, self.mesh_buffer.normals.data, 0, self.mesh_buffer.normals.size);
pass.setVertexBuffer(2, self.mesh_buffer.tex_coords.data, 0, self.mesh_buffer.tex_coords.size);
+ pass.setBindGroup(0, self.bind_group, null);
pass.setIndexBuffer(self.mesh_buffer.indices.data, .uint32, 0, self.mesh_buffer.indices.size);
for (0..self.mesh_buffer.meshes.items.len) |mesh_idx| {
@@ -640,6 +725,7 @@ pub fn main() !void {
.mesh_buffer = &mesh_buffer,
.path = "teapot.obj",
.scale = 0.05,
+ .offset = .{ 0, -0.5, 0 },
});
var drp = try UnlitRenderPipeline.init(&app, &mesh_buffer, allocator);