renderling/light/
cpu.rs

1//! CPU-only lighting and shadows.
2use std::sync::{Arc, RwLock};
3
4#[cfg(doc)]
5use crate::stage::Stage;
6use craballoc::{
7    prelude::{Hybrid, SlabAllocator, WgpuRuntime},
8    slab::SlabBuffer,
9    value::HybridArray,
10};
11use crabslab::Id;
12use glam::{Mat4, UVec2, Vec3, Vec4};
13use snafu::prelude::*;
14
15use crate::{
16    atlas::{Atlas, AtlasBlitter, AtlasError},
17    geometry::Geometry,
18    transform::{shader::TransformDescriptor, NestedTransform, Transform},
19};
20
21use super::shader::{
22    DirectionalLightDescriptor, LightDescriptor, LightStyle, LightingDescriptor,
23    PointLightDescriptor, SpotLightDescriptor,
24};
25
26pub use super::shader::{Candela, Lux};
27pub use super::shadow_map::ShadowMap;
28
29#[derive(Debug, Snafu)]
30#[snafu(visibility(pub(crate)))]
31pub enum LightingError {
32    #[snafu(display("{source}"))]
33    Atlas { source: AtlasError },
34
35    #[snafu(display("Driver poll error: {source}"))]
36    Poll { source: wgpu::PollError },
37}
38
39impl From<AtlasError> for LightingError {
40    fn from(source: AtlasError) -> Self {
41        LightingError::Atlas { source }
42    }
43}
44
45/// Describes shared behaviour between all analytical lights.
46pub trait IsLight: Clone {
47    /// Return the style of this light.
48    fn style(&self) -> LightStyle;
49
50    fn light_space_transforms(
51        &self,
52        // Another transform applied to the light.
53        parent_transform: &TransformDescriptor,
54        // Near limits of the light's reach
55        //
56        // The maximum should be the `Camera`'s `Frustum::depth()`.
57        // TODO: in `DirectionalLightDescriptor::shadow_mapping_projection_and_view`, take Frustum
58        // as a parameter and then figure out the minimal view projection that includes that frustum
59        z_near: f32,
60        // Far limits of the light's reach
61        z_far: f32,
62    ) -> Vec<Mat4>;
63}
64
65/// A directional light.
66///
67/// An analitical light that casts light in parallel, infinitely.
68#[derive(Clone, Debug)]
69pub struct DirectionalLight {
70    descriptor: Hybrid<DirectionalLightDescriptor>,
71}
72
73impl IsLight for DirectionalLight {
74    fn style(&self) -> LightStyle {
75        LightStyle::Directional
76    }
77
78    fn light_space_transforms(
79        &self,
80        parent_transform: &TransformDescriptor,
81        z_near: f32,
82        z_far: f32,
83    ) -> Vec<Mat4> {
84        let m = Mat4::from(*parent_transform);
85        vec![{
86            let (p, v) = self
87                .descriptor()
88                .shadow_mapping_projection_and_view(&m, z_near, z_far);
89            p * v
90        }]
91    }
92}
93
94impl DirectionalLight {
95    /// Returns a pointer to the descriptor data on the GPU slab.
96    pub fn id(&self) -> Id<DirectionalLightDescriptor> {
97        self.descriptor.id()
98    }
99
100    /// Returns the a copy of the descriptor.
101    pub fn descriptor(&self) -> DirectionalLightDescriptor {
102        self.descriptor.get()
103    }
104}
105
106/// A [`DirectionalLight`] comes wrapped in [`AnalyticalLight`], giving the
107/// [`AnalyticalLight`] the ability to simulate sunlight or other lights that
108/// are "infinitely" far away.
109impl AnalyticalLight<DirectionalLight> {
110    /// Set the direction of the directional light.
111    pub fn set_direction(&self, direction: Vec3) -> &Self {
112        self.inner.descriptor.modify(|d| d.direction = direction);
113        self
114    }
115
116    /// Set the direction and return the directional light.
117    pub fn with_direction(self, direction: Vec3) -> Self {
118        self.set_direction(direction);
119        self
120    }
121
122    /// Modify the direction of the directional light.
123    pub fn modify_direction<T: 'static>(&self, f: impl FnOnce(&mut Vec3) -> T) -> T {
124        self.inner.descriptor.modify(|d| f(&mut d.direction))
125    }
126
127    /// Get the direction of the directional light.
128    pub fn direction(&self) -> Vec3 {
129        self.inner.descriptor.get().direction
130    }
131
132    /// Set the color of the directional light.
133    pub fn set_color(&self, color: Vec4) -> &Self {
134        self.inner.descriptor.modify(|d| d.color = color);
135        self
136    }
137
138    /// Set the color and return the directional light.
139    pub fn with_color(self, color: Vec4) -> Self {
140        self.set_color(color);
141        self
142    }
143
144    /// Modify the color of the directional light.
145    pub fn modify_color<T: 'static>(&self, f: impl FnOnce(&mut Vec4) -> T) -> T {
146        self.inner.descriptor.modify(|d| f(&mut d.color))
147    }
148
149    /// Get the color of the directional light.
150    pub fn color(&self) -> Vec4 {
151        self.inner.descriptor.get().color
152    }
153
154    /// Set the intensity of the directional light.
155    pub fn set_intensity(&self, intensity: Lux) -> &Self {
156        self.inner.descriptor.modify(|d| d.intensity = intensity);
157        self
158    }
159
160    /// Set the intensity and return the directional light.
161    pub fn with_intensity(self, intensity: Lux) -> Self {
162        self.set_intensity(intensity);
163        self
164    }
165
166    /// Modify the intensity of the directional light.
167    pub fn modify_intensity<T: 'static>(&self, f: impl FnOnce(&mut Lux) -> T) -> T {
168        self.inner.descriptor.modify(|d| f(&mut d.intensity))
169    }
170
171    /// Get the intensity of the directional light.
172    pub fn intensity(&self) -> Lux {
173        self.inner.descriptor.get().intensity
174    }
175}
176
177/// A point light.
178///
179/// An analytical light that emits light in all directions from a single point.
180#[derive(Clone, Debug)]
181pub struct PointLight {
182    descriptor: Hybrid<PointLightDescriptor>,
183}
184
185impl IsLight for PointLight {
186    fn style(&self) -> LightStyle {
187        LightStyle::Point
188    }
189
190    fn light_space_transforms(
191        &self,
192        t: &TransformDescriptor,
193        // Near limits of the light's reach
194        //
195        // The maximum should be the `Camera`'s `Frustum::depth()`.
196        z_near: f32,
197        // Far limits of the light's reach
198        z_far: f32,
199    ) -> Vec<Mat4> {
200        let m = Mat4::from(*t);
201        let (p, vs) = self
202            .descriptor()
203            .shadow_mapping_projection_and_view_matrices(&m, z_near, z_far);
204        vs.into_iter().map(|v| p * v).collect()
205    }
206}
207
208impl PointLight {
209    /// Returns a pointer to the descriptor data on the GPU slab.
210    pub fn id(&self) -> Id<PointLightDescriptor> {
211        self.descriptor.id()
212    }
213
214    /// Returns a copy of the descriptor.
215    pub fn descriptor(&self) -> PointLightDescriptor {
216        self.descriptor.get()
217    }
218}
219
220/// A [`PointLight`] comes wrapped in [`AnalyticalLight`], giving the
221/// [`AnalyticalLight`] the ability to simulate lights that
222/// emit from a single point in space and attenuate exponentially with
223/// distance.
224impl AnalyticalLight<PointLight> {
225    /// Set the position of the point light.
226    pub fn set_position(&self, position: Vec3) -> &Self {
227        self.inner.descriptor.modify(|d| d.position = position);
228        self
229    }
230
231    /// Set the position and return the point light.
232    pub fn with_position(self, position: Vec3) -> Self {
233        self.set_position(position);
234        self
235    }
236
237    /// Modify the position of the point light.
238    pub fn modify_position<T: 'static>(&self, f: impl FnOnce(&mut Vec3) -> T) -> T {
239        self.inner.descriptor.modify(|d| f(&mut d.position))
240    }
241
242    /// Get the position of the point light.
243    pub fn position(&self) -> Vec3 {
244        self.inner.descriptor.get().position
245    }
246
247    /// Set the color of the point light.
248    pub fn set_color(&self, color: Vec4) -> &Self {
249        self.inner.descriptor.modify(|d| d.color = color);
250        self
251    }
252
253    /// Set the color and return the point light.
254    pub fn with_color(self, color: Vec4) -> Self {
255        self.set_color(color);
256        self
257    }
258
259    /// Modify the color of the point light.
260    pub fn modify_color<T: 'static>(&self, f: impl FnOnce(&mut Vec4) -> T) -> T {
261        self.inner.descriptor.modify(|d| f(&mut d.color))
262    }
263
264    /// Get the color of the point light.
265    pub fn color(&self) -> Vec4 {
266        self.inner.descriptor.get().color
267    }
268
269    /// Set the intensity of the point light.
270    pub fn set_intensity(&self, intensity: Candela) -> &Self {
271        self.inner.descriptor.modify(|d| d.intensity = intensity);
272        self
273    }
274
275    /// Set the intensity and return the point light.
276    pub fn with_intensity(self, intensity: Candela) -> Self {
277        self.set_intensity(intensity);
278        self
279    }
280
281    /// Modify the intensity of the point light.
282    pub fn modify_intensity<T: 'static>(&self, f: impl FnOnce(&mut Candela) -> T) -> T {
283        self.inner.descriptor.modify(|d| f(&mut d.intensity))
284    }
285
286    /// Get the intensity of the point light.
287    pub fn intensity(&self) -> Candela {
288        self.inner.descriptor.get().intensity
289    }
290}
291
292/// A spot light.
293///
294/// An analytical light that emits light in a cone shape.
295#[derive(Clone, Debug)]
296pub struct SpotLight {
297    descriptor: Hybrid<SpotLightDescriptor>,
298}
299
300impl IsLight for SpotLight {
301    fn style(&self) -> LightStyle {
302        LightStyle::Spot
303    }
304
305    fn light_space_transforms(
306        &self,
307        t: &TransformDescriptor,
308        // Near limits of the light's reach
309        //
310        // The maximum should be the `Camera`'s `Frustum::depth()`.
311        z_near: f32,
312        // Far limits of the light's reach
313        z_far: f32,
314    ) -> Vec<Mat4> {
315        let m = Mat4::from(*t);
316        vec![{
317            let (p, v) = self
318                .descriptor()
319                .shadow_mapping_projection_and_view(&m, z_near, z_far);
320            p * v
321        }]
322    }
323}
324
325impl SpotLight {
326    /// Returns a pointer to the descriptor data on the GPU slab.
327    pub fn id(&self) -> Id<SpotLightDescriptor> {
328        self.descriptor.id()
329    }
330
331    /// Returns a copy of the descriptor.
332    pub fn descriptor(&self) -> SpotLightDescriptor {
333        self.descriptor.get()
334    }
335}
336
337/// A [`SpotLight`] comes wrapped in [`AnalyticalLight`], giving the
338/// [`AnalyticalLight`] the ability to simulate lights that
339/// emit from a single point in space in a specific direction, with
340/// a specific spread.
341impl AnalyticalLight<SpotLight> {
342    /// Set the position of the spot light.
343    pub fn set_position(&self, position: Vec3) -> &Self {
344        self.inner.descriptor.modify(|d| d.position = position);
345        self
346    }
347
348    /// Set the position and return the spot light.
349    pub fn with_position(self, position: Vec3) -> Self {
350        self.set_position(position);
351        self
352    }
353
354    /// Modify the position of the spot light.
355    pub fn modify_position<T: 'static>(&self, f: impl FnOnce(&mut Vec3) -> T) -> T {
356        self.inner.descriptor.modify(|d| f(&mut d.position))
357    }
358
359    /// Get the position of the spot light.
360    pub fn position(&self) -> Vec3 {
361        self.inner.descriptor.get().position
362    }
363
364    /// Set the direction of the spot light.
365    pub fn set_direction(&self, direction: Vec3) -> &Self {
366        self.inner.descriptor.modify(|d| d.direction = direction);
367        self
368    }
369
370    /// Set the direction and return the spot light.
371    pub fn with_direction(self, direction: Vec3) -> Self {
372        self.set_direction(direction);
373        self
374    }
375
376    /// Modify the direction of the spot light.
377    pub fn modify_direction<T: 'static>(&self, f: impl FnOnce(&mut Vec3) -> T) -> T {
378        self.inner.descriptor.modify(|d| f(&mut d.direction))
379    }
380
381    /// Get the direction of the spot light.
382    pub fn direction(&self) -> Vec3 {
383        self.inner.descriptor.get().direction
384    }
385
386    /// Set the inner cutoff of the spot light.
387    pub fn set_inner_cutoff(&self, inner_cutoff: f32) -> &Self {
388        self.inner
389            .descriptor
390            .modify(|d| d.inner_cutoff = inner_cutoff);
391        self
392    }
393
394    /// Set the inner cutoff and return the spot light.
395    pub fn with_inner_cutoff(self, inner_cutoff: f32) -> Self {
396        self.set_inner_cutoff(inner_cutoff);
397        self
398    }
399
400    /// Modify the inner cutoff of the spot light.
401    pub fn modify_inner_cutoff<T: 'static>(&self, f: impl FnOnce(&mut f32) -> T) -> T {
402        self.inner.descriptor.modify(|d| f(&mut d.inner_cutoff))
403    }
404
405    /// Get the inner cutoff of the spot light.
406    pub fn inner_cutoff(&self) -> f32 {
407        self.inner.descriptor.get().inner_cutoff
408    }
409
410    /// Set the outer cutoff of the spot light.
411    pub fn set_outer_cutoff(&self, outer_cutoff: f32) -> &Self {
412        self.inner
413            .descriptor
414            .modify(|d| d.outer_cutoff = outer_cutoff);
415        self
416    }
417
418    /// Set the outer cutoff and return the spot light.
419    pub fn with_outer_cutoff(self, outer_cutoff: f32) -> Self {
420        self.set_outer_cutoff(outer_cutoff);
421        self
422    }
423
424    /// Modify the outer cutoff of the spot light.
425    pub fn modify_outer_cutoff<T: 'static>(&self, f: impl FnOnce(&mut f32) -> T) -> T {
426        self.inner.descriptor.modify(|d| f(&mut d.outer_cutoff))
427    }
428
429    /// Get the outer cutoff of the spot light.
430    pub fn outer_cutoff(&self) -> f32 {
431        self.inner.descriptor.get().outer_cutoff
432    }
433
434    /// Set the color of the spot light.
435    pub fn set_color(&self, color: Vec4) -> &Self {
436        self.inner.descriptor.modify(|d| d.color = color);
437        self
438    }
439
440    /// Set the color and return the spot light.
441    pub fn with_color(self, color: Vec4) -> Self {
442        self.set_color(color);
443        self
444    }
445
446    /// Modify the color of the spot light.
447    pub fn modify_color<T: 'static>(&self, f: impl FnOnce(&mut Vec4) -> T) -> T {
448        self.inner.descriptor.modify(|d| f(&mut d.color))
449    }
450
451    /// Get the color of the spot light.
452    pub fn color(&self) -> Vec4 {
453        self.inner.descriptor.get().color
454    }
455
456    /// Set the intensity of the spot light.
457    pub fn set_intensity(&self, intensity: Candela) -> &Self {
458        self.inner.descriptor.modify(|d| d.intensity = intensity);
459        self
460    }
461
462    /// Set the intensity and return the spot light.
463    pub fn with_intensity(self, intensity: Candela) -> Self {
464        self.set_intensity(intensity);
465        self
466    }
467
468    /// Modify the intensity of the spot light.
469    pub fn modify_intensity<T: 'static>(&self, f: impl FnOnce(&mut Candela) -> T) -> T {
470        self.inner.descriptor.modify(|d| f(&mut d.intensity))
471    }
472
473    /// Get the intensity of the spot light.
474    pub fn intensity(&self) -> Candela {
475        self.inner.descriptor.get().intensity
476    }
477}
478
479#[derive(Clone)]
480pub enum Light {
481    Directional(DirectionalLight),
482    Point(PointLight),
483    Spot(SpotLight),
484}
485
486impl From<DirectionalLight> for Light {
487    fn from(light: DirectionalLight) -> Self {
488        Light::Directional(light)
489    }
490}
491
492impl From<PointLight> for Light {
493    fn from(light: PointLight) -> Self {
494        Light::Point(light)
495    }
496}
497
498impl From<SpotLight> for Light {
499    fn from(light: SpotLight) -> Self {
500        Light::Spot(light)
501    }
502}
503
504impl IsLight for Light {
505    fn style(&self) -> LightStyle {
506        match self {
507            Light::Directional(light) => light.style(),
508            Light::Point(light) => light.style(),
509            Light::Spot(light) => light.style(),
510        }
511    }
512
513    fn light_space_transforms(
514        &self,
515        // Another transform applied to the light.
516        parent_transform: &TransformDescriptor,
517        // Near limits of the light's reach
518        //
519        // The maximum should be the `Camera`'s `Frustum::depth()`.
520        z_near: f32,
521        // Far limits of the light's reach
522        z_far: f32,
523    ) -> Vec<Mat4> {
524        match self {
525            Light::Directional(light) => {
526                light.light_space_transforms(parent_transform, z_near, z_far)
527            }
528            Light::Point(light) => light.light_space_transforms(parent_transform, z_near, z_far),
529            Light::Spot(light) => light.light_space_transforms(parent_transform, z_near, z_far),
530        }
531    }
532}
533
534/// A bundle of lighting resources representing one analytical light in a scene.
535///
536/// Create an [`AnalyticalLight`] with:
537/// * [`Stage::new_directional_light`]
538/// * [`Stage::new_point_light`]
539/// * [`Stage::new_spot_light`].
540///
541/// Lights may be added and removed from rendering with [`Stage::add_light`] and
542/// [`Stage::remove_light`].
543/// The GPU resources a light uses will not be released until [`Stage::remove_light`]
544/// is called _and_ the light is dropped.
545#[derive(Clone)]
546pub struct AnalyticalLight<T = Light> {
547    /// The generic light descriptor.
548    pub(crate) light_descriptor: Hybrid<LightDescriptor>,
549    /// The specific light.
550    inner: T,
551    /// The light's global transform.
552    ///
553    /// This value lives in the lighting slab.
554    transform: Transform,
555    /// The light's nested transform.
556    ///
557    /// This value comes from the light's node, if it belongs to one.
558    /// This may have been set if this light originated from a GLTF file.
559    /// This value lives on the geometry slab and must be referenced here
560    /// to keep the two in sync, which is required to animate lights.
561    node_transform: Arc<RwLock<Option<NestedTransform>>>,
562}
563
564impl<T: IsLight> core::fmt::Display for AnalyticalLight<T> {
565    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
566        f.write_fmt(format_args!(
567            "AnalyticalLight type={} light-id={:?} node-nested-transform-global-id:{:?}",
568            self.inner.style(),
569            self.light_descriptor.id(),
570            self.node_transform
571                .read()
572                .unwrap()
573                .as_ref()
574                .map(|h| h.global_id())
575        ))
576    }
577}
578
579impl<T: IsLight> IsLight for AnalyticalLight<T> {
580    fn style(&self) -> LightStyle {
581        self.inner.style()
582    }
583
584    fn light_space_transforms(
585        &self,
586        // Another transform applied to the light.
587        parent_transform: &TransformDescriptor,
588        // Near limits of the light's reach
589        //
590        // The maximum should be the `Camera`'s `Frustum::depth()`.
591        // TODO: in `DirectionalLightDescriptor::shadow_mapping_projection_and_view`, take Frustum
592        // as a parameter and then figure out the minimal view projection that includes that frustum
593        z_near: f32,
594        // Far limits of the light's reach
595        z_far: f32,
596    ) -> Vec<Mat4> {
597        self.inner
598            .light_space_transforms(parent_transform, z_near, z_far)
599    }
600}
601
602impl AnalyticalLight {
603    /// Returns a reference to the inner `DirectionalLight`, if this light is directional.
604    pub fn as_directional(&self) -> Option<&DirectionalLight> {
605        match &self.inner {
606            Light::Directional(light) => Some(light),
607            _ => None,
608        }
609    }
610
611    /// Returns a reference to the inner `PointLight`, if this light is a point light.
612    pub fn as_point(&self) -> Option<&PointLight> {
613        match &self.inner {
614            Light::Point(light) => Some(light),
615            _ => None,
616        }
617    }
618
619    /// Returns a reference to the inner `SpotLight`, if this light is a spot light.
620    pub fn as_spot(&self) -> Option<&SpotLight> {
621        match &self.inner {
622            Light::Spot(light) => Some(light),
623            _ => None,
624        }
625    }
626}
627
628impl<T: IsLight> AnalyticalLight<T> {
629    /// Returns a pointer to this light on the GPU
630    pub fn id(&self) -> Id<LightDescriptor> {
631        self.light_descriptor.id()
632    }
633
634    /// Returns a copy of the descriptor on the GPU.
635    pub fn descriptor(&self) -> LightDescriptor {
636        self.light_descriptor.get()
637    }
638
639    /// Link this light to a node's `NestedTransform`.
640    pub fn link_node_transform(&self, transform: &NestedTransform) {
641        *self.node_transform.write().unwrap() = Some(transform.clone());
642    }
643
644    /// Get a reference to the inner light.
645    pub fn inner(&self) -> &T {
646        &self.inner
647    }
648
649    /// Get a reference to the light's global transform.
650    ///
651    /// This value lives in the lighting slab.
652    ///
653    /// ## Note
654    /// If a [`NestedTransform`] has been linked to this light by using [`Self::link_node_transform`],
655    /// the transform returned by this function may be overwritten at any point by the given
656    /// [`NestedTransform`].
657    pub fn transform(&self) -> &Transform {
658        &self.transform
659    }
660
661    /// Get a reference to the light's linked global node transform.
662    ///
663    /// ## Note
664    /// The returned transform, if any, is the global transform of a linked `NestedTransform`.
665    /// To change this value, you should do so through the `NestedTransform`, which is likely
666    /// held in the
667    pub fn linked_node_transform(&self) -> Option<NestedTransform> {
668        self.node_transform
669            .read()
670            .unwrap()
671            .as_ref()
672            .map(|t| t.clone())
673    }
674
675    /// Convert this light into a generic light, hiding the specific light type that it is.
676    ///
677    /// This is useful if you want to store your lights together.
678    pub fn into_generic(self) -> AnalyticalLight
679    where
680        Light: From<T>,
681    {
682        let AnalyticalLight {
683            light_descriptor,
684            inner,
685            transform,
686            node_transform,
687        } = self;
688        let inner = Light::from(inner);
689        AnalyticalLight {
690            light_descriptor,
691            inner,
692            transform,
693            node_transform,
694        }
695    }
696}
697
698/// Manages lighting for an entire scene.
699#[derive(Clone)]
700pub struct Lighting {
701    pub(crate) geometry_slab: SlabAllocator<WgpuRuntime>,
702    pub(crate) light_slab: SlabAllocator<WgpuRuntime>,
703    pub(crate) light_slab_buffer: Arc<RwLock<SlabBuffer<wgpu::Buffer>>>,
704    pub(crate) geometry_slab_buffer: Arc<RwLock<SlabBuffer<wgpu::Buffer>>>,
705    pub(crate) lighting_descriptor: Hybrid<LightingDescriptor>,
706    pub(crate) analytical_lights: Arc<RwLock<Vec<AnalyticalLight>>>,
707    pub(crate) analytical_lights_array: Arc<RwLock<Option<HybridArray<Id<LightDescriptor>>>>>,
708    pub(crate) shadow_map_update_pipeline: Arc<wgpu::RenderPipeline>,
709    pub(crate) shadow_map_update_bindgroup_layout: Arc<wgpu::BindGroupLayout>,
710    pub(crate) shadow_map_update_blitter: AtlasBlitter,
711    pub(crate) shadow_map_atlas: Atlas,
712}
713
714pub struct LightingBindGroupLayoutEntries {
715    pub light_slab: wgpu::BindGroupLayoutEntry,
716    pub shadow_map_image: wgpu::BindGroupLayoutEntry,
717    pub shadow_map_sampler: wgpu::BindGroupLayoutEntry,
718}
719
720impl LightingBindGroupLayoutEntries {
721    pub fn new(starting_binding: u32) -> Self {
722        Self {
723            light_slab: wgpu::BindGroupLayoutEntry {
724                binding: starting_binding,
725                visibility: wgpu::ShaderStages::FRAGMENT,
726                ty: wgpu::BindingType::Buffer {
727                    ty: wgpu::BufferBindingType::Storage { read_only: true },
728                    has_dynamic_offset: false,
729                    min_binding_size: None,
730                },
731                count: None,
732            },
733            shadow_map_image: wgpu::BindGroupLayoutEntry {
734                binding: starting_binding + 1,
735                visibility: wgpu::ShaderStages::FRAGMENT,
736                ty: wgpu::BindingType::Texture {
737                    sample_type: wgpu::TextureSampleType::Float { filterable: false },
738                    view_dimension: wgpu::TextureViewDimension::D2Array,
739                    multisampled: false,
740                },
741                count: None,
742            },
743            shadow_map_sampler: wgpu::BindGroupLayoutEntry {
744                binding: starting_binding + 2,
745                visibility: wgpu::ShaderStages::FRAGMENT,
746                ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
747                count: None,
748            },
749        }
750    }
751}
752
753impl Lighting {
754    /// Create the atlas used to store all shadow maps.
755    fn create_shadow_map_atlas(
756        light_slab: &SlabAllocator<WgpuRuntime>,
757        size: wgpu::Extent3d,
758    ) -> Atlas {
759        let usage = wgpu::TextureUsages::RENDER_ATTACHMENT
760            | wgpu::TextureUsages::TEXTURE_BINDING
761            | wgpu::TextureUsages::COPY_SRC;
762        Atlas::new(
763            light_slab,
764            size,
765            Some(wgpu::TextureFormat::R32Float),
766            Some("shadow-map-atlas"),
767            Some(usage),
768        )
769    }
770
771    /// Create a new [`Lighting`] manager.
772    pub fn new(atlas_size: wgpu::Extent3d, geometry: &Geometry) -> Self {
773        let runtime = geometry.runtime();
774        let light_slab = SlabAllocator::new(runtime, "light-slab", wgpu::BufferUsages::empty());
775        let lighting_descriptor = light_slab.new_value(LightingDescriptor::default());
776        let light_slab_buffer = light_slab.commit();
777        let shadow_map_update_bindgroup_layout: Arc<_> =
778            ShadowMap::create_update_bindgroup_layout(&runtime.device).into();
779        let shadow_map_update_pipeline =
780            ShadowMap::create_update_pipeline(&runtime.device, &shadow_map_update_bindgroup_layout)
781                .into();
782        Self {
783            shadow_map_atlas: Self::create_shadow_map_atlas(&light_slab, atlas_size),
784            analytical_lights: Default::default(),
785            analytical_lights_array: Default::default(),
786            geometry_slab: geometry.slab_allocator().clone(),
787            light_slab,
788            light_slab_buffer: Arc::new(RwLock::new(light_slab_buffer)),
789            lighting_descriptor,
790            geometry_slab_buffer: Arc::new(RwLock::new(geometry.slab_allocator().commit())),
791            shadow_map_update_pipeline,
792            shadow_map_update_bindgroup_layout,
793            shadow_map_update_blitter: AtlasBlitter::new(
794                &runtime.device,
795                wgpu::TextureFormat::R32Float,
796                wgpu::FilterMode::Nearest,
797            ),
798        }
799    }
800
801    pub fn slab_allocator(&self) -> &SlabAllocator<WgpuRuntime> {
802        &self.light_slab
803    }
804
805    /// Add an [`AnalyticalLight`] to the internal list of lights.
806    ///
807    /// This is called implicitly by:
808    ///
809    /// * [`Lighting::new_directional_light`].
810    /// * [`Lighting::new_point_light`].
811    /// * [`Lighting::new_spot_light`].
812    ///
813    /// This can be used to add the light back to the scene after using
814    /// [`Lighting::remove_light`].
815    pub fn add_light<T>(&self, bundle: &AnalyticalLight<T>)
816    where
817        T: IsLight,
818        Light: From<T>,
819    {
820        log::trace!(
821            "adding light {:?} ({})",
822            bundle.light_descriptor.id(),
823            bundle.inner.style()
824        );
825        // Update our list of weakly ref'd light bundles
826        self.analytical_lights
827            .write()
828            .unwrap()
829            .push(bundle.clone().into_generic());
830        // Invalidate the array of lights
831        *self.analytical_lights_array.write().unwrap() = None;
832    }
833
834    /// Remove an [`AnalyticalLight`] from the internal list of lights.
835    ///
836    /// Use this to exclude a light from rendering, without dropping the light.
837    ///
838    /// After calling this function you can include the light again using [`Lighting::add_light`].
839    pub fn remove_light<T: IsLight>(&self, bundle: &AnalyticalLight<T>) {
840        log::trace!(
841            "removing light {:?} ({})",
842            bundle.light_descriptor.id(),
843            bundle.inner.style()
844        );
845        // Remove the light from the list of weakly ref'd light bundles
846        let mut guard = self.analytical_lights.write().unwrap();
847        guard.retain(|stored_light| {
848            stored_light.light_descriptor.id() != bundle.light_descriptor.id()
849        });
850        *self.analytical_lights_array.write().unwrap() = None;
851    }
852
853    /// Return an iterator over all lights.
854    pub fn lights(&self) -> Vec<AnalyticalLight> {
855        self.analytical_lights.read().unwrap().clone()
856    }
857
858    /// Create a new [`AnalyticalLight<DirectionalLight>`].
859    ///
860    /// The light is automatically added with [`Lighting::add_light`].
861    pub fn new_directional_light(&self) -> AnalyticalLight<DirectionalLight> {
862        let descriptor = self
863            .light_slab
864            .new_value(DirectionalLightDescriptor::default());
865        let transform = Transform::new(&self.light_slab);
866        let light_descriptor = self.light_slab.new_value({
867            let mut light = LightDescriptor::from(descriptor.id());
868            light.transform_id = transform.id();
869            light
870        });
871
872        let bundle = AnalyticalLight {
873            light_descriptor,
874            inner: DirectionalLight { descriptor },
875            transform,
876            node_transform: Default::default(),
877        };
878        self.add_light(&bundle);
879
880        bundle
881    }
882
883    /// Create a new [`AnalyticalLight<PointLight>`].
884    ///
885    /// The light is automatically added with [`Lighting::add_light`].
886    pub fn new_point_light(&self) -> AnalyticalLight<PointLight> {
887        let descriptor = self.light_slab.new_value(PointLightDescriptor::default());
888        let transform = Transform::new(&self.light_slab);
889        let light_descriptor = self.light_slab.new_value({
890            let mut light = LightDescriptor::from(descriptor.id());
891            light.transform_id = transform.id();
892            light
893        });
894
895        let bundle = AnalyticalLight {
896            light_descriptor,
897            inner: PointLight { descriptor },
898            transform,
899            node_transform: Default::default(),
900        };
901        self.add_light(&bundle);
902
903        bundle
904    }
905
906    /// Create a new [`AnalyticalLight<SpotLight>`].
907    ///
908    /// The light is automatically added with [`Lighting::add_light`].
909    pub fn new_spot_light(&self) -> AnalyticalLight<SpotLight> {
910        let descriptor = self.light_slab.new_value(SpotLightDescriptor::default());
911        let transform = Transform::new(&self.light_slab);
912        let light_descriptor = self.light_slab.new_value({
913            let mut light = LightDescriptor::from(descriptor.id());
914            light.transform_id = transform.id();
915            light
916        });
917
918        let bundle = AnalyticalLight {
919            light_descriptor,
920            inner: SpotLight { descriptor },
921            transform,
922            node_transform: Default::default(),
923        };
924        self.add_light(&bundle);
925
926        bundle
927    }
928
929    /// Enable shadow mapping for the given [`AnalyticalLight`], creating
930    /// a new [`ShadowMap`].
931    pub fn new_shadow_map<T>(
932        &self,
933        analytical_light_bundle: &AnalyticalLight<T>,
934        // Size of the shadow map
935        size: UVec2,
936        // Distance to the near plane of the shadow map's frustum.
937        //
938        // Only objects within the shadow map's frustum will cast shadows.
939        z_near: f32,
940        // Distance to the far plane of the shadow map's frustum
941        //
942        // Only objects within the shadow map's frustum will cast shadows.
943        z_far: f32,
944    ) -> Result<ShadowMap, LightingError>
945    where
946        T: IsLight,
947        Light: From<T>,
948    {
949        ShadowMap::new(self, analytical_light_bundle, size, z_near, z_far)
950    }
951
952    #[must_use]
953    pub fn commit(&self) -> SlabBuffer<wgpu::Buffer> {
954        log::trace!("committing lights");
955
956        // Sync any lights whose node transforms have changed
957        for light in self.analytical_lights.read().unwrap().iter() {
958            if let Some(node_transform) = light.node_transform.read().unwrap().as_ref() {
959                let global_node_transform = node_transform.global_descriptor();
960                if node_transform.global_descriptor() != light.transform.descriptor() {
961                    light.transform.set_descriptor(global_node_transform);
962                }
963            }
964        }
965
966        let lights_array = {
967            let mut array_guard = self.analytical_lights_array.write().unwrap();
968
969            // Create a new array if lights have been invalidated by
970            // `Lighting::add_light` or `Lighting::remove_light`
971            array_guard
972                .get_or_insert_with(|| {
973                    log::trace!("  analytical lights array was invalidated");
974                    let lights_guard = self.analytical_lights.read().unwrap();
975                    let new_lights = lights_guard
976                        .iter()
977                        .map(|bundle| bundle.light_descriptor.id());
978                    let array = self.light_slab.new_array(new_lights);
979                    log::trace!("  lights array is now: {:?}", array.array());
980                    array
981                })
982                .array()
983        };
984
985        self.lighting_descriptor.modify(
986            |LightingDescriptor {
987                 analytical_lights_array,
988                 shadow_map_atlas_descriptor_id,
989                 update_shadow_map_id,
990                 update_shadow_map_texture_index,
991                 // Don't change the tiling descriptor
992                 light_tiling_descriptor_id: _,
993             }| {
994                *analytical_lights_array = lights_array;
995                *shadow_map_atlas_descriptor_id = self.shadow_map_atlas.descriptor_id();
996                *update_shadow_map_id = Id::NONE;
997                *update_shadow_map_texture_index = 0;
998            },
999        );
1000
1001        let buffer = self.light_slab.commit();
1002        log::trace!("  light slab creation time: {}", buffer.creation_time());
1003        buffer
1004    }
1005}
1006
1007#[cfg(test)]
1008mod test;