rootvg_mesh/pipeline/
solid.rs

1use std::ops::Range;
2
3use rootvg_core::{
4    buffer::Buffer,
5    math::{PhysicalSizeI32, ScaleFactor},
6    pipeline::DefaultConstantUniforms,
7};
8use wgpu::PipelineCompilationOptions;
9
10use crate::{SolidMeshPrimitive, SolidVertex2D};
11
12use super::{InstanceUniforms, INITIAL_INDEX_COUNT, INITIAL_VERTEX_COUNT};
13
14struct Instance {
15    range_in_vertex_buffer: Range<u32>,
16    range_in_index_buffer: Range<u32>,
17}
18
19pub struct SolidMeshBatchBuffer {
20    instances: Vec<Instance>,
21    vertex_buffer: Buffer<SolidVertex2D>,
22    index_buffer: Buffer<u32>,
23    instance_uniforms_buffer: Buffer<InstanceUniforms>,
24    instance_uniforms_bind_group: wgpu::BindGroup,
25    temp_vertex_buffer: Vec<SolidVertex2D>,
26    temp_index_buffer: Vec<u32>,
27    temp_instance_uniforms_buffer: Vec<InstanceUniforms>,
28
29    prev_primitives: Vec<SolidMeshPrimitive>,
30}
31
32impl SolidMeshBatchBuffer {
33    pub fn new(device: &wgpu::Device, instance_uniforms_layout: &wgpu::BindGroupLayout) -> Self {
34        let vertex_buffer = Buffer::new(
35            device,
36            "rootvg-mesh solid vertex buffer",
37            INITIAL_VERTEX_COUNT,
38            wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
39        );
40
41        let index_buffer = Buffer::new(
42            device,
43            "rootvg-mesh solid index buffer",
44            INITIAL_INDEX_COUNT,
45            wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
46        );
47
48        let instance_uniforms_buffer = Buffer::new(
49            device,
50            "rootvg-mesh solid uniforms buffer",
51            1,
52            wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
53        );
54
55        let instance_uniforms_bind_group = Self::bind_group(
56            device,
57            &instance_uniforms_buffer.raw,
58            instance_uniforms_layout,
59        );
60
61        Self {
62            instances: Vec::new(),
63            vertex_buffer,
64            index_buffer,
65            instance_uniforms_buffer,
66            instance_uniforms_bind_group,
67            temp_vertex_buffer: Vec::new(),
68            temp_index_buffer: Vec::new(),
69            temp_instance_uniforms_buffer: Vec::new(),
70            prev_primitives: Vec::new(),
71        }
72    }
73
74    fn bind_group(
75        device: &wgpu::Device,
76        buffer: &wgpu::Buffer,
77        layout: &wgpu::BindGroupLayout,
78    ) -> wgpu::BindGroup {
79        device.create_bind_group(&wgpu::BindGroupDescriptor {
80            label: Some("rootvg-mesh solid uniforms bind group"),
81            layout,
82            entries: &[wgpu::BindGroupEntry {
83                binding: 0,
84                resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
85                    buffer,
86                    offset: 0,
87                    size: InstanceUniforms::min_size(),
88                }),
89            }],
90        })
91    }
92
93    pub fn prepare(
94        &mut self,
95        primitives: &[SolidMeshPrimitive],
96        device: &wgpu::Device,
97        queue: &wgpu::Queue,
98        instance_uniforms_layout: &wgpu::BindGroupLayout,
99    ) {
100        // Don't prepare if the list of primitives hasn't changed since the last
101        // preparation.
102        if primitives == &self.prev_primitives {
103            return;
104        }
105        self.prev_primitives = primitives.into();
106
107        // TODO: Detect when multiple primitives share the same mesh and batch them
108        // together into a separate draw call to reduce the amount of duplicated data
109        // sent to the GPU? Testing is needed to see if this will actually improve
110        // performance in practice.
111
112        self.instances.clear();
113        self.temp_index_buffer.clear();
114        self.temp_vertex_buffer.clear();
115        self.temp_instance_uniforms_buffer.clear();
116
117        for mesh in primitives.iter() {
118            let vertex_buffer_start = self.temp_vertex_buffer.len() as u32;
119            let index_buffer_start = self.temp_index_buffer.len() as u32;
120
121            self.temp_vertex_buffer
122                .extend_from_slice(&mesh.mesh.buffers.vertices);
123            self.temp_index_buffer
124                .extend_from_slice(&mesh.mesh.buffers.indices);
125
126            self.instances.push(Instance {
127                range_in_vertex_buffer: vertex_buffer_start..self.temp_vertex_buffer.len() as u32,
128                range_in_index_buffer: index_buffer_start..self.temp_index_buffer.len() as u32,
129            });
130
131            self.temp_instance_uniforms_buffer
132                .push(InstanceUniforms::new(mesh.uniform));
133        }
134
135        let _ = self
136            .vertex_buffer
137            .expand_to_fit_new_size(device, self.temp_vertex_buffer.len());
138        let _ = self
139            .index_buffer
140            .expand_to_fit_new_size(device, self.temp_index_buffer.len());
141
142        let _ = self.vertex_buffer.write(queue, 0, &self.temp_vertex_buffer);
143        let _ = self.index_buffer.write(queue, 0, &self.temp_index_buffer);
144
145        if self
146            .instance_uniforms_buffer
147            .expand_to_fit_new_size(device, self.instances.len())
148        {
149            self.instance_uniforms_bind_group = Self::bind_group(
150                device,
151                &self.instance_uniforms_buffer.raw,
152                instance_uniforms_layout,
153            );
154        }
155
156        let _ = self
157            .instance_uniforms_buffer
158            .write(queue, 0, &self.temp_instance_uniforms_buffer);
159    }
160}
161
162pub struct SolidMeshPipeline {
163    pipeline: wgpu::RenderPipeline,
164
165    constants_buffer: wgpu::Buffer,
166    constants_bind_group: wgpu::BindGroup,
167    instance_uniforms_layout: wgpu::BindGroupLayout,
168
169    screen_size: PhysicalSizeI32,
170    scale_factor: ScaleFactor,
171}
172
173impl SolidMeshPipeline {
174    pub fn new(
175        device: &wgpu::Device,
176        format: wgpu::TextureFormat,
177        multisample: wgpu::MultisampleState,
178    ) -> Self {
179        let (constants_layout, constants_buffer, constants_bind_group) =
180            DefaultConstantUniforms::layout_buffer_and_bind_group(device);
181
182        let instance_uniforms_layout = super::instance_uniforms_layout(device);
183
184        let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
185            label: Some("rootvg-mesh solid pipeline layout"),
186            bind_group_layouts: &[&constants_layout, &instance_uniforms_layout],
187            push_constant_ranges: &[],
188        });
189
190        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
191            label: Some("rootvg-mesh solid shader"),
192            source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(concat!(
193                include_str!("../shader/mesh.wgsl"),
194                "\n",
195                include_str!("../shader/solid.wgsl"),
196            ))),
197        });
198
199        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
200            label: Some("rootvg-mesh solid pipeline"),
201            layout: Some(&layout),
202            vertex: wgpu::VertexState {
203                module: &shader,
204                entry_point: "solid_vs_main",
205                buffers: &[wgpu::VertexBufferLayout {
206                    array_stride: std::mem::size_of::<SolidVertex2D>() as u64,
207                    step_mode: wgpu::VertexStepMode::Vertex,
208                    attributes: &wgpu::vertex_attr_array!(
209                        // Position
210                        0 => Float32x2,
211                        // Color
212                        1 => Float32x4,
213                    ),
214                }],
215                compilation_options: PipelineCompilationOptions::default(),
216            },
217            fragment: Some(wgpu::FragmentState {
218                module: &shader,
219                entry_point: "solid_fs_main",
220                targets: &super::color_target_state(format),
221                compilation_options: PipelineCompilationOptions::default(),
222            }),
223            primitive: wgpu::PrimitiveState {
224                topology: wgpu::PrimitiveTopology::TriangleList,
225                front_face: wgpu::FrontFace::Cw,
226                ..Default::default()
227            },
228            depth_stencil: None,
229            multisample,
230            multiview: None,
231        });
232
233        Self {
234            pipeline,
235            constants_buffer,
236            constants_bind_group,
237            instance_uniforms_layout,
238            screen_size: PhysicalSizeI32::default(),
239            scale_factor: ScaleFactor::default(),
240        }
241    }
242
243    pub fn create_batch(&mut self, device: &wgpu::Device) -> SolidMeshBatchBuffer {
244        SolidMeshBatchBuffer::new(device, &self.instance_uniforms_layout)
245    }
246
247    pub fn start_preparations(
248        &mut self,
249        _device: &wgpu::Device,
250        queue: &wgpu::Queue,
251        screen_size: PhysicalSizeI32,
252        scale_factor: ScaleFactor,
253    ) {
254        if self.screen_size == screen_size && self.scale_factor == scale_factor {
255            return;
256        }
257
258        self.screen_size = screen_size;
259        self.scale_factor = scale_factor;
260
261        DefaultConstantUniforms::prepare_buffer(
262            &self.constants_buffer,
263            screen_size,
264            scale_factor,
265            queue,
266        );
267    }
268
269    pub fn prepare_batch(
270        &mut self,
271        batch: &mut SolidMeshBatchBuffer,
272        primitives: &[SolidMeshPrimitive],
273        device: &wgpu::Device,
274        queue: &wgpu::Queue,
275    ) {
276        batch.prepare(primitives, device, queue, &self.instance_uniforms_layout);
277    }
278
279    pub fn render_batch<'pass>(
280        &'pass self,
281        batch: &'pass SolidMeshBatchBuffer,
282        render_pass: &mut wgpu::RenderPass<'pass>,
283    ) {
284        if batch.instances.is_empty() {
285            return;
286        }
287
288        render_pass.set_pipeline(&self.pipeline);
289        render_pass.set_bind_group(0, &self.constants_bind_group, &[]);
290
291        let vertex_end = batch.instances.last().unwrap().range_in_vertex_buffer.end;
292        let index_end = batch.instances.last().unwrap().range_in_index_buffer.end;
293
294        render_pass.set_vertex_buffer(0, batch.vertex_buffer.slice(0..vertex_end as usize));
295        render_pass.set_index_buffer(
296            batch.index_buffer.slice(0..index_end as usize),
297            wgpu::IndexFormat::Uint32,
298        );
299
300        for (i, instance) in batch.instances.iter().enumerate() {
301            render_pass.set_bind_group(
302                1,
303                &batch.instance_uniforms_bind_group,
304                &[(i * std::mem::size_of::<InstanceUniforms>()) as u32],
305            );
306
307            render_pass.draw_indexed(
308                instance.range_in_index_buffer.start..instance.range_in_index_buffer.end,
309                instance.range_in_vertex_buffer.start as i32,
310                0..1,
311            );
312        }
313    }
314}