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
186 .geometry_slab_buffer
187 .read()
188 .expect("geometry_slab_buffer read");
189 let is_point_light = analytical_light_bundle.style() == LightStyle::Point;
190 let count = if is_point_light { 6 } else { 1 };
191 let atlas = &lighting.shadow_map_atlas;
192 let image = AtlasImage::new(size, crate::atlas::AtlasImageFormat::R32FLOAT);
193 let atlas_textures = atlas.add_images(vec![ℑ count])?;
195 let atlas_len = atlas.len();
196 let label = format!("shadow-map-{atlas_len}");
199 let update_texture = crate::texture::Texture::create_depth_texture_for_shadow_map(
200 atlas.device(),
201 size.x,
202 size.y,
203 1,
204 Some(&label),
205 is_point_light,
206 );
207 let atlas_textures_array = lighting
208 .light_slab
209 .new_array(atlas_textures.iter().map(|t| t.id()));
210 let blitting_op = AtlasBlittingOperation::new(
211 &lighting.shadow_map_update_blitter,
212 atlas,
213 if is_point_light { 6 } else { 1 },
214 );
215 let light_space_transforms =
216 lighting
217 .light_slab
218 .new_array(analytical_light_bundle.light_space_transforms(
219 &analytical_light_bundle.transform().descriptor(),
220 z_near,
221 z_far,
222 ));
223 let shadowmap_descriptor = lighting.light_slab.new_value(ShadowMapDescriptor {
224 light_space_transforms_array: light_space_transforms.array(),
225 z_near,
226 z_far,
227 atlas_textures_array: atlas_textures_array.array(),
228 bias_min: 0.0005,
229 bias_max: 0.005,
230 pcf_samples: 4,
231 });
232 analytical_light_bundle.light_descriptor.modify(|light| {
234 light.shadow_map_desc_id = shadowmap_descriptor.id();
235 });
236 let light_slab_buffer = lighting.commit();
237 let update_bindgroup = ManagedBindGroup::from(ShadowMap::create_update_bindgroup(
238 lighting.light_slab.device(),
239 &lighting.shadow_map_update_bindgroup_layout,
240 stage_slab_buffer.deref(),
241 &light_slab_buffer,
242 ));
243
244 Ok(ShadowMap {
245 stage_slab_buffer_creation_time: Arc::new(stage_slab_buffer.creation_time().into()),
246 light_slab_buffer_creation_time: Arc::new(light_slab_buffer.creation_time().into()),
247 shadowmap_descriptor,
248 light_space_transforms,
249 update_bindgroup,
250 atlas_textures,
251 _atlas_textures_array: atlas_textures_array,
252 update_texture,
253 blitting_op,
254 light_bundle: analytical_light_bundle.clone().into_generic(),
255 })
256 }
257
258 pub fn update<'a>(
269 &self,
270 lighting: impl AsRef<Lighting>,
271 renderlets: impl IntoIterator<Item = &'a Primitive>,
272 ) -> Result<(), LightingError> {
273 let lighting = lighting.as_ref();
274 let shadow_desc = self.shadowmap_descriptor.get();
275 let new_transforms = self.light_bundle.light_space_transforms(
276 &self.light_bundle.transform().descriptor(),
277 shadow_desc.z_near,
278 shadow_desc.z_far,
279 );
280 for (i, t) in (0..self.light_space_transforms.len()).zip(new_transforms) {
281 self.light_space_transforms.set_item(i, t);
282 }
283 if lighting.geometry_slab.has_queued_updates() {
284 lighting.geometry_slab.commit();
285 }
286 let renderlets = renderlets.into_iter().collect::<Vec<_>>();
287
288 let device = lighting.light_slab.device();
289 let queue = lighting.light_slab.queue();
290 let mut light_slab_buffer = lighting
291 .light_slab_buffer
292 .write()
293 .expect("light_slab_buffer write");
294 let mut stage_slab_buffer = lighting
295 .geometry_slab_buffer
296 .write()
297 .expect("geometry_slab_buffer write");
298
299 let bindgroup = {
300 light_slab_buffer.update_if_invalid();
301 stage_slab_buffer.update_if_invalid();
302 let stored_light_buffer_creation_time = self.light_slab_buffer_creation_time.swap(
303 light_slab_buffer.creation_time(),
304 std::sync::atomic::Ordering::Relaxed,
305 );
306 let stored_stage_buffer_creation_time = self.stage_slab_buffer_creation_time.swap(
307 stage_slab_buffer.creation_time(),
308 std::sync::atomic::Ordering::Relaxed,
309 );
310 let should_invalidate = light_slab_buffer.creation_time()
311 > stored_light_buffer_creation_time
312 || stage_slab_buffer.creation_time() > stored_stage_buffer_creation_time;
313 self.update_bindgroup.get(should_invalidate, || {
314 log::trace!("recreating shadow mapping bindgroup");
315 Self::create_update_bindgroup(
316 device,
317 &lighting.shadow_map_update_bindgroup_layout,
318 &stage_slab_buffer,
319 &light_slab_buffer,
320 )
321 })
322 };
323 for (i, atlas_texture) in self.atlas_textures.iter().enumerate() {
324 let mut encoder = device
325 .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Self::LABEL });
326
327 lighting.lighting_descriptor.modify(|ld| {
331 let id = self.shadowmap_descriptor.id();
332 log::trace!("updating the shadow map {id:?} {i}");
333 ld.update_shadow_map_id = id;
334 ld.update_shadow_map_texture_index = i as u32;
335 });
336 let _ = lighting.light_slab.commit();
338 let label = format!("{}-view-{i}", Self::LABEL.unwrap());
339 let view = self
340 .update_texture
341 .texture
342 .create_view(&wgpu::TextureViewDescriptor {
343 label: Some(&label),
344 base_array_layer: i as u32,
345 array_layer_count: Some(1),
346 dimension: Some(wgpu::TextureViewDimension::D2),
347 ..Default::default()
348 });
349 {
350 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
351 label: Self::LABEL,
352 color_attachments: &[],
353 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
354 view: &view,
355 depth_ops: Some(wgpu::Operations {
356 load: wgpu::LoadOp::Clear(1.0),
357 store: wgpu::StoreOp::Store,
358 }),
359 stencil_ops: None,
360 }),
361 ..Default::default()
362 });
363 render_pass.set_pipeline(&lighting.shadow_map_update_pipeline);
364 render_pass.set_bind_group(0, Some(bindgroup.as_ref()), &[]);
365 let mut count = 0;
366 for rlet in renderlets.iter() {
367 let id = rlet.id();
368 let rlet = rlet.descriptor();
369 let vertex_range = 0..rlet.get_vertex_count();
370 let instance_range = id.inner()..id.inner() + 1;
371 render_pass.draw(vertex_range, instance_range);
372 count += 1;
373 }
374 log::trace!("rendered {count} renderlets to the shadow map");
375 }
376 self.blitting_op.run(
379 lighting.light_slab.runtime(),
380 &mut encoder,
381 &self.update_texture,
382 i as u32,
383 &lighting.shadow_map_atlas,
384 atlas_texture,
385 )?;
386 let submission = queue.submit(Some(encoder.finish()));
387 device
388 .poll(wgpu::PollType::WaitForSubmissionIndex(submission))
389 .context(PollSnafu)?;
390 }
391 Ok(())
392 }
393}
394
395#[cfg(test)]
396#[allow(clippy::unused_enumerate_index)]
397mod test {
398 use glam::{UVec2, Vec3};
399
400 use crate::{context::Context, test::BlockOnFuture};
401
402 #[test]
403 fn shadow_mapping_just_cuboid() {
404 let w = 800.0;
405 let h = 800.0;
406 let ctx = Context::headless(w as u32, h as u32).block();
407 let stage = ctx
408 .new_stage()
409 .with_lighting(true)
410 .with_msaa_sample_count(4);
411
412 let doc = stage
420 .load_gltf_document_from_path(
421 crate::test::workspace_dir()
422 .join("gltf")
423 .join("shadow_mapping_only_cuboid.gltf"),
424 )
425 .unwrap();
426 let camera = doc.cameras.first().unwrap();
427 camera
428 .as_ref()
429 .set_projection(crate::camera::perspective(w, h));
430 stage.use_camera(camera);
431
432 let frame = ctx.get_next_frame().unwrap();
433 stage.render(&frame.view());
434 let img = frame.read_image().block().unwrap();
435 frame.present();
436
437 img_diff::assert_img_eq("shadows/shadow_mapping_just_cuboid/scene_before.png", img);
439
440 let gltf_light = doc.lights.first().unwrap();
441 let shadow_map = stage
442 .new_shadow_map(gltf_light, UVec2::splat(256), 0.0, 20.0)
443 .unwrap();
444 shadow_map.shadowmap_descriptor.modify(|desc| {
445 desc.bias_min = 0.00008;
446 desc.bias_max = 0.00008;
447 });
448 shadow_map.update(&stage, doc.renderlets_iter()).unwrap();
449
450 let frame = ctx.get_next_frame().unwrap();
451 stage.render(&frame.view());
452 let img = frame.read_image().block().unwrap();
453 img_diff::assert_img_eq("shadows/shadow_mapping_just_cuboid/scene_after.png", img);
454 frame.present();
455 }
456
457 #[test]
458 fn shadow_mapping_just_cuboid_red_and_blue() {
459 let w = 800.0;
460 let h = 800.0;
461 let ctx = Context::headless(w as u32, h as u32).block();
462 let stage = ctx
463 .new_stage()
464 .with_lighting(true)
465 .with_msaa_sample_count(4);
466
467 let doc = stage
468 .load_gltf_document_from_path(
469 crate::test::workspace_dir()
470 .join("gltf")
471 .join("shadow_mapping_only_cuboid_red_and_blue.gltf"),
472 )
473 .unwrap();
474 let camera = doc.cameras.first().unwrap();
475 camera
476 .as_ref()
477 .set_projection(crate::camera::perspective(w, h));
478 stage.use_camera(camera);
479
480 let gltf_light_a = doc.lights.first().unwrap();
481 let gltf_light_b = doc.lights.get(1).unwrap();
482 let shadow_map_a = stage
483 .new_shadow_map(gltf_light_a, UVec2::splat(256), 0.0, 20.0)
484 .unwrap();
485 shadow_map_a.shadowmap_descriptor.modify(|desc| {
486 desc.bias_min = 0.00008;
487 desc.bias_max = 0.00008;
488 });
489 shadow_map_a.update(&stage, doc.renderlets_iter()).unwrap();
490 let shadow_map_b = stage
491 .new_shadow_map(gltf_light_b, UVec2::splat(256), 0.0, 20.0)
492 .unwrap();
493 shadow_map_b.shadowmap_descriptor.modify(|desc| {
494 desc.bias_min = 0.00008;
495 desc.bias_max = 0.00008;
496 });
497 shadow_map_b.update(&stage, doc.renderlets_iter()).unwrap();
498
499 let frame = ctx.get_next_frame().unwrap();
500
501 stage.render(&frame.view());
502 let img = frame.read_image().block().unwrap();
503 img_diff::assert_img_eq(
504 "shadows/shadow_mapping_just_cuboid/red_and_blue/frame.png",
505 img,
506 );
507 frame.present();
508 }
509
510 #[test]
511 fn shadow_mapping_sanity() {
512 let w = 800.0;
513 let h = 800.0;
514 let ctx = Context::headless(w as u32, h as u32)
515 .block()
516 .with_shadow_mapping_atlas_texture_size([1024, 1024, 2]);
517 let stage = ctx.new_stage().with_lighting(true);
518
519 let doc = stage
520 .load_gltf_document_from_path(
521 crate::test::workspace_dir()
522 .join("gltf")
523 .join("shadow_mapping_sanity.gltf"),
524 )
525 .unwrap();
526 let camera = doc.cameras.first().unwrap();
527 camera
528 .as_ref()
529 .set_projection(crate::camera::perspective(w, h));
530 stage.use_camera(camera);
531
532 let frame = ctx.get_next_frame().unwrap();
533 stage.render(&frame.view());
534 let img = frame.read_image().block().unwrap();
535 frame.present();
536
537 img_diff::assert_img_eq("shadows/shadow_mapping_sanity/scene_before.png", img);
539
540 let gltf_light = doc.lights.first().unwrap();
541 assert_eq!(
542 gltf_light.descriptor().transform_id,
543 gltf_light.transform().id(),
544 "light's global transform id is different from its transform_id"
545 );
546
547 let shadows = stage
548 .new_shadow_map(gltf_light, UVec2::new(w as u32, h as u32), 0.0, 20.0)
549 .unwrap();
550 shadows.update(&stage, doc.renderlets_iter()).unwrap();
551
552 let frame = ctx.get_next_frame().unwrap();
586 stage.render(&frame.view());
587
588 let img = frame.read_image().block().unwrap();
589 frame.present();
590 img_diff::assert_img_eq_cfg(
591 "shadows/shadow_mapping_sanity/stage_render.png",
592 img,
593 img_diff::DiffCfg {
594 image_threshold: 0.01,
595 ..Default::default()
596 },
597 );
598 }
599
600 #[test]
601 fn shadow_mapping_spot_lights() {
602 let w = 800.0;
603 let h = 800.0;
604 let ctx = Context::headless(w as u32, h as u32).block();
605 let stage = ctx
606 .new_stage()
607 .with_lighting(true)
608 .with_msaa_sample_count(4);
609
610 let doc = stage
611 .load_gltf_document_from_path(
612 crate::test::workspace_dir()
613 .join("gltf")
614 .join("shadow_mapping_spots.glb"),
615 )
616 .unwrap();
617 let camera = doc.cameras.first().unwrap();
618 camera
619 .as_ref()
620 .set_projection(crate::camera::perspective(w, h));
621 stage.use_camera(camera);
622
623 let mut shadow_maps = vec![];
624 let z_near = 0.1;
625 let z_far = 100.0;
626 for (_i, light_bundle) in doc.lights.iter().enumerate() {
627 {
628 let desc = light_bundle.as_spot().unwrap().descriptor();
629 let (p, v) = desc.shadow_mapping_projection_and_view(
630 &light_bundle.transform().as_mat4(),
631 z_near,
632 z_far,
633 );
634 let temp_camera = stage.new_camera().with_projection_and_view(p, v);
635 stage.use_camera(temp_camera);
636 let frame = ctx.get_next_frame().unwrap();
637 stage.render(&frame.view());
638 let _img = frame.read_image().block().unwrap();
639 frame.present();
644 }
645 let shadow = stage
646 .new_shadow_map(light_bundle, UVec2::splat(256), z_near, z_far)
647 .unwrap();
648 shadow.shadowmap_descriptor.modify(|desc| {
649 desc.bias_min = f32::EPSILON;
650 desc.bias_max = f32::EPSILON;
651 });
652
653 shadow.update(&stage, doc.renderlets_iter()).unwrap();
654 shadow_maps.push(shadow);
655 }
656
657 stage.use_camera(camera);
658
659 let frame = ctx.get_next_frame().unwrap();
660 stage.render(&frame.view());
661 let img = frame.read_image().block().unwrap();
662 img_diff::assert_img_eq("shadows/shadow_mapping_spots/frame.png", img);
663 frame.present();
664 }
665
666 #[test]
667 fn shadow_mapping_point_lights() {
668 let w = 800.0;
669 let h = 800.0;
670 let ctx = Context::headless(w as u32, h as u32).block();
671 let stage = ctx
672 .new_stage()
673 .with_lighting(true)
674 .with_background_color(Vec3::splat(0.05087).extend(1.0))
675 .with_msaa_sample_count(4);
676 let doc = stage
677 .load_gltf_document_from_path(
678 crate::test::workspace_dir()
679 .join("gltf")
680 .join("shadow_mapping_points.glb"),
681 )
682 .unwrap();
683 let camera = doc.cameras.first().unwrap();
684 camera
685 .as_ref()
686 .set_projection(crate::camera::perspective(w, h));
687 stage.use_camera(camera);
688
689 let mut shadows = vec![];
690 let z_near = 0.1;
691 let z_far = 100.0;
692 for (i, light_bundle) in doc.lights.iter().enumerate() {
693 {
694 let desc = light_bundle.as_point().unwrap().descriptor();
695 println!("point light {i}: {desc:?}");
696 let (p, vs) = desc.shadow_mapping_projection_and_view_matrices(
697 &light_bundle.transform().as_mat4(),
698 z_near,
699 z_far,
700 );
701 for (_j, v) in vs.into_iter().enumerate() {
702 stage.use_camera(stage.new_camera().with_projection_and_view(p, v));
703 let frame = ctx.get_next_frame().unwrap();
704 stage.render(&frame.view());
705 let _img = frame.read_image().block().unwrap();
706 frame.present();
711 }
712 }
713 let shadow = stage
714 .new_shadow_map(light_bundle, UVec2::splat(256), z_near, z_far)
715 .unwrap();
716 shadow.shadowmap_descriptor.modify(|desc| {
717 desc.pcf_samples = 16;
718 desc.bias_min = 0.00010;
719 desc.bias_max = 0.010;
720 });
721 shadow.update(&stage, doc.renderlets_iter()).unwrap();
722 shadows.push(shadow);
723 }
724
725 stage.use_camera(camera);
726
727 let frame = ctx.get_next_frame().unwrap();
728 stage.render(&frame.view());
729 let img = frame.read_image().block().unwrap();
730 img_diff::assert_img_eq("shadows/shadow_mapping_points/frame.png", img);
731 frame.present();
732 }
733}