Deduplicate multiple mesh instances

This commit is contained in:
Riyyi
2026-10-31 22:01:10 +01:00
parent 148b511009
commit 6feb0c1575
2 changed files with 95 additions and 83 deletions
+8 -10
View File
@@ -100,20 +100,18 @@ load_test_model :: proc() {
"assets/cube.obj", "assets/cube.obj",
cube.vertices, cube.vertices,
cube.indices, cube.indices,
true, 0,
) )
platform.create_renderable( platform.create_renderable(
"assets/suzanne.obj", "assets/suzanne.obj",
suzanne.vertices, suzanne.vertices,
suzanne.indices, suzanne.indices,
false, 3,
)
platform.create_renderable(
"assets/suzanne.obj",
suzanne.vertices,
suzanne.indices,
-3,
) )
} }
// per-object:
// - vertex buffer
// - index buffer
// - uniform buffer (model matrix)
// - texture
// - material
// - 1 drawcall
+87 -73
View File
@@ -25,7 +25,7 @@ Material_Uniform :: struct {
} }
// Updated once per mesh // Updated once per mesh
Renderable_Uniform :: struct { Object_Uniform :: struct {
transform: base.Mat4, transform: base.Mat4,
} }
@@ -34,13 +34,16 @@ Material :: struct {
uniform_buffer: wgpu.Buffer, uniform_buffer: wgpu.Buffer,
} }
// TODO: Split Renderable into mesh (vert, idx, #idx) and object (bg, ub) Object :: struct {
Renderable :: struct { name: string,
bind_group: wgpu.BindGroup, bind_group: wgpu.BindGroup,
uniform_buffer: wgpu.Buffer, uniform_buffer: wgpu.Buffer,
vertex_buffer: wgpu.Buffer, }
index_buffer: wgpu.Buffer,
index_count: u32, Mesh :: struct {
vertex_buffer: wgpu.Buffer,
index_buffer: wgpu.Buffer,
index_count: u32,
} }
// ----------------------------------------- // -----------------------------------------
@@ -48,38 +51,39 @@ Renderable :: struct {
// Instance -> Surface -> Adapter -> Device -> Queue // Instance -> Surface -> Adapter -> Device -> Queue
state: struct { state: struct {
ctx: runtime.Context, ctx: runtime.Context,
os: OS, os: OS,
// ---------------------------------------- // ----------------------------------------
instance: wgpu.Instance, // entry point: creates Adapter, Device and Surface instance: wgpu.Instance, // entry point: creates Adapter, Device and Surface
surface: wgpu.Surface, // handle to a presentable surface (e.g. a window) surface: wgpu.Surface, // handle to a presentable surface (e.g. a window)
adapter: wgpu.Adapter, // handle to physical GPU adapter: wgpu.Adapter, // handle to physical GPU
device: wgpu.Device, // open connection to a GPU device: wgpu.Device, // open connection to a GPU
queue: wgpu.Queue, // handle to command queue on a Device queue: wgpu.Queue, // handle to command queue on a Device
config: wgpu.SurfaceConfiguration, config: wgpu.SurfaceConfiguration,
module: wgpu.ShaderModule, module: wgpu.ShaderModule,
pipeline_layout: wgpu.PipelineLayout, pipeline_layout: wgpu.PipelineLayout,
pipeline: wgpu.RenderPipeline, pipeline: wgpu.RenderPipeline,
// ---------------------------------------- // ----------------------------------------
capabilities: wgpu.SurfaceCapabilities, capabilities: wgpu.SurfaceCapabilities,
vsync: bool, vsync: bool,
// ---------------------------------------- // ----------------------------------------
depth_texture: wgpu.Texture, depth_texture: wgpu.Texture,
depth_texture_view: wgpu.TextureView, depth_texture_view: wgpu.TextureView,
// ---------------------------------------- // ----------------------------------------
camera_uniform_buffer: wgpu.Buffer, camera_uniform_buffer: wgpu.Buffer,
camera_bind_group_layout: wgpu.BindGroupLayout, camera_bind_group_layout: wgpu.BindGroupLayout,
camera_bind_group: wgpu.BindGroup, camera_bind_group: wgpu.BindGroup,
// ---------------------------------------- // ----------------------------------------
material_bind_group_layout: wgpu.BindGroupLayout, material_bind_group_layout: wgpu.BindGroupLayout,
materials: map[string]Material, materials: map[string]Material,
// ---------------------------------------- // ----------------------------------------
renderable_bind_group_layout: wgpu.BindGroupLayout, objects_bind_group_layout: wgpu.BindGroupLayout,
renderables: map[string]Renderable, objects: [dynamic]Object,
meshes: map[string]Mesh,
// ---------------------------------------- // ----------------------------------------
diffuse_texture: wgpu.Texture, diffuse_texture: wgpu.Texture,
diffuse_texture_view: wgpu.TextureView, diffuse_texture_view: wgpu.TextureView,
diffuse_sampler: wgpu.Sampler, diffuse_sampler: wgpu.Sampler,
} }
// ----------------------------------------- // -----------------------------------------
@@ -92,7 +96,8 @@ instance_init :: proc(
state.ctx = context state.ctx = context
state.materials = make(map[string]Material, allocator) state.materials = make(map[string]Material, allocator)
state.renderables = make(map[string]Renderable, allocator) state.objects = make([dynamic]Object, allocator)
state.meshes = make(map[string]Mesh, allocator)
wgpu.SetLogCallback(log_callback, nil) wgpu.SetLogCallback(log_callback, nil)
wgpu.SetLogLevel(.Warn) wgpu.SetLogLevel(.Warn)
@@ -142,16 +147,21 @@ instance_destroy :: proc() {
} }
wgpu.BindGroupLayoutRelease(state.material_bind_group_layout) wgpu.BindGroupLayoutRelease(state.material_bind_group_layout)
if state.renderables != nil { if state.meshes != nil {
for _, r in state.renderables { for _, m in state.meshes {
wgpu.BufferRelease(m.vertex_buffer)
wgpu.BufferRelease(m.index_buffer)
}
delete(state.meshes)
}
if state.objects != nil {
for r in state.objects {
wgpu.BufferRelease(r.uniform_buffer) wgpu.BufferRelease(r.uniform_buffer)
wgpu.BindGroupRelease(r.bind_group) wgpu.BindGroupRelease(r.bind_group)
wgpu.BufferRelease(r.vertex_buffer)
wgpu.BufferRelease(r.index_buffer)
} }
delete(state.renderables) delete(state.objects)
} }
wgpu.BindGroupLayoutRelease(state.renderable_bind_group_layout) wgpu.BindGroupLayoutRelease(state.objects_bind_group_layout)
wgpu.RenderPipelineRelease(state.pipeline) wgpu.RenderPipelineRelease(state.pipeline)
wgpu.PipelineLayoutRelease(state.pipeline_layout) wgpu.PipelineLayoutRelease(state.pipeline_layout)
@@ -256,6 +266,7 @@ frame :: proc "c" () {
dynamicOffsets = nil, // reuse bind group af different byte offsets, per binding dynamicOffsets = nil, // reuse bind group af different byte offsets, per binding
) )
// Uniform group 1, materials
m := state.materials["dummy"] m := state.materials["dummy"]
wgpu.RenderPassEncoderSetBindGroup( wgpu.RenderPassEncoderSetBindGroup(
render_pass_encoder, render_pass_encoder,
@@ -264,27 +275,29 @@ frame :: proc "c" () {
dynamicOffsets = nil, // reuse bind group af different byte offsets, per binding dynamicOffsets = nil, // reuse bind group af different byte offsets, per binding
) )
// Render all renderables // Render all objects
for _, r in state.renderables { for o in state.objects {
// Uniform group 2 m := state.meshes[o.name]
// Uniform group 2, object
wgpu.RenderPassEncoderSetBindGroup( wgpu.RenderPassEncoderSetBindGroup(
render_pass_encoder, render_pass_encoder,
2, 2,
r.bind_group, o.bind_group,
dynamicOffsets = nil, // reuse bind group af different byte offsets, per binding dynamicOffsets = nil, // reuse bind group af different byte offsets, per binding
) )
wgpu.RenderPassEncoderSetVertexBuffer( wgpu.RenderPassEncoderSetVertexBuffer(
render_pass_encoder, render_pass_encoder,
slot = 0, slot = 0,
buffer = r.vertex_buffer, buffer = m.vertex_buffer,
offset = 0, // offset into buffer to start from offset = 0, // offset into buffer to start from
size = wgpu.WHOLE_SIZE, // x bytes of the buffer to use size = wgpu.WHOLE_SIZE, // x bytes of the buffer to use
) )
wgpu.RenderPassEncoderSetIndexBuffer( wgpu.RenderPassEncoderSetIndexBuffer(
render_pass_encoder, render_pass_encoder,
buffer = r.index_buffer, buffer = m.index_buffer,
format = .Uint16, // type of each element in the buffer format = .Uint16, // type of each element in the buffer
offset = 0, // offset into buffer to start from offset = 0, // offset into buffer to start from
size = wgpu.WHOLE_SIZE, // x bytes of the buffer to use size = wgpu.WHOLE_SIZE, // x bytes of the buffer to use
@@ -292,13 +305,12 @@ frame :: proc "c" () {
wgpu.RenderPassEncoderDrawIndexed( wgpu.RenderPassEncoderDrawIndexed(
render_pass_encoder, render_pass_encoder,
r.index_count, m.index_count,
instanceCount = 1, // draw x instance(s) of the mesh instanceCount = 1, // draw x instance(s) of the mesh
firstIndex = 0, // offset into buffer to start from, useful for drawing sub-mesh firstIndex = 0, // offset into buffer to start from, useful for drawing sub-mesh
baseVertex = 0, // value added to index before vertex fetch, useful for multi-mesh vertex buffers baseVertex = 0, // value added to index before vertex fetch, useful for multi-mesh vertex buffers
firstInstance = 0, // number to start instance index from firstInstance = 0, // number to start instance index from
) )
} }
wgpu.RenderPassEncoderEnd(render_pass_encoder) wgpu.RenderPassEncoderEnd(render_pass_encoder)
@@ -362,27 +374,20 @@ create_renderable :: proc(
name: string, name: string,
vertices: []base.Vertex, vertices: []base.Vertex,
indices: []u16, indices: []u16,
first: bool, x_offset: f32,
) { ) {
if name in state.renderables do return
uniform_buffer := wgpu.DeviceCreateBuffer( uniform_buffer := wgpu.DeviceCreateBuffer(
state.device, state.device,
&{ &{
label = "renderable uniform", label = "object uniform",
usage = {.Uniform, .CopyDst}, // shader reads, queue writes usage = {.Uniform, .CopyDst}, // shader reads, queue writes
size = cast(u64)size_of(Renderable_Uniform), size = cast(u64)size_of(Object_Uniform),
}, },
) )
transform: base.Mat4 uniform := Object_Uniform {
if first { // transform = linalg.identity(base.Mat4)
transform = linalg.identity(base.Mat4) transform = linalg.matrix4_translate(base.Vec3{x_offset, 0, 0}),
} else {
transform = linalg.matrix4_translate(base.Vec3{3, 0, 0})
}
uniform := Renderable_Uniform {
transform = transform,
} }
// bufferOffset = offset into buffer to start from // bufferOffset = offset into buffer to start from
@@ -391,7 +396,7 @@ create_renderable :: proc(
uniform_buffer, uniform_buffer,
0, 0,
&uniform, &uniform,
size_of(Renderable_Uniform), size_of(Object_Uniform),
) )
entries := [?]wgpu.BindGroupEntry { entries := [?]wgpu.BindGroupEntry {
@@ -399,19 +404,30 @@ create_renderable :: proc(
binding = 0, // slot 0 binding = 0, // slot 0
buffer = uniform_buffer, buffer = uniform_buffer,
offset = 0, // offset into buffer to start from offset = 0, // offset into buffer to start from
size = cast(u64)size_of(Renderable_Uniform), size = cast(u64)size_of(Object_Uniform),
}, },
} }
bind_group := wgpu.DeviceCreateBindGroup( bind_group := wgpu.DeviceCreateBindGroup(
state.device, state.device,
&{ &{
label = "renderable bind group", label = "object bind group",
layout = state.renderable_bind_group_layout, layout = state.objects_bind_group_layout,
entryCount = len(entries), entryCount = len(entries),
entries = raw_data(entries[:]), entries = raw_data(entries[:]),
}, },
) )
append(
&state.objects,
Object {
name = name,
bind_group = bind_group,
uniform_buffer = uniform_buffer,
},
)
if name in state.meshes do return
vertex_buffer := wgpu.DeviceCreateBuffer( vertex_buffer := wgpu.DeviceCreateBuffer(
state.device, state.device,
&{ &{
@@ -446,12 +462,10 @@ create_renderable :: proc(
cast(uint)(len(indices) * size_of(u16)), cast(uint)(len(indices) * size_of(u16)),
) )
state.renderables[name] = { state.meshes[name] = {
bind_group = bind_group, vertex_buffer = vertex_buffer,
uniform_buffer = uniform_buffer, index_buffer = index_buffer,
vertex_buffer = vertex_buffer, index_count = cast(u32)len(indices),
index_buffer = index_buffer,
index_count = cast(u32)len(indices),
} }
} }
@@ -522,7 +536,7 @@ on_device :: proc "c" (
material_struct := to_wgsl_struct(Material_Uniform, "Material") material_struct := to_wgsl_struct(Material_Uniform, "Material")
defer delete(material_struct) defer delete(material_struct)
renderable_struct := to_wgsl_struct(Renderable_Uniform, "Renderable") renderable_struct := to_wgsl_struct(Object_Uniform, "Renderable")
defer delete(renderable_struct) defer delete(renderable_struct)
shader := fmt.tprintf( shader := fmt.tprintf(
@@ -653,11 +667,11 @@ on_device :: proc "c" (
visibility = {.Vertex}, visibility = {.Vertex},
buffer = { buffer = {
type = .Uniform, type = .Uniform,
minBindingSize = cast(u64)size_of(Renderable_Uniform), minBindingSize = cast(u64)size_of(Object_Uniform),
}, },
}, },
} }
state.renderable_bind_group_layout = wgpu.DeviceCreateBindGroupLayout( state.objects_bind_group_layout = wgpu.DeviceCreateBindGroupLayout(
state.device, state.device,
&{ &{
label = "renderable layout", label = "renderable layout",
@@ -690,7 +704,7 @@ on_device :: proc "c" (
group_layouts := [?]wgpu.BindGroupLayout { group_layouts := [?]wgpu.BindGroupLayout {
state.camera_bind_group_layout, state.camera_bind_group_layout,
state.material_bind_group_layout, state.material_bind_group_layout,
state.renderable_bind_group_layout, state.objects_bind_group_layout,
} }
state.pipeline_layout = wgpu.DeviceCreatePipelineLayout( state.pipeline_layout = wgpu.DeviceCreatePipelineLayout(
state.device, state.device,