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 if primitives == &self.prev_primitives {
103 return;
104 }
105 self.prev_primitives = primitives.into();
106
107 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 0 => Float32x2,
211 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}