Skip to main content

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