Skip to main content

game_toolkit_gfx/
mesh.rs

1//! Instanced static-mesh rendering: a registry of uploaded meshes plus a depth-tested
2//! pipeline. Meshes draw before the 2D layers so the 2D painter (HUD, text) sits on top.
3
4use std::collections::HashMap;
5
6use bytemuck::{Pod, Zeroable};
7use wgpu::util::DeviceExt;
8
9use crate::target::Targets;
10use crate::transform::Mat4;
11
12/// Handle to a mesh uploaded via [`crate::Graphics::create_mesh`].
13#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
14pub struct MeshId(pub u32);
15
16/// One mesh vertex: position and normal, both in model space.
17#[repr(C)]
18#[derive(Copy, Clone, Pod, Zeroable, Debug)]
19pub struct MeshVertex {
20    pub pos: [f32; 3],
21    pub normal: [f32; 3],
22}
23
24impl MeshVertex {
25    pub fn new(pos: [f32; 3], normal: [f32; 3]) -> Self {
26        Self { pos, normal }
27    }
28}
29
30/// One instance of a mesh: a column-major model matrix and a tint color.
31#[repr(C)]
32#[derive(Copy, Clone, Pod, Zeroable, Debug)]
33pub struct MeshInstance {
34    pub model: Mat4,
35    pub color: [f32; 4],
36}
37
38impl MeshInstance {
39    pub fn new(model: Mat4, color: [f32; 4]) -> Self {
40        Self { model, color }
41    }
42}
43
44struct MeshGpu {
45    vertex_buf: wgpu::Buffer,
46    index_buf: wgpu::Buffer,
47    index_count: u32,
48}
49
50/// Owns the GPU buffers for every uploaded mesh.
51pub(crate) struct MeshRegistry {
52    map: HashMap<MeshId, MeshGpu>,
53    next: u32,
54}
55
56impl MeshRegistry {
57    pub fn new() -> Self {
58        Self {
59            map: HashMap::new(),
60            next: 1,
61        }
62    }
63
64    pub fn create(
65        &mut self,
66        device: &wgpu::Device,
67        vertices: &[MeshVertex],
68        indices: &[u16],
69    ) -> MeshId {
70        let vertex_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
71            label: Some("mesh.vertices"),
72            contents: bytemuck::cast_slice(vertices),
73            usage: wgpu::BufferUsages::VERTEX,
74        });
75        let index_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
76            label: Some("mesh.indices"),
77            contents: bytemuck::cast_slice(indices),
78            usage: wgpu::BufferUsages::INDEX,
79        });
80        let id = MeshId(self.next);
81        self.next += 1;
82        self.map.insert(
83            id,
84            MeshGpu {
85                vertex_buf,
86                index_buf,
87                index_count: indices.len() as u32,
88            },
89        );
90        id
91    }
92}
93
94pub(crate) struct MeshBatcher {
95    pipeline: wgpu::RenderPipeline,
96    instance_vb: wgpu::Buffer,
97    capacity: usize,
98    pending: Vec<(MeshId, MeshInstance)>,
99}
100
101impl MeshBatcher {
102    pub fn new(
103        device: &wgpu::Device,
104        surface_format: wgpu::TextureFormat,
105        camera_bgl: &wgpu::BindGroupLayout,
106        sample_count: u32,
107        depth_format: Option<wgpu::TextureFormat>,
108    ) -> Self {
109        let capacity = 256usize;
110        let instance_vb = device.create_buffer(&wgpu::BufferDescriptor {
111            label: Some("mesh.instances"),
112            size: (capacity * size_of::<MeshInstance>()) as u64,
113            usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
114            mapped_at_creation: false,
115        });
116
117        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
118            label: Some("mesh.shader"),
119            source: wgpu::ShaderSource::Wgsl(
120                concat!(include_str!("srgb.wgsl"), include_str!("mesh.wgsl")).into(),
121            ),
122        });
123        let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
124            label: Some("mesh.layout"),
125            bind_group_layouts: &[Some(camera_bgl)],
126            immediate_size: 0,
127        });
128
129        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
130            label: Some("mesh.pipeline"),
131            layout: Some(&layout),
132            vertex: wgpu::VertexState {
133                module: &shader,
134                entry_point: Some("vs_main"),
135                compilation_options: Default::default(),
136                buffers: &[
137                    Some(wgpu::VertexBufferLayout {
138                        array_stride: size_of::<MeshVertex>() as u64,
139                        step_mode: wgpu::VertexStepMode::Vertex,
140                        attributes: &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3],
141                    }),
142                    Some(wgpu::VertexBufferLayout {
143                        array_stride: size_of::<MeshInstance>() as u64,
144                        step_mode: wgpu::VertexStepMode::Instance,
145                        attributes: &wgpu::vertex_attr_array![
146                            2 => Float32x4,
147                            3 => Float32x4,
148                            4 => Float32x4,
149                            5 => Float32x4,
150                            6 => Float32x4,
151                        ],
152                    }),
153                ],
154            },
155            fragment: Some(wgpu::FragmentState {
156                module: &shader,
157                entry_point: Some("fs_main"),
158                compilation_options: Default::default(),
159                targets: &[Some(wgpu::ColorTargetState {
160                    format: surface_format,
161                    blend: Some(wgpu::BlendState::ALPHA_BLENDING),
162                    write_mask: wgpu::ColorWrites::ALL,
163                })],
164            }),
165            // No backface culling: an opaque mesh relies on the depth test to hide its back
166            // faces, which keeps callers from having to match a specific winding order.
167            primitive: wgpu::PrimitiveState::default(),
168            depth_stencil: depth_format.map(crate::target::depth_test),
169            multisample: crate::target::multisample(sample_count),
170            multiview_mask: None,
171            cache: None,
172        });
173
174        Self {
175            pipeline,
176            instance_vb,
177            capacity,
178            pending: Vec::new(),
179        }
180    }
181
182    pub fn push(&mut self, id: MeshId, instance: MeshInstance) {
183        self.pending.push((id, instance));
184    }
185
186    pub fn draw(
187        &mut self,
188        device: &wgpu::Device,
189        queue: &wgpu::Queue,
190        registry: &MeshRegistry,
191        encoder: &mut wgpu::CommandEncoder,
192        targets: &Targets<'_>,
193        camera_bg: &wgpu::BindGroup,
194    ) {
195        if self.pending.is_empty() {
196            return;
197        }
198        // Group instances by mesh so each mesh's vertex/index buffers bind once.
199        self.pending.sort_by_key(|(id, _)| *id);
200        if self.pending.len() > self.capacity {
201            self.capacity = self.pending.len().next_power_of_two();
202            self.instance_vb = device.create_buffer(&wgpu::BufferDescriptor {
203                label: Some("mesh.instances"),
204                size: (self.capacity * size_of::<MeshInstance>()) as u64,
205                usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
206                mapped_at_creation: false,
207            });
208        }
209        let flat: Vec<MeshInstance> = self.pending.iter().map(|(_, i)| *i).collect();
210        queue.write_buffer(&self.instance_vb, 0, bytemuck::cast_slice(&flat));
211
212        let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
213            label: Some("mesh.pass"),
214            color_attachments: &[Some(targets.color_attachment(wgpu::LoadOp::Load))],
215            depth_stencil_attachment: targets.depth_attachment(wgpu::LoadOp::Load),
216            occlusion_query_set: None,
217            timestamp_writes: None,
218            multiview_mask: None,
219        });
220        pass.set_pipeline(&self.pipeline);
221        pass.set_bind_group(0, camera_bg, &[]);
222        pass.set_vertex_buffer(1, self.instance_vb.slice(..));
223
224        let mut i = 0;
225        while i < self.pending.len() {
226            let id = self.pending[i].0;
227            let start = i;
228            while i < self.pending.len() && self.pending[i].0 == id {
229                i += 1;
230            }
231            let Some(mesh) = registry.map.get(&id) else {
232                continue;
233            };
234            pass.set_vertex_buffer(0, mesh.vertex_buf.slice(..));
235            pass.set_index_buffer(mesh.index_buf.slice(..), wgpu::IndexFormat::Uint16);
236            pass.draw_indexed(0..mesh.index_count, 0, (start as u32)..(i as u32));
237        }
238    }
239
240    pub fn clear(&mut self) {
241        self.pending.clear();
242    }
243}