1use core::{ops::Deref, sync::atomic::AtomicUsize};
4use std::sync::Arc;
5
6use craballoc::{
7 prelude::Hybrid,
8 value::{HybridArray, HybridWriteGuard},
9};
10use crabslab::Id;
11use glam::{Mat4, UVec2};
12use snafu::ResultExt;
13
14use crate::{
15 atlas::{shader::AtlasTextureDescriptor, AtlasBlittingOperation, AtlasImage, AtlasTexture},
16 bindgroup::ManagedBindGroup,
17 light::{IsLight, Light},
18 primitive::Primitive,
19};
20
21use super::{
22 shader::{LightStyle, ShadowMapDescriptor},
23 AnalyticalLight, Lighting, LightingError, PollSnafu,
24};
25
26#[derive(Clone)]
35pub struct ShadowMap {
36 pub(crate) stage_slab_buffer_creation_time: Arc<AtomicUsize>,
38 pub(crate) light_slab_buffer_creation_time: Arc<AtomicUsize>,
40 pub(crate) shadowmap_descriptor: Hybrid<ShadowMapDescriptor>,
42 pub(crate) light_space_transforms: HybridArray<Mat4>,
47 pub(crate) update_bindgroup: ManagedBindGroup,
49 pub(crate) atlas_textures: Vec<AtlasTexture>,
50 pub(crate) _atlas_textures_array: HybridArray<Id<AtlasTextureDescriptor>>,
51 pub(crate) update_texture: crate::texture::Texture,
52 pub(crate) blitting_op: AtlasBlittingOperation,
53 pub(crate) light_bundle: AnalyticalLight,
54}
55
56impl ShadowMap {
57 const LABEL: Option<&str> = Some("shadow-map");
58
59 pub fn create_update_bindgroup_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout {
60 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
61 label: Self::LABEL,
62 entries: &[
63 wgpu::BindGroupLayoutEntry {
64 binding: 0,
65 visibility: wgpu::ShaderStages::VERTEX,
66 ty: wgpu::BindingType::Buffer {
67 ty: wgpu::BufferBindingType::Storage { read_only: true },
68 has_dynamic_offset: false,
69 min_binding_size: None,
70 },
71 count: None,
72 },
73 wgpu::BindGroupLayoutEntry {
74 binding: 1,
75 visibility: wgpu::ShaderStages::VERTEX,
76 ty: wgpu::BindingType::Buffer {
77 ty: wgpu::BufferBindingType::Storage { read_only: true },
78 has_dynamic_offset: false,
79 min_binding_size: None,
80 },
81 count: None,
82 },
83 ],
84 })
85 }
86
87 pub fn create_update_pipeline(
88 device: &wgpu::Device,
89 bindgroup_layout: &wgpu::BindGroupLayout,
90 ) -> wgpu::RenderPipeline {
91 let shadow_map_update_layout =
92 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
93 label: ShadowMap::LABEL,
94 bind_group_layouts: &[bindgroup_layout],
95 push_constant_ranges: &[],
96 });
97 let shadow_map_update_vertex = crate::linkage::shadow_mapping_vertex::linkage(device);
98 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
99 label: Self::LABEL,
100 layout: Some(&shadow_map_update_layout),
101 vertex: wgpu::VertexState {
102 module: &shadow_map_update_vertex.module,
103 entry_point: Some(shadow_map_update_vertex.entry_point),
104 compilation_options: wgpu::PipelineCompilationOptions::default(),
105 buffers: &[],
106 },
107 primitive: wgpu::PrimitiveState {
108 topology: wgpu::PrimitiveTopology::TriangleList,
109 strip_index_format: None,
110 front_face: wgpu::FrontFace::Ccw,
111 cull_mode: Some(wgpu::Face::Front),
112 unclipped_depth: false,
113 polygon_mode: wgpu::PolygonMode::Fill,
114 conservative: false,
115 },
116 depth_stencil: Some(wgpu::DepthStencilState {
117 format: wgpu::TextureFormat::Depth32Float,
118 depth_write_enabled: true,
119 depth_compare: wgpu::CompareFunction::Less,
120 stencil: wgpu::StencilState::default(),
121 bias: wgpu::DepthBiasState::default(),
122 }),
123 multisample: wgpu::MultisampleState::default(),
124 fragment: None,
125 multiview: None,
126 cache: None,
127 })
128 }
129
130 fn create_update_bindgroup(
132 device: &wgpu::Device,
133 bindgroup_layout: &wgpu::BindGroupLayout,
134 geometry_slab_buffer: &wgpu::Buffer,
135 light_slab_buffer: &wgpu::Buffer,
136 ) -> wgpu::BindGroup {
137 device.create_bind_group(&wgpu::BindGroupDescriptor {
138 label: Self::LABEL,
139 layout: bindgroup_layout,
140 entries: &[
141 wgpu::BindGroupEntry {
142 binding: 0,
143 resource: wgpu::BindingResource::Buffer(
144 geometry_slab_buffer.as_entire_buffer_binding(),
145 ),
146 },
147 wgpu::BindGroupEntry {
148 binding: 1,
149 resource: wgpu::BindingResource::Buffer(
150 light_slab_buffer.as_entire_buffer_binding(),
151 ),
152 },
153 ],
154 })
155 }
156
157 pub fn descriptor_id(&self) -> Id<ShadowMapDescriptor> {
159 self.shadowmap_descriptor.id()
160 }
161
162 pub fn descriptor_lock(&self) -> HybridWriteGuard<'_, ShadowMapDescriptor> {
166 self.shadowmap_descriptor.lock()
167 }
168
169 pub fn new<T>(
172 lighting: &Lighting,
173 analytical_light_bundle: &AnalyticalLight<T>,
174 size: UVec2,
176 z_near: f32,
178 z_far: f32,
180 ) -> Result<Self, LightingError>
181 where
182 T: IsLight,
183 Light: From<T>,
184 {
185 let stage_slab_buffer = lighting.geometry_slab_buffer.read().unwrap();
186 let is_point_light = analytical_light_bundle.style() == LightStyle::Point;
187 let count = if is_point_light { 6 } else { 1 };
188 let atlas = &lighting.shadow_map_atlas;
189 let image = AtlasImage::new(size, crate::atlas::AtlasImageFormat::R32FLOAT);
190 let atlas_textures = atlas.add_images(vec![ℑ count])?;
192 let atlas_len = atlas.len();
193 let label = format!("shadow-map-{atlas_len}");
196 let update_texture = crate::texture::Texture::create_depth_texture_for_shadow_map(
197 atlas.device(),
198 size.x,
199 size.y,
200 1,
201 Some(&label),
202 is_point_light,
203 );
204 let atlas_textures_array = lighting
205 .light_slab
206 .new_array(atlas_textures.iter().map(|t| t.id()));
207 let blitting_op = AtlasBlittingOperation::new(
208 &lighting.shadow_map_update_blitter,
209 atlas,
210 if is_point_light { 6 } else { 1 },
211 );
212 let light_space_transforms =
213 lighting
214 .light_slab
215 .new_array(analytical_light_bundle.light_space_transforms(
216 &analytical_light_bundle.transform().descriptor(),
217 z_near,
218 z_far,
219 ));
220 let shadowmap_descriptor = lighting.light_slab.new_value(ShadowMapDescriptor {
221 light_space_transforms_array: light_space_transforms.array(),
222 z_near,
223 z_far,
224 atlas_textures_array: atlas_textures_array.array(),
225 bias_min: 0.0005,
226 bias_max: 0.005,
227 pcf_samples: 4,
228 });
229 analytical_light_bundle.light_descriptor.modify(|light| {
231 light.shadow_map_desc_id = shadowmap_descriptor.id();
232 });
233 let light_slab_buffer = lighting.commit();
234 let update_bindgroup = ManagedBindGroup::from(ShadowMap::create_update_bindgroup(
235 lighting.light_slab.device(),
236 &lighting.shadow_map_update_bindgroup_layout,
237 stage_slab_buffer.deref(),
238 &light_slab_buffer,
239 ));
240
241 Ok(ShadowMap {
242 stage_slab_buffer_creation_time: Arc::new(stage_slab_buffer.creation_time().into()),
243 light_slab_buffer_creation_time: Arc::new(light_slab_buffer.creation_time().into()),
244 shadowmap_descriptor,
245 light_space_transforms,
246 update_bindgroup,
247 atlas_textures,
248 _atlas_textures_array: atlas_textures_array,
249 update_texture,
250 blitting_op,
251 light_bundle: analytical_light_bundle.clone().into_generic(),
252 })
253 }
254
255 pub fn update<'a>(
266 &self,
267 lighting: impl AsRef<Lighting>,
268 renderlets: impl IntoIterator<Item = &'a Primitive>,
269 ) -> Result<(), LightingError> {
270 let lighting = lighting.as_ref();
271 let shadow_desc = self.shadowmap_descriptor.get();
272 let new_transforms = self.light_bundle.light_space_transforms(
273 &self.light_bundle.transform().descriptor(),
274 shadow_desc.z_near,
275 shadow_desc.z_far,
276 );
277 for (i, t) in (0..self.light_space_transforms.len()).zip(new_transforms) {
278 self.light_space_transforms.set_item(i, t);
279 }
280 if lighting.geometry_slab.has_queued_updates() {
281 lighting.geometry_slab.commit();
282 }
283 let renderlets = renderlets.into_iter().collect::<Vec<_>>();
284
285 let device = lighting.light_slab.device();
286 let queue = lighting.light_slab.queue();
287 let mut light_slab_buffer = lighting.light_slab_buffer.write().unwrap();
288 let mut stage_slab_buffer = lighting.geometry_slab_buffer.write().unwrap();
289
290 let bindgroup = {
291 light_slab_buffer.update_if_invalid();
292 stage_slab_buffer.update_if_invalid();
293 let stored_light_buffer_creation_time = self.light_slab_buffer_creation_time.swap(
294 light_slab_buffer.creation_time(),
295 std::sync::atomic::Ordering::Relaxed,
296 );
297 let stored_stage_buffer_creation_time = self.stage_slab_buffer_creation_time.swap(
298 stage_slab_buffer.creation_time(),
299 std::sync::atomic::Ordering::Relaxed,
300 );
301 let should_invalidate = light_slab_buffer.creation_time()
302 > stored_light_buffer_creation_time
303 || stage_slab_buffer.creation_time() > stored_stage_buffer_creation_time;
304 self.update_bindgroup.get(should_invalidate, || {
305 log::trace!("recreating shadow mapping bindgroup");
306 Self::create_update_bindgroup(
307 device,
308 &lighting.shadow_map_update_bindgroup_layout,
309 &stage_slab_buffer,
310 &light_slab_buffer,
311 )
312 })
313 };
314 for (i, atlas_texture) in self.atlas_textures.iter().enumerate() {
315 let mut encoder = device
316 .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Self::LABEL });
317
318 lighting.lighting_descriptor.modify(|ld| {
321 let id = self.shadowmap_descriptor.id();
322 log::trace!("updating the shadow map {id:?} {i}");
323 ld.update_shadow_map_id = id;
324 ld.update_shadow_map_texture_index = i as u32;
325 });
326 let _ = lighting.light_slab.commit();
328 let label = format!("{}-view-{i}", Self::LABEL.unwrap());
329 let view = self
330 .update_texture
331 .texture
332 .create_view(&wgpu::TextureViewDescriptor {
333 label: Some(&label),
334 base_array_layer: i as u32,
335 array_layer_count: Some(1),
336 dimension: Some(wgpu::TextureViewDimension::D2),
337 ..Default::default()
338 });
339 {
340 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
341 label: Self::LABEL,
342 color_attachments: &[],
343 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
344 view: &view,
345 depth_ops: Some(wgpu::Operations {
346 load: wgpu::LoadOp::Clear(1.0),
347 store: wgpu::StoreOp::Store,
348 }),
349 stencil_ops: None,
350 }),
351 ..Default::default()
352 });
353 render_pass.set_pipeline(&lighting.shadow_map_update_pipeline);
354 render_pass.set_bind_group(0, Some(bindgroup.as_ref()), &[]);
355 let mut count = 0;
356 for rlet in renderlets.iter() {
357 let id = rlet.id();
358 let rlet = rlet.descriptor();
359 let vertex_range = 0..rlet.get_vertex_count();
360 let instance_range = id.inner()..id.inner() + 1;
361 render_pass.draw(vertex_range, instance_range);
362 count += 1;
363 }
364 log::trace!("rendered {count} renderlets to the shadow map");
365 }
366 self.blitting_op.run(
368 lighting.light_slab.runtime(),
369 &mut encoder,
370 &self.update_texture,
371 i as u32,
372 &lighting.shadow_map_atlas,
373 atlas_texture,
374 )?;
375 let submission = queue.submit(Some(encoder.finish()));
376 device
377 .poll(wgpu::PollType::WaitForSubmissionIndex(submission))
378 .context(PollSnafu)?;
379 }
380 Ok(())
381 }
382}
383
384#[cfg(test)]
385#[allow(clippy::unused_enumerate_index)]
386mod test {
387 use glam::{UVec2, Vec3};
388
389 use crate::{context::Context, test::BlockOnFuture};
390
391 #[test]
392 fn shadow_mapping_just_cuboid() {
393 let w = 800.0;
394 let h = 800.0;
395 let ctx = Context::headless(w as u32, h as u32).block();
396 let stage = ctx
397 .new_stage()
398 .with_lighting(true)
399 .with_msaa_sample_count(4);
400
401 let doc = stage
408 .load_gltf_document_from_path(
409 crate::test::workspace_dir()
410 .join("gltf")
411 .join("shadow_mapping_only_cuboid.gltf"),
412 )
413 .unwrap();
414 let camera = doc.cameras.first().unwrap();
415 camera
416 .as_ref()
417 .set_projection(crate::camera::perspective(w, h));
418 stage.use_camera(camera);
419
420 let frame = ctx.get_next_frame().unwrap();
421 stage.render(&frame.view());
422 let img = frame.read_image().block().unwrap();
423 frame.present();
424
425 img_diff::assert_img_eq("shadows/shadow_mapping_just_cuboid/scene_before.png", img);
427
428 let gltf_light = doc.lights.first().unwrap();
429 let shadow_map = stage
430 .new_shadow_map(gltf_light, UVec2::splat(256), 0.0, 20.0)
431 .unwrap();
432 shadow_map.shadowmap_descriptor.modify(|desc| {
433 desc.bias_min = 0.00008;
434 desc.bias_max = 0.00008;
435 });
436 shadow_map.update(&stage, doc.renderlets_iter()).unwrap();
437
438 let frame = ctx.get_next_frame().unwrap();
439 stage.render(&frame.view());
440 let img = frame.read_image().block().unwrap();
441 img_diff::assert_img_eq("shadows/shadow_mapping_just_cuboid/scene_after.png", img);
442 frame.present();
443 }
444
445 #[test]
446 fn shadow_mapping_just_cuboid_red_and_blue() {
447 let w = 800.0;
448 let h = 800.0;
449 let ctx = Context::headless(w as u32, h as u32).block();
450 let stage = ctx
451 .new_stage()
452 .with_lighting(true)
453 .with_msaa_sample_count(4);
454
455 let doc = stage
456 .load_gltf_document_from_path(
457 crate::test::workspace_dir()
458 .join("gltf")
459 .join("shadow_mapping_only_cuboid_red_and_blue.gltf"),
460 )
461 .unwrap();
462 let camera = doc.cameras.first().unwrap();
463 camera
464 .as_ref()
465 .set_projection(crate::camera::perspective(w, h));
466 stage.use_camera(camera);
467
468 let gltf_light_a = doc.lights.first().unwrap();
469 let gltf_light_b = doc.lights.get(1).unwrap();
470 let shadow_map_a = stage
471 .new_shadow_map(gltf_light_a, UVec2::splat(256), 0.0, 20.0)
472 .unwrap();
473 shadow_map_a.shadowmap_descriptor.modify(|desc| {
474 desc.bias_min = 0.00008;
475 desc.bias_max = 0.00008;
476 });
477 shadow_map_a.update(&stage, doc.renderlets_iter()).unwrap();
478 let shadow_map_b = stage
479 .new_shadow_map(gltf_light_b, UVec2::splat(256), 0.0, 20.0)
480 .unwrap();
481 shadow_map_b.shadowmap_descriptor.modify(|desc| {
482 desc.bias_min = 0.00008;
483 desc.bias_max = 0.00008;
484 });
485 shadow_map_b.update(&stage, doc.renderlets_iter()).unwrap();
486
487 let frame = ctx.get_next_frame().unwrap();
488
489 stage.render(&frame.view());
490 let img = frame.read_image().block().unwrap();
491 img_diff::assert_img_eq(
492 "shadows/shadow_mapping_just_cuboid/red_and_blue/frame.png",
493 img,
494 );
495 frame.present();
496 }
497
498 #[test]
499 fn shadow_mapping_sanity() {
500 let w = 800.0;
501 let h = 800.0;
502 let ctx = Context::headless(w as u32, h as u32)
503 .block()
504 .with_shadow_mapping_atlas_texture_size([1024, 1024, 2]);
505 let stage = ctx.new_stage().with_lighting(true);
506
507 let doc = stage
508 .load_gltf_document_from_path(
509 crate::test::workspace_dir()
510 .join("gltf")
511 .join("shadow_mapping_sanity.gltf"),
512 )
513 .unwrap();
514 let camera = doc.cameras.first().unwrap();
515 camera
516 .as_ref()
517 .set_projection(crate::camera::perspective(w, h));
518 stage.use_camera(camera);
519
520 let frame = ctx.get_next_frame().unwrap();
521 stage.render(&frame.view());
522 let img = frame.read_image().block().unwrap();
523 frame.present();
524
525 img_diff::assert_img_eq("shadows/shadow_mapping_sanity/scene_before.png", img);
527
528 let gltf_light = doc.lights.first().unwrap();
529 assert_eq!(
530 gltf_light.descriptor().transform_id,
531 gltf_light.transform().id(),
532 "light's global transform id is different from its transform_id"
533 );
534
535 let shadows = stage
536 .new_shadow_map(gltf_light, UVec2::new(w as u32, h as u32), 0.0, 20.0)
537 .unwrap();
538 shadows.update(&stage, doc.renderlets_iter()).unwrap();
539
540 let frame = ctx.get_next_frame().unwrap();
571 stage.render(&frame.view());
572
573 let img = frame.read_image().block().unwrap();
574 frame.present();
575 img_diff::assert_img_eq_cfg(
576 "shadows/shadow_mapping_sanity/stage_render.png",
577 img,
578 img_diff::DiffCfg {
579 image_threshold: 0.01,
580 ..Default::default()
581 },
582 );
583 }
584
585 #[test]
586 fn shadow_mapping_spot_lights() {
587 let w = 800.0;
588 let h = 800.0;
589 let ctx = Context::headless(w as u32, h as u32).block();
590 let stage = ctx
591 .new_stage()
592 .with_lighting(true)
593 .with_msaa_sample_count(4);
594
595 let doc = stage
596 .load_gltf_document_from_path(
597 crate::test::workspace_dir()
598 .join("gltf")
599 .join("shadow_mapping_spots.glb"),
600 )
601 .unwrap();
602 let camera = doc.cameras.first().unwrap();
603 camera
604 .as_ref()
605 .set_projection(crate::camera::perspective(w, h));
606 stage.use_camera(camera);
607
608 let mut shadow_maps = vec![];
609 let z_near = 0.1;
610 let z_far = 100.0;
611 for (_i, light_bundle) in doc.lights.iter().enumerate() {
612 {
613 let desc = light_bundle.as_spot().unwrap().descriptor();
614 let (p, v) = desc.shadow_mapping_projection_and_view(
615 &light_bundle.transform().as_mat4(),
616 z_near,
617 z_far,
618 );
619 let temp_camera = stage.new_camera().with_projection_and_view(p, v);
620 stage.use_camera(temp_camera);
621 let frame = ctx.get_next_frame().unwrap();
622 stage.render(&frame.view());
623 let _img = frame.read_image().block().unwrap();
624 frame.present();
629 }
630 let shadow = stage
631 .new_shadow_map(light_bundle, UVec2::splat(256), z_near, z_far)
632 .unwrap();
633 shadow.shadowmap_descriptor.modify(|desc| {
634 desc.bias_min = f32::EPSILON;
635 desc.bias_max = f32::EPSILON;
636 });
637
638 shadow.update(&stage, doc.renderlets_iter()).unwrap();
639 shadow_maps.push(shadow);
640 }
641
642 stage.use_camera(camera);
643
644 let frame = ctx.get_next_frame().unwrap();
645 stage.render(&frame.view());
646 let img = frame.read_image().block().unwrap();
647 img_diff::assert_img_eq("shadows/shadow_mapping_spots/frame.png", img);
648 frame.present();
649 }
650
651 #[test]
652 fn shadow_mapping_point_lights() {
653 let w = 800.0;
654 let h = 800.0;
655 let ctx = Context::headless(w as u32, h as u32).block();
656 let stage = ctx
657 .new_stage()
658 .with_lighting(true)
659 .with_background_color(Vec3::splat(0.05087).extend(1.0))
660 .with_msaa_sample_count(4);
661 let doc = stage
662 .load_gltf_document_from_path(
663 crate::test::workspace_dir()
664 .join("gltf")
665 .join("shadow_mapping_points.glb"),
666 )
667 .unwrap();
668 let camera = doc.cameras.first().unwrap();
669 camera
670 .as_ref()
671 .set_projection(crate::camera::perspective(w, h));
672 stage.use_camera(camera);
673
674 let mut shadows = vec![];
675 let z_near = 0.1;
676 let z_far = 100.0;
677 for (i, light_bundle) in doc.lights.iter().enumerate() {
678 {
679 let desc = light_bundle.as_point().unwrap().descriptor();
680 println!("point light {i}: {desc:?}");
681 let (p, vs) = desc.shadow_mapping_projection_and_view_matrices(
682 &light_bundle.transform().as_mat4(),
683 z_near,
684 z_far,
685 );
686 for (_j, v) in vs.into_iter().enumerate() {
687 stage.use_camera(stage.new_camera().with_projection_and_view(p, v));
688 let frame = ctx.get_next_frame().unwrap();
689 stage.render(&frame.view());
690 let _img = frame.read_image().block().unwrap();
691 frame.present();
696 }
697 }
698 let shadow = stage
699 .new_shadow_map(light_bundle, UVec2::splat(256), z_near, z_far)
700 .unwrap();
701 shadow.shadowmap_descriptor.modify(|desc| {
702 desc.pcf_samples = 16;
703 desc.bias_min = 0.00010;
704 desc.bias_max = 0.010;
705 });
706 shadow.update(&stage, doc.renderlets_iter()).unwrap();
707 shadows.push(shadow);
708 }
709
710 stage.use_camera(camera);
711
712 let frame = ctx.get_next_frame().unwrap();
713 stage.render(&frame.view());
714 let img = frame.read_image().block().unwrap();
715 img_diff::assert_img_eq("shadows/shadow_mapping_points/frame.png", img);
716 frame.present();
717 }
718}