1use crate::resources::*;
2
3#[derive(Default)]
7pub(crate) struct InstancingResources {
8 pub(crate) bind_group_layout: Option<wgpu::BindGroupLayout>,
10 pub(crate) storage_buf: Option<wgpu::Buffer>,
12 pub(crate) storage_capacity: usize,
14 pub(crate) bind_groups: std::collections::HashMap<(u64, u64, u64), wgpu::BindGroup>,
21 pub(crate) solid_pipeline: Option<wgpu::RenderPipeline>,
23 pub(crate) solid_two_sided_pipeline: Option<wgpu::RenderPipeline>,
26 pub(crate) transparent_pipeline: Option<wgpu::RenderPipeline>,
28 pub(crate) shadow_pipeline: Option<wgpu::RenderPipeline>,
30 pub(crate) shadow_two_sided_pipeline: Option<wgpu::RenderPipeline>,
33 pub(crate) shadow_cascade_bufs: [Option<wgpu::Buffer>; 4],
35 pub(crate) shadow_cascade_bgs: [Option<wgpu::BindGroup>; 4],
37 pub(crate) hdr_solid_pipeline: Option<wgpu::RenderPipeline>,
39 pub(crate) hdr_solid_two_sided_pipeline: Option<wgpu::RenderPipeline>,
42 pub(crate) hdr_transparent_pipeline: Option<wgpu::RenderPipeline>,
43 pub(crate) hdr_additive_pipeline: Option<wgpu::RenderPipeline>,
46 pub(crate) hdr_premultiplied_pipeline: Option<wgpu::RenderPipeline>,
49}
50
51#[derive(Default)]
56pub(crate) struct CullResources {
57 pub(crate) aabb_buf: Option<wgpu::Buffer>,
59 pub(crate) aabb_capacity: usize,
60 pub(crate) batch_meta_buf: Option<wgpu::Buffer>,
62 pub(crate) batch_meta_capacity: usize,
63 pub(crate) bind_group_layout: Option<wgpu::BindGroupLayout>,
66 pub(crate) hdr_solid_pipeline: Option<wgpu::RenderPipeline>,
68 pub(crate) hdr_solid_two_sided_pipeline: Option<wgpu::RenderPipeline>,
71 pub(crate) oit_pipeline: Option<wgpu::RenderPipeline>,
73 pub(crate) shadow_pipeline: Option<wgpu::RenderPipeline>,
75 pub(crate) shadow_two_sided_pipeline: Option<wgpu::RenderPipeline>,
78 pub(crate) shadow_bgl: Option<wgpu::BindGroupLayout>,
80}
81
82impl DeviceResources {
83 pub(crate) fn ensure_instanced_pipelines(&mut self, device: &wgpu::Device) {
86 if self.instancing.bind_group_layout.is_some() {
87 return; }
89
90 let instance_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
95 label: Some("instance_bgl"),
96 entries: &[
97 wgpu::BindGroupLayoutEntry {
99 binding: 0,
100 visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
101 ty: wgpu::BindingType::Buffer {
102 ty: wgpu::BufferBindingType::Storage { read_only: true },
103 has_dynamic_offset: false,
104 min_binding_size: None,
105 },
106 count: None,
107 },
108 wgpu::BindGroupLayoutEntry {
110 binding: 1,
111 visibility: wgpu::ShaderStages::FRAGMENT,
112 ty: wgpu::BindingType::Texture {
113 sample_type: wgpu::TextureSampleType::Float { filterable: true },
114 view_dimension: wgpu::TextureViewDimension::D2,
115 multisampled: false,
116 },
117 count: None,
118 },
119 wgpu::BindGroupLayoutEntry {
121 binding: 2,
122 visibility: wgpu::ShaderStages::FRAGMENT,
123 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
124 count: None,
125 },
126 wgpu::BindGroupLayoutEntry {
128 binding: 3,
129 visibility: wgpu::ShaderStages::FRAGMENT,
130 ty: wgpu::BindingType::Texture {
131 sample_type: wgpu::TextureSampleType::Float { filterable: true },
132 view_dimension: wgpu::TextureViewDimension::D2,
133 multisampled: false,
134 },
135 count: None,
136 },
137 wgpu::BindGroupLayoutEntry {
139 binding: 4,
140 visibility: wgpu::ShaderStages::FRAGMENT,
141 ty: wgpu::BindingType::Texture {
142 sample_type: wgpu::TextureSampleType::Float { filterable: true },
143 view_dimension: wgpu::TextureViewDimension::D2,
144 multisampled: false,
145 },
146 count: None,
147 },
148 ],
149 });
150
151 let instanced_shader = {
153 let base = include_str!(concat!(env!("OUT_DIR"), "/mesh_instanced.wgsl"));
154 let composed = crate::resources::mesh_sidecar::registry::compose_shader(
155 base,
156 &self.deform.registrations,
157 );
158 crate::resources::builders::wgsl_module(device, "mesh_instanced_shader", composed)
159 };
160
161 let instanced_layout = crate::resources::mesh::mesh_pipelines::instanced_pipeline_layout(
162 device,
163 "instanced_pipeline_layout",
164 &self.camera_bind_group_layout,
165 &instance_bgl,
166 self.deform
167 .enabled
168 .then_some(&self.deform.bind_group_layout),
169 );
170 let ldr_inst = crate::resources::mesh::mesh_pipelines::build_ldr_instanced_mesh_pipelines(
171 device,
172 &instanced_layout,
173 &instanced_shader,
174 self.target_format,
175 self.sample_count,
176 );
177 let solid_instanced = ldr_inst.solid;
178 let solid_two_sided_instanced = ldr_inst.solid_two_sided;
179 let transparent_instanced = ldr_inst.transparent;
180
181 let shadow_instanced_shader = crate::resources::builders::wgsl_module(
183 device,
184 "shadow_instanced_shader",
185 crate::resources::builders::wgsl_source!("shadow_instanced"),
186 );
187
188 let shadow_bgl = crate::resources::builders::uniform_bgl(
191 device,
192 "shadow_bgl_for_instanced",
193 wgpu::ShaderStages::VERTEX,
194 );
195
196 let shadow_instanced_layout =
197 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
198 label: Some("shadow_instanced_pipeline_layout"),
199 bind_group_layouts: &[&shadow_bgl, &instance_bgl],
200 push_constant_ranges: &[],
201 });
202
203 let make_shadow_instanced =
208 |label: &str, cull_mode: Option<wgpu::Face>, bias: wgpu::DepthBiasState| {
209 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
210 label: Some(label),
211 layout: Some(&shadow_instanced_layout),
212 vertex: wgpu::VertexState {
213 module: &shadow_instanced_shader,
214 entry_point: Some("vs_main"),
215 buffers: &[Vertex::buffer_layout()],
216 compilation_options: wgpu::PipelineCompilationOptions::default(),
217 },
218 fragment: None,
219 primitive: wgpu::PrimitiveState {
220 topology: wgpu::PrimitiveTopology::TriangleList,
221 cull_mode,
222 ..Default::default()
223 },
224 depth_stencil: Some(wgpu::DepthStencilState {
225 format: wgpu::TextureFormat::Depth32Float,
226 depth_write_enabled: true,
227 depth_compare: wgpu::CompareFunction::Less,
228 stencil: wgpu::StencilState::default(),
229 bias,
230 }),
231 multisample: wgpu::MultisampleState::default(),
232 multiview: None,
233 cache: None,
234 })
235 };
236 let shadow_instanced = make_shadow_instanced(
237 "shadow_instanced_pipeline",
238 Some(wgpu::Face::Front),
239 crate::resources::mesh::mesh_pipelines::CSM_SHADOW_BIAS,
240 );
241 let shadow_instanced_two_sided = make_shadow_instanced(
242 "shadow_instanced_two_sided_pipeline",
243 None,
244 crate::resources::mesh::mesh_pipelines::CSM_SHADOW_BIAS_TWO_SIDED,
245 );
246
247 let cascade_bufs: [wgpu::Buffer; 4] = std::array::from_fn(|i| {
252 device.create_buffer(&wgpu::BufferDescriptor {
253 label: Some(&format!("shadow_instanced_cascade_buf_{i}")),
254 size: 64, usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
256 mapped_at_creation: false,
257 })
258 });
259 let cascade_bgs: [wgpu::BindGroup; 4] = std::array::from_fn(|i| {
260 device.create_bind_group(&wgpu::BindGroupDescriptor {
261 label: Some(&format!("shadow_instanced_cascade_bg_{i}")),
262 layout: &shadow_bgl,
263 entries: &[wgpu::BindGroupEntry {
264 binding: 0,
265 resource: cascade_bufs[i].as_entire_binding(),
266 }],
267 })
268 });
269 self.instancing.shadow_cascade_bufs = cascade_bufs.map(Some);
270 self.instancing.shadow_cascade_bgs = cascade_bgs.map(Some);
271
272 self.instancing.bind_group_layout = Some(instance_bgl);
273 self.instancing.solid_pipeline = Some(solid_instanced);
274 self.instancing.solid_two_sided_pipeline = Some(solid_two_sided_instanced);
275 self.instancing.transparent_pipeline = Some(transparent_instanced);
276 self.instancing.shadow_pipeline = Some(shadow_instanced);
277 self.instancing.shadow_two_sided_pipeline = Some(shadow_instanced_two_sided);
278 }
279
280 pub(crate) fn ensure_hdr_instanced_pipelines(&mut self, device: &wgpu::Device) {
285 if self.instancing.hdr_solid_pipeline.is_some() {
286 return;
287 }
288 let Some(ref instance_bgl) = self.instancing.bind_group_layout else {
289 return;
290 };
291
292 let inst_shader = {
293 let base = include_str!(concat!(env!("OUT_DIR"), "/mesh_instanced.wgsl"));
294 let composed = crate::resources::mesh_sidecar::registry::compose_shader(
295 base,
296 &self.deform.registrations,
297 );
298 crate::resources::builders::wgsl_module(device, "mesh_instanced_shader_hdr", composed)
299 };
300 let inst_layout = crate::resources::mesh::mesh_pipelines::instanced_pipeline_layout(
301 device,
302 "hdr_instanced_pipeline_layout",
303 &self.camera_bind_group_layout,
304 instance_bgl,
305 self.deform
306 .enabled
307 .then_some(&self.deform.bind_group_layout),
308 );
309 let hdr_inst = crate::resources::mesh::mesh_pipelines::build_hdr_instanced_mesh_pipelines(
310 device,
311 &inst_layout,
312 &inst_shader,
313 );
314 self.instancing.hdr_solid_pipeline = Some(hdr_inst.solid);
315 self.instancing.hdr_solid_two_sided_pipeline = Some(hdr_inst.solid_two_sided);
316 self.instancing.hdr_transparent_pipeline = Some(hdr_inst.transparent);
317 self.instancing.hdr_additive_pipeline = Some(hdr_inst.additive);
318 self.instancing.hdr_premultiplied_pipeline = Some(hdr_inst.premultiplied);
319 }
320
321 pub(crate) fn ensure_oit_instanced_pipeline(&mut self, device: &wgpu::Device) {
326 if self.oit.instanced_pipeline.is_some() {
327 return;
328 }
329 let Some(ref instance_bgl) = self.instancing.bind_group_layout else {
330 return;
331 };
332
333 let instanced_oit_shader = {
334 let base = include_str!(concat!(env!("OUT_DIR"), "/mesh_instanced_oit.wgsl"));
335 let composed = crate::resources::mesh_sidecar::registry::compose_shader(
336 base,
337 &self.deform.registrations,
338 );
339 crate::resources::builders::wgsl_module(device, "mesh_instanced_oit_shader", composed)
340 };
341 let instanced_oit_layout =
342 crate::resources::mesh::mesh_pipelines::instanced_pipeline_layout(
343 device,
344 "oit_instanced_pipeline_layout",
345 &self.camera_bind_group_layout,
346 instance_bgl,
347 self.deform
348 .enabled
349 .then_some(&self.deform.bind_group_layout),
350 );
351 let pipeline = crate::resources::mesh::mesh_pipelines::build_oit_instanced_pipeline(
352 device,
353 &instanced_oit_layout,
354 &instanced_oit_shader,
355 "oit_instanced_pipeline",
356 "vs_main",
357 );
358
359 self.oit.instanced_pipeline = Some(pipeline);
360 }
361
362 pub(crate) fn upload_instance_data(
365 &mut self,
366 device: &wgpu::Device,
367 queue: &wgpu::Queue,
368 data: &[InstanceData],
369 ) {
370 if data.is_empty() {
371 return;
372 }
373
374 let _bgl = self
375 .instancing
376 .bind_group_layout
377 .as_ref()
378 .expect("ensure_instanced_pipelines must be called first");
379
380 let max_instances = (device.limits().max_storage_buffer_binding_size as usize)
383 / std::mem::size_of::<InstanceData>();
384 let data = &data[..data.len().min(max_instances)];
385
386 let needed = data.len();
387 if needed > self.instancing.storage_capacity {
388 let new_cap = (needed * 2).max(64).min(max_instances);
390 let buf_size = (new_cap * std::mem::size_of::<InstanceData>()) as u64;
391 self.instancing.storage_buf = Some(device.create_buffer(&wgpu::BufferDescriptor {
392 label: Some("instance_storage_buf"),
393 size: buf_size,
394 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
395 mapped_at_creation: false,
396 }));
397 self.instancing.storage_capacity = new_cap;
398
399 self.instancing.bind_groups.clear();
401 }
402
403 queue.write_buffer(
404 self.instancing.storage_buf.as_ref().unwrap(),
405 0,
406 bytemuck::cast_slice(data),
407 );
408 }
409
410 pub(crate) fn upload_cull_inputs(
418 &mut self,
419 device: &wgpu::Device,
420 queue: &wgpu::Queue,
421 aabbs: &[crate::resources::types::InstanceAabb],
422 metas: &[crate::resources::types::BatchMeta],
423 ) {
424 let max_instances = (device.limits().max_storage_buffer_binding_size as usize)
426 / std::mem::size_of::<crate::resources::types::InstanceAabb>();
427 let aabbs = &aabbs[..aabbs.len().min(max_instances)];
428
429 if aabbs.len() > self.cull.aabb_capacity {
430 let new_cap = (aabbs.len() * 2).max(64).min(max_instances);
431 self.cull.aabb_buf = Some(device.create_buffer(&wgpu::BufferDescriptor {
432 label: Some("instance_aabb_buf"),
433 size: (new_cap * std::mem::size_of::<crate::resources::types::InstanceAabb>())
434 as u64,
435 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
436 mapped_at_creation: false,
437 }));
438 self.cull.aabb_capacity = new_cap;
439 }
440 if !aabbs.is_empty() {
441 queue.write_buffer(
442 self.cull.aabb_buf.as_ref().unwrap(),
443 0,
444 bytemuck::cast_slice(aabbs),
445 );
446 }
447
448 let max_batches = (device.limits().max_storage_buffer_binding_size as usize)
450 / std::mem::size_of::<crate::resources::types::BatchMeta>();
451 let metas = &metas[..metas.len().min(max_batches)];
452 let batch_count = metas.len();
453
454 if batch_count > self.cull.batch_meta_capacity {
455 let new_cap = (batch_count * 2).max(16).min(max_batches);
456 let meta_size =
457 (new_cap * std::mem::size_of::<crate::resources::types::BatchMeta>()) as u64;
458 self.cull.batch_meta_buf = Some(device.create_buffer(&wgpu::BufferDescriptor {
459 label: Some("batch_meta_buf"),
460 size: meta_size,
461 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
462 mapped_at_creation: false,
463 }));
464 self.cull.batch_meta_capacity = new_cap;
465 }
466
467 if !metas.is_empty() {
468 queue.write_buffer(
469 self.cull.batch_meta_buf.as_ref().unwrap(),
470 0,
471 bytemuck::cast_slice(metas),
472 );
473 }
474 }
475
476 pub(crate) fn ensure_cull_instance_pipelines(&mut self, device: &wgpu::Device) {
480 if self.cull.bind_group_layout.is_some() {
481 return;
482 }
483
484 let Some(ref _instance_bgl) = self.instancing.bind_group_layout else {
485 return; };
487
488 let cull_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
490 label: Some("instance_cull_bgl"),
491 entries: &[
492 wgpu::BindGroupLayoutEntry {
493 binding: 0,
494 visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
495 ty: wgpu::BindingType::Buffer {
496 ty: wgpu::BufferBindingType::Storage { read_only: true },
497 has_dynamic_offset: false,
498 min_binding_size: None,
499 },
500 count: None,
501 },
502 wgpu::BindGroupLayoutEntry {
503 binding: 1,
504 visibility: wgpu::ShaderStages::FRAGMENT,
505 ty: wgpu::BindingType::Texture {
506 sample_type: wgpu::TextureSampleType::Float { filterable: true },
507 view_dimension: wgpu::TextureViewDimension::D2,
508 multisampled: false,
509 },
510 count: None,
511 },
512 wgpu::BindGroupLayoutEntry {
513 binding: 2,
514 visibility: wgpu::ShaderStages::FRAGMENT,
515 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
516 count: None,
517 },
518 wgpu::BindGroupLayoutEntry {
519 binding: 3,
520 visibility: wgpu::ShaderStages::FRAGMENT,
521 ty: wgpu::BindingType::Texture {
522 sample_type: wgpu::TextureSampleType::Float { filterable: true },
523 view_dimension: wgpu::TextureViewDimension::D2,
524 multisampled: false,
525 },
526 count: None,
527 },
528 wgpu::BindGroupLayoutEntry {
529 binding: 4,
530 visibility: wgpu::ShaderStages::FRAGMENT,
531 ty: wgpu::BindingType::Texture {
532 sample_type: wgpu::TextureSampleType::Float { filterable: true },
533 view_dimension: wgpu::TextureViewDimension::D2,
534 multisampled: false,
535 },
536 count: None,
537 },
538 wgpu::BindGroupLayoutEntry {
540 binding: 5,
541 visibility: wgpu::ShaderStages::VERTEX,
542 ty: wgpu::BindingType::Buffer {
543 ty: wgpu::BufferBindingType::Storage { read_only: true },
544 has_dynamic_offset: false,
545 min_binding_size: None,
546 },
547 count: None,
548 },
549 ],
550 });
551
552 let instanced_shader = {
554 let base = include_str!(concat!(env!("OUT_DIR"), "/mesh_instanced.wgsl"));
555 let composed = crate::resources::mesh_sidecar::registry::compose_shader(
556 base,
557 &self.deform.registrations,
558 );
559 crate::resources::builders::wgsl_module(device, "mesh_instanced_shader_cull", composed)
560 };
561 let inst_cull_layout = crate::resources::mesh::mesh_pipelines::instanced_pipeline_layout(
562 device,
563 "hdr_instanced_cull_pipeline_layout",
564 &self.camera_bind_group_layout,
565 &cull_bgl,
566 self.deform
567 .enabled
568 .then_some(&self.deform.bind_group_layout),
569 );
570 let hdr_solid_cull =
571 crate::resources::mesh::mesh_pipelines::build_hdr_instanced_cull_pipeline(
572 device,
573 &inst_cull_layout,
574 &instanced_shader,
575 );
576 let hdr_solid_cull_two_sided =
577 crate::resources::mesh::mesh_pipelines::build_hdr_instanced_cull_two_sided_pipeline(
578 device,
579 &inst_cull_layout,
580 &instanced_shader,
581 );
582
583 let oit_shader = {
585 let base = include_str!(concat!(env!("OUT_DIR"), "/mesh_instanced_oit.wgsl"));
586 let composed = crate::resources::mesh_sidecar::registry::compose_shader(
587 base,
588 &self.deform.registrations,
589 );
590 crate::resources::builders::wgsl_module(
591 device,
592 "mesh_instanced_oit_shader_cull",
593 composed,
594 )
595 };
596 let oit_cull_layout = crate::resources::mesh::mesh_pipelines::instanced_pipeline_layout(
597 device,
598 "oit_instanced_cull_pipeline_layout",
599 &self.camera_bind_group_layout,
600 &cull_bgl,
601 self.deform
602 .enabled
603 .then_some(&self.deform.bind_group_layout),
604 );
605 let oit_cull = crate::resources::mesh::mesh_pipelines::build_oit_instanced_pipeline(
606 device,
607 &oit_cull_layout,
608 &oit_shader,
609 "oit_instanced_cull_pipeline",
610 "vs_main_cull",
611 );
612
613 self.cull.bind_group_layout = Some(cull_bgl);
614 self.cull.hdr_solid_pipeline = Some(hdr_solid_cull);
615 self.cull.hdr_solid_two_sided_pipeline = Some(hdr_solid_cull_two_sided);
616 self.cull.oit_pipeline = Some(oit_cull);
617
618 let shadow_cull_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
622 label: Some("shadow_cull_instance_bgl"),
623 entries: &[
624 wgpu::BindGroupLayoutEntry {
625 binding: 0,
626 visibility: wgpu::ShaderStages::VERTEX,
627 ty: wgpu::BindingType::Buffer {
628 ty: wgpu::BufferBindingType::Storage { read_only: true },
629 has_dynamic_offset: false,
630 min_binding_size: None,
631 },
632 count: None,
633 },
634 wgpu::BindGroupLayoutEntry {
635 binding: 5,
636 visibility: wgpu::ShaderStages::VERTEX,
637 ty: wgpu::BindingType::Buffer {
638 ty: wgpu::BufferBindingType::Storage { read_only: true },
639 has_dynamic_offset: false,
640 min_binding_size: None,
641 },
642 count: None,
643 },
644 ],
645 });
646 let shadow_bgl_for_cull = crate::resources::builders::uniform_bgl(
648 device,
649 "shadow_bgl_for_cull",
650 wgpu::ShaderStages::VERTEX,
651 );
652 let shadow_cull_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
653 label: Some("shadow_instanced_cull_pipeline_layout"),
654 bind_group_layouts: &[&shadow_bgl_for_cull, &shadow_cull_bgl],
655 push_constant_ranges: &[],
656 });
657 let shadow_cull_shader = crate::resources::builders::wgsl_module(
658 device,
659 "shadow_instanced_cull_shader",
660 crate::resources::builders::wgsl_source!("shadow_instanced"),
661 );
662 let make_shadow_cull =
665 |label: &str, cull_mode: Option<wgpu::Face>, bias: wgpu::DepthBiasState| {
666 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
667 label: Some(label),
668 layout: Some(&shadow_cull_layout),
669 vertex: wgpu::VertexState {
670 module: &shadow_cull_shader,
671 entry_point: Some("vs_shadow_cull"),
672 buffers: &[Vertex::buffer_layout()],
673 compilation_options: wgpu::PipelineCompilationOptions::default(),
674 },
675 fragment: None,
676 primitive: wgpu::PrimitiveState {
677 topology: wgpu::PrimitiveTopology::TriangleList,
678 cull_mode,
679 ..Default::default()
680 },
681 depth_stencil: Some(wgpu::DepthStencilState {
682 format: wgpu::TextureFormat::Depth32Float,
683 depth_write_enabled: true,
684 depth_compare: wgpu::CompareFunction::Less,
685 stencil: wgpu::StencilState::default(),
686 bias,
687 }),
688 multisample: wgpu::MultisampleState::default(),
689 multiview: None,
690 cache: None,
691 })
692 };
693 let shadow_instanced_cull = make_shadow_cull(
694 "shadow_instanced_cull_pipeline",
695 Some(wgpu::Face::Front),
696 crate::resources::mesh::mesh_pipelines::CSM_SHADOW_BIAS,
697 );
698 let shadow_instanced_cull_two_sided = make_shadow_cull(
699 "shadow_instanced_cull_two_sided_pipeline",
700 None,
701 crate::resources::mesh::mesh_pipelines::CSM_SHADOW_BIAS_TWO_SIDED,
702 );
703 self.cull.shadow_pipeline = Some(shadow_instanced_cull);
704 self.cull.shadow_two_sided_pipeline = Some(shadow_instanced_cull_two_sided);
705 self.cull.shadow_bgl = Some(shadow_cull_bgl);
706 }
707
708 pub(crate) fn get_shadow_cull_instance_bind_group<'a>(
713 &self,
714 shadow_cull: &'a mut crate::resources::ShadowCullState,
715 device: &wgpu::Device,
716 cascade_idx: usize,
717 ) -> Option<&'a wgpu::BindGroup> {
718 if shadow_cull.shadow_cull_instance_bgs[cascade_idx].is_none() {
719 let bgl = self.cull.shadow_bgl.as_ref()?;
720 let inst_buf = self.instancing.storage_buf.as_ref()?;
721 let vis_buf = shadow_cull.shadow_vis_bufs[cascade_idx].as_ref()?;
722 let bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
723 label: Some(&format!("shadow_cull_instance_bg_{cascade_idx}")),
724 layout: bgl,
725 entries: &[
726 wgpu::BindGroupEntry {
727 binding: 0,
728 resource: inst_buf.as_entire_binding(),
729 },
730 wgpu::BindGroupEntry {
731 binding: 5,
732 resource: vis_buf.as_entire_binding(),
733 },
734 ],
735 });
736 shadow_cull.shadow_cull_instance_bgs[cascade_idx] = Some(bg);
737 }
738 shadow_cull.shadow_cull_instance_bgs[cascade_idx].as_ref()
739 }
740
741 pub(crate) fn get_instance_cull_bind_group<'a>(
746 &self,
747 cull_state: &'a mut crate::resources::ViewportCullState,
748 device: &wgpu::Device,
749 albedo_id: Option<crate::resources::TextureId>,
750 normal_map_id: Option<crate::resources::TextureId>,
751 ao_map_id: Option<crate::resources::TextureId>,
752 ) -> Option<&'a wgpu::BindGroup> {
753 let key = (
754 albedo_id.map(|t| t.raw()).unwrap_or(u64::MAX),
755 normal_map_id.map(|t| t.raw()).unwrap_or(u64::MAX),
756 ao_map_id.map(|t| t.raw()).unwrap_or(u64::MAX),
757 );
758
759 if !cull_state.instance_cull_bind_groups.contains_key(&key) {
760 let bgl = self.cull.bind_group_layout.as_ref()?;
761 let inst_buf = self.instancing.storage_buf.as_ref()?;
762 let vis_buf = cull_state.visibility_index_buf.as_ref()?;
763
764 let albedo_view = match albedo_id {
765 Some(id) if self.content.textures.get(id).is_some() => {
766 &self.content.textures.get(id).unwrap().view
767 }
768 _ => &self.fallback_texture.view,
769 };
770 let normal_view = match normal_map_id {
771 Some(id) if self.content.textures.get(id).is_some() => {
772 &self.content.textures.get(id).unwrap().view
773 }
774 _ => &self.fallback_normal_map_view,
775 };
776 let ao_view = match ao_map_id {
777 Some(id) if self.content.textures.get(id).is_some() => {
778 &self.content.textures.get(id).unwrap().view
779 }
780 _ => &self.fallback_ao_map_view,
781 };
782
783 let bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
784 label: Some("instance_cull_tex_bg"),
785 layout: bgl,
786 entries: &[
787 wgpu::BindGroupEntry {
788 binding: 0,
789 resource: inst_buf.as_entire_binding(),
790 },
791 wgpu::BindGroupEntry {
792 binding: 1,
793 resource: wgpu::BindingResource::TextureView(albedo_view),
794 },
795 wgpu::BindGroupEntry {
796 binding: 2,
797 resource: wgpu::BindingResource::Sampler(&self.material_sampler),
798 },
799 wgpu::BindGroupEntry {
800 binding: 3,
801 resource: wgpu::BindingResource::TextureView(normal_view),
802 },
803 wgpu::BindGroupEntry {
804 binding: 4,
805 resource: wgpu::BindingResource::TextureView(ao_view),
806 },
807 wgpu::BindGroupEntry {
808 binding: 5,
809 resource: vis_buf.as_entire_binding(),
810 },
811 ],
812 });
813 cull_state.instance_cull_bind_groups.insert(key, bg);
814 }
815
816 cull_state.instance_cull_bind_groups.get(&key)
817 }
818
819 pub(crate) fn get_instance_bind_group(
826 &mut self,
827 device: &wgpu::Device,
828 albedo_id: Option<crate::resources::TextureId>,
829 normal_map_id: Option<crate::resources::TextureId>,
830 ao_map_id: Option<crate::resources::TextureId>,
831 ) -> Option<&wgpu::BindGroup> {
832 let key = (
833 albedo_id.map(|t| t.raw()).unwrap_or(u64::MAX),
834 normal_map_id.map(|t| t.raw()).unwrap_or(u64::MAX),
835 ao_map_id.map(|t| t.raw()).unwrap_or(u64::MAX),
836 );
837
838 if !self.instancing.bind_groups.contains_key(&key) {
839 let bgl = self.instancing.bind_group_layout.as_ref()?;
840 let buf = self.instancing.storage_buf.as_ref()?;
841
842 let albedo_view = match albedo_id {
843 Some(id) if self.content.textures.get(id).is_some() => {
844 &self.content.textures.get(id).unwrap().view
845 }
846 _ => &self.fallback_texture.view,
847 };
848 let normal_view = match normal_map_id {
849 Some(id) if self.content.textures.get(id).is_some() => {
850 &self.content.textures.get(id).unwrap().view
851 }
852 _ => &self.fallback_normal_map_view,
853 };
854 let ao_view = match ao_map_id {
855 Some(id) if self.content.textures.get(id).is_some() => {
856 &self.content.textures.get(id).unwrap().view
857 }
858 _ => &self.fallback_ao_map_view,
859 };
860
861 let bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
862 label: Some("instance_tex_bg"),
863 layout: bgl,
864 entries: &[
865 wgpu::BindGroupEntry {
866 binding: 0,
867 resource: buf.as_entire_binding(),
868 },
869 wgpu::BindGroupEntry {
870 binding: 1,
871 resource: wgpu::BindingResource::TextureView(albedo_view),
872 },
873 wgpu::BindGroupEntry {
874 binding: 2,
875 resource: wgpu::BindingResource::Sampler(&self.material_sampler),
876 },
877 wgpu::BindGroupEntry {
878 binding: 3,
879 resource: wgpu::BindingResource::TextureView(normal_view),
880 },
881 wgpu::BindGroupEntry {
882 binding: 4,
883 resource: wgpu::BindingResource::TextureView(ao_view),
884 },
885 ],
886 });
887 self.instancing.bind_groups.insert(key, bg);
888 }
889
890 self.instancing.bind_groups.get(&key)
891 }
892
893 pub(crate) fn upload_mesh_instance(
905 &mut self,
906 device: &wgpu::Device,
907 queue: &wgpu::Queue,
908 item: &crate::renderer::MeshInstanceItem,
909 ) -> Option<crate::resources::types::MeshInstanceGpuData> {
910 self.upload_mesh_instance_from(device, queue, item, item.mesh_id, None)
911 }
912
913 pub(crate) fn upload_mesh_instance_from(
919 &mut self,
920 device: &wgpu::Device,
921 queue: &wgpu::Queue,
922 item: &crate::renderer::MeshInstanceItem,
923 mesh_id: crate::resources::mesh::mesh_store::MeshId,
924 indices: Option<&[u32]>,
925 ) -> Option<crate::resources::types::MeshInstanceGpuData> {
926 let instance_count = match indices {
927 Some(idx) => idx.len() as u32,
928 None => item.transforms.len() as u32,
929 };
930 if instance_count == 0 {
931 return None;
932 }
933
934 #[repr(C)]
937 #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
938 struct GpuInstanceData {
939 model: [[f32; 4]; 4],
940 colour: [f32; 4],
941 selected: u32,
942 wireframe: u32,
943 ambient: f32,
944 diffuse: f32,
945 specular: f32,
946 shininess: f32,
947 has_texture: u32,
948 use_pbr: u32,
949 metallic: f32,
950 roughness: f32,
951 has_normal_map: u32,
952 has_ao_map: u32,
953 unlit: u32,
954 receive_shadows: u32,
955 use_flat: u32,
956 _pad_inst1: u32,
957 uv_transform: [f32; 4],
958 ao_range: [f32; 2],
959 _pad_ao_range: [f32; 2],
960 }
961
962 const _: () = assert!(std::mem::size_of::<GpuInstanceData>() == 176);
963
964 let has_texture = if item
965 .texture_id
966 .is_some_and(|id| self.content.textures.get(id).is_some())
967 {
968 1u32
969 } else {
970 0u32
971 };
972
973 let build = |i: usize| -> GpuInstanceData {
974 GpuInstanceData {
975 model: item.transforms[i],
976 colour: item.colours.get(i).copied().unwrap_or([1.0, 1.0, 1.0, 1.0]),
977 selected: 0,
978 wireframe: 0,
979 ambient: 1.0,
980 diffuse: 0.0,
981 specular: 0.0,
982 shininess: 0.0,
983 has_texture,
984 use_pbr: 0,
985 metallic: 0.0,
986 roughness: 0.0,
987 has_normal_map: 0,
988 has_ao_map: 0,
989 unlit: 1,
990 receive_shadows: 0,
991 use_flat: 1,
992 _pad_inst1: 0,
993 uv_transform: [0.0, 0.0, 1.0, 1.0],
994 ao_range: [0.0, 1.0],
995 _pad_ao_range: [0.0, 0.0],
996 }
997 };
998 let instances: Vec<GpuInstanceData> = match indices {
999 Some(idx) => idx.iter().map(|&i| build(i as usize)).collect(),
1000 None => (0..item.transforms.len()).map(build).collect(),
1001 };
1002
1003 let instance_bytes = bytemuck::cast_slice(&instances);
1004 let instance_buf = device.create_buffer(&wgpu::BufferDescriptor {
1005 label: Some("mesh_instance_buf"),
1006 size: instance_bytes
1007 .len()
1008 .max(std::mem::size_of::<GpuInstanceData>()) as u64,
1009 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
1010 mapped_at_creation: false,
1011 });
1012 queue.write_buffer(&instance_buf, 0, instance_bytes);
1013
1014 let bgl = self.instancing.bind_group_layout.as_ref()?;
1015 let albedo_view = match item.texture_id {
1016 Some(id) if self.content.textures.get(id).is_some() => {
1017 &self.content.textures.get(id).unwrap().view
1018 }
1019 _ => &self.fallback_texture.view,
1020 };
1021 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1022 label: Some("mesh_instance_bg"),
1023 layout: bgl,
1024 entries: &[
1025 wgpu::BindGroupEntry {
1026 binding: 0,
1027 resource: instance_buf.as_entire_binding(),
1028 },
1029 wgpu::BindGroupEntry {
1030 binding: 1,
1031 resource: wgpu::BindingResource::TextureView(albedo_view),
1032 },
1033 wgpu::BindGroupEntry {
1034 binding: 2,
1035 resource: wgpu::BindingResource::Sampler(&self.material_sampler),
1036 },
1037 wgpu::BindGroupEntry {
1038 binding: 3,
1039 resource: wgpu::BindingResource::TextureView(&self.fallback_normal_map_view),
1040 },
1041 wgpu::BindGroupEntry {
1042 binding: 4,
1043 resource: wgpu::BindingResource::TextureView(&self.fallback_ao_map_view),
1044 },
1045 ],
1046 });
1047
1048 Some(crate::resources::types::MeshInstanceGpuData {
1049 mesh_id,
1050 instance_count,
1051 bind_group,
1052 blend: item.blend,
1053 _instance_buf: instance_buf,
1054 })
1055 }
1056}
1057
1058#[repr(C)]
1107#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
1108pub(crate) struct ObjectUniform {
1109 pub(crate) model: [[f32; 4]; 4], pub(crate) colour: [f32; 4], pub(crate) selected: u32, pub(crate) wireframe: u32, pub(crate) ambient: f32, pub(crate) diffuse: f32, pub(crate) specular: f32, pub(crate) shininess: f32, pub(crate) has_texture: u32, pub(crate) use_pbr: u32, pub(crate) metallic: f32, pub(crate) roughness: f32, pub(crate) has_normal_map: u32, pub(crate) has_ao_map: u32, pub(crate) has_attribute: u32, pub(crate) scalar_min: f32, pub(crate) scalar_max: f32, pub(crate) receive_shadows: u32, pub(crate) nan_colour: [f32; 4], pub(crate) use_nan_colour: u32, pub(crate) use_matcap: u32, pub(crate) matcap_blendable: u32, pub(crate) unlit: u32, pub(crate) use_face_colour: u32, pub(crate) uv_vis_mode: u32, pub(crate) uv_vis_scale: f32, pub(crate) backface_policy: u32, pub(crate) backface_colour: [f32; 4], pub(crate) has_warp: u32, pub(crate) warp_scale: f32, pub(crate) has_position_override: u32, pub(crate) has_normal_override: u32, pub(crate) emissive: [f32; 3], pub(crate) use_flat: u32, pub(crate) alpha_mode: u32, pub(crate) alpha_cutoff: f32, pub(crate) has_metallic_roughness_tex: u32, pub(crate) has_emissive_tex: u32, pub(crate) uv_transform: [f32; 4], pub(crate) deform_flags: u32, pub(crate) _pad_after_deform: u32, pub(crate) ao_range: [f32; 2], pub(crate) metallic_range: [f32; 2], pub(crate) roughness_range: [f32; 2], }
1173
1174const _: () = assert!(std::mem::size_of::<ObjectUniform>() == 304);
1175#[repr(C)]
1179#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
1180pub(crate) struct InstanceData {
1181 pub(crate) model: [[f32; 4]; 4], pub(crate) colour: [f32; 4], pub(crate) selected: u32, pub(crate) wireframe: u32, pub(crate) ambient: f32, pub(crate) diffuse: f32, pub(crate) specular: f32, pub(crate) shininess: f32, pub(crate) has_texture: u32, pub(crate) use_pbr: u32, pub(crate) metallic: f32, pub(crate) roughness: f32, pub(crate) has_normal_map: u32, pub(crate) has_ao_map: u32, pub(crate) unlit: u32, pub(crate) receive_shadows: u32, pub(crate) use_flat: u32, pub(crate) _pad_inst: u32, pub(crate) uv_transform: [f32; 4], pub(crate) ao_range: [f32; 2], pub(crate) _pad_ao_range: [f32; 2], }
1212
1213const _: () = assert!(std::mem::size_of::<InstanceData>() == 176);
1214#[repr(C)]
1225#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
1226pub(crate) struct PickInstance {
1227 pub(crate) model_c0: [f32; 4],
1228 pub(crate) model_c1: [f32; 4],
1229 pub(crate) model_c2: [f32; 4],
1230 pub(crate) model_c3: [f32; 4],
1231 pub(crate) object_id: u32,
1232 pub(crate) _pad: [u32; 3],
1233}
1234
1235const _: () = assert!(std::mem::size_of::<PickInstance>() == 80);
1236#[repr(C)]
1244#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
1245pub(crate) struct InstanceAabb {
1246 pub(crate) min: [f32; 3],
1247 pub(crate) batch_index: u32,
1248 pub(crate) max: [f32; 3],
1249 pub(crate) cast_shadows: u32,
1251}
1252
1253const _: () = assert!(std::mem::size_of::<InstanceAabb>() == 32);
1254#[repr(C)]
1272#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
1273pub struct BatchMeta {
1274 pub index_count: u32,
1276 pub first_index: u32,
1278 pub instance_offset: u32,
1280 pub instance_count: u32,
1282 pub vis_offset: u32,
1284 pub is_transparent: u32,
1286 pub _pad: [u32; 2],
1288}
1289
1290const _: () = assert!(std::mem::size_of::<BatchMeta>() == 32);