Skip to main content

renderling/light/
shader.rs

1//! Shader functions for the lighting system.
2//!
3//! Directional lights are in lux, spot and point lights are in
4//! candelas. Conversion happens in the [PBR shader during radiance
5//! accumulation](crate::pbr::shader::shade_fragment).
6//!
7//! More info is here
8//! <https://www.realtimerendering.com/blog/physical-units-for-lights>.
9//!
10//! ## Note
11//!
12//! The glTF spec [1] says directional light is in lux, whereas spot and point
13//! are in candelas. The same goes for this library's shaders, but not a ton of
14//! work has gone into verifying that conversion from these units into
15//! radiometric units is accurate _in any way_. The shaders roughly do a
16//! conversion by dividing by 683 [2] or some other constant involving 683 [3].
17//!
18//! More work needs to be done here. PRs would be very appreciated.
19//!
20//! [1]: https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_lights_punctual/README.md
21//! [2]: https://depts.washington.edu/mictech/optics/me557/Radiometry.pdf
22//! [3]: https://projects.blender.org/blender/blender-addons/commit/9d903a93f03b
23use crabslab::{Array, Id, Slab, SlabItem};
24use glam::{Mat4, UVec2, UVec3, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles};
25#[cfg(gpu)]
26use spirv_std::num_traits::Float;
27use spirv_std::{spirv, Image};
28
29use crate::{
30    atlas::shader::{AtlasDescriptor, AtlasTextureDescriptor},
31    cubemap::shader::{CubemapDescriptor, CubemapFaceDirection},
32    geometry::shader::GeometryDescriptor,
33    math::{Fetch, IsSampler, IsVector, Sample2dArray},
34    primitive::shader::{PrimitiveDescriptor, VertexInfo},
35    transform::shader::TransformDescriptor,
36};
37
38#[derive(Clone, Copy, Default, SlabItem, core::fmt::Debug)]
39#[offsets]
40pub struct LightingDescriptor {
41    /// List of all analytical lights in the scene.
42    pub analytical_lights_array: Array<Id<LightDescriptor>>,
43    /// Shadow mapping atlas info.
44    pub shadow_map_atlas_descriptor_id: Id<AtlasDescriptor>,
45    /// `Id` of the [`ShadowMapDescriptor`] to use when updating
46    /// a shadow map.
47    ///
48    /// This changes from each run of the `shadow_mapping_vertex`.
49    pub update_shadow_map_id: Id<ShadowMapDescriptor>,
50    /// The index of the shadow map atlas texture to update.
51    pub update_shadow_map_texture_index: u32,
52    /// `Id` of the [`LightTilingDescriptor`] to use when performing
53    /// light tiling.
54    pub light_tiling_descriptor_id: Id<LightTilingDescriptor>,
55    /// Global ambient light color and intensity.
56    ///
57    /// XYZ components are the RGB color, W is the intensity.
58    /// The ambient term is added to the final shaded color,
59    /// modulated by the surface albedo and ambient occlusion.
60    ///
61    /// Defaults to `Vec4::ZERO` (no ambient contribution).
62    pub ambient_color: Vec4,
63}
64
65#[derive(Clone, Copy, SlabItem, core::fmt::Debug)]
66pub struct ShadowMapDescriptor {
67    pub light_space_transforms_array: Array<Mat4>,
68    /// Near plane of the projection matrix
69    pub z_near: f32,
70    /// Far plane of the projection matrix
71    pub z_far: f32,
72    /// Pointers to the atlas textures where the shadow map depth
73    /// data is stored.
74    ///
75    /// This will be an array of one `Id` for directional and spot lights,
76    /// and an array of four `Id`s for a point light.
77    pub atlas_textures_array: Array<Id<AtlasTextureDescriptor>>,
78    pub bias_min: f32,
79    pub bias_max: f32,
80    pub pcf_samples: u32,
81}
82
83impl Default for ShadowMapDescriptor {
84    fn default() -> Self {
85        Self {
86            light_space_transforms_array: Default::default(),
87            z_near: Default::default(),
88            z_far: Default::default(),
89            atlas_textures_array: Default::default(),
90            bias_min: 0.0005,
91            bias_max: 0.005,
92            pcf_samples: 4,
93        }
94    }
95}
96
97#[cfg(test)]
98#[derive(Default, Debug, Clone, Copy, PartialEq)]
99pub struct ShadowMappingVertexInfo {
100    pub renderlet_id: Id<PrimitiveDescriptor>,
101    pub vertex_index: u32,
102    pub vertex: crate::geometry::Vertex,
103    pub transform: TransformDescriptor,
104    pub model_matrix: Mat4,
105    pub world_pos: Vec3,
106    pub view_projection: Mat4,
107    pub clip_pos: Vec4,
108}
109
110/// Shadow mapping vertex shader.
111///
112/// It is assumed that a [`LightingDescriptor`] is stored at `Id(0)` of the
113/// `light_slab`.
114///
115/// This shader reads the [`LightingDescriptor`] to find the shadow map to
116/// be updated, then determines the clip positions to emit based on the
117/// shadow map's atlas texture.
118///
119/// It then renders the renderlet into the designated atlas frame.
120// Note:
121// If this is taking too long to render for each renderlet, think about
122// a frustum and occlusion culling pass to generate the list of renderlets.
123#[spirv(vertex)]
124#[allow(clippy::too_many_arguments)]
125pub fn shadow_mapping_vertex(
126    // Points at a `Renderlet`
127    #[spirv(instance_index)] renderlet_id: Id<PrimitiveDescriptor>,
128    // Which vertex within the renderlet are we rendering
129    #[spirv(vertex_index)] vertex_index: u32,
130    // The slab where the renderlet's geometry is staged
131    #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] geometry_slab: &[u32],
132    // The slab where the scene's lighting data is staged
133    #[spirv(storage_buffer, descriptor_set = 0, binding = 1)] light_slab: &[u32],
134
135    #[spirv(position)] out_clip_pos: &mut Vec4,
136    #[cfg(test)] out_comparison_info: &mut ShadowMappingVertexInfo,
137) {
138    let renderlet = geometry_slab.read_unchecked(renderlet_id);
139    if !renderlet.visible {
140        // put it outside the clipping frustum
141        *out_clip_pos = Vec4::new(100.0, 100.0, 100.0, 1.0);
142        return;
143    }
144
145    let VertexInfo {
146        world_pos,
147        vertex: _vertex,
148        transform: _transform,
149        model_matrix: _model_matrix,
150    } = renderlet.get_vertex_info(vertex_index, geometry_slab);
151
152    let lighting_desc = light_slab.read_unchecked(Id::<LightingDescriptor>::new(0));
153    let shadow_desc = light_slab.read_unchecked(lighting_desc.update_shadow_map_id);
154    let light_space_transform_id = shadow_desc
155        .light_space_transforms_array
156        .at(lighting_desc.update_shadow_map_texture_index as usize);
157    let light_space_transform = light_slab.read_unchecked(light_space_transform_id);
158    let clip_pos = light_space_transform * world_pos.extend(1.0);
159    #[cfg(test)]
160    {
161        *out_comparison_info = ShadowMappingVertexInfo {
162            renderlet_id,
163            vertex_index,
164            vertex: _vertex,
165            transform: _transform,
166            model_matrix: _model_matrix,
167            world_pos,
168            view_projection: light_space_transform,
169            clip_pos,
170        };
171    }
172    *out_clip_pos = clip_pos;
173}
174
175#[spirv(fragment)]
176pub fn shadow_mapping_fragment(clip_pos: Vec4, frag_color: &mut Vec4) {
177    *frag_color = (clip_pos.xyz() / clip_pos.w).extend(1.0);
178}
179
180/// Contains values needed to determine the outgoing radiance of a fragment.
181///
182/// For more info, see the **Spotlight** section of the
183/// [learnopengl](https://learnopengl.com/Lighting/Light-casters)
184/// article.
185#[derive(Clone, Copy, Default, core::fmt::Debug)]
186pub struct SpotLightCalculation {
187    /// Position of the light in world space
188    pub light_position: Vec3,
189    /// Position of the fragment in world space
190    pub frag_position: Vec3,
191    /// Unit vector (LightDir) pointing from the fragment to the light
192    pub frag_to_light: Vec3,
193    /// Distance from the fragment to the light
194    pub frag_to_light_distance: f32,
195    /// Unit vector (SpotDir) direction that the light is pointing in
196    pub light_direction: Vec3,
197    /// The cosine of the cutoff angle (Phi ϕ) that specifies the spotlight's
198    /// radius.
199    ///
200    /// Everything inside this angle is lit by the spotlight.
201    pub cos_inner_cutoff: f32,
202    /// The cosine of the cutoff angle (Gamma γ) that specifies the spotlight's
203    /// outer radius.
204    ///
205    /// Everything outside this angle is not lit by the spotlight.
206    ///
207    /// Fragments between `inner_cutoff` and `outer_cutoff` have an intensity
208    /// between `1.0` and `0.0`.
209    pub cos_outer_cutoff: f32,
210    /// Whether the fragment is inside the `inner_cutoff` cone.
211    pub fragment_is_inside_inner_cone: bool,
212    /// Whether the fragment is inside the `outer_cutoff` cone.
213    pub fragment_is_inside_outer_cone: bool,
214    /// `outer_cutoff` - `inner_cutoff`
215    pub epsilon: f32,
216    /// Cosine of the angle (Theta θ) between `frag_to_light` (LightDir) vector
217    /// and the `light_direction` (SpotDir) vector.
218    ///
219    /// θ  should be smaller than `outer_cutoff` (Gamma γ) to be
220    /// inside the spotlight, but since these are all cosines of angles, we
221    /// actually compare using `>`.
222    pub cos_theta: f32,
223    pub contribution_unclamped: f32,
224    /// The intensity level between `0.0` and `1.0` that should be used to
225    /// determine outgoing radiance.
226    pub contribution: f32,
227}
228
229impl SpotLightCalculation {
230    /// Calculate the values required to determine outgoing radiance of a spot
231    /// light.
232    pub fn new(
233        spot_light_descriptor: SpotLightDescriptor,
234        node_transform: Mat4,
235        fragment_world_position: Vec3,
236    ) -> Self {
237        let light_position = node_transform.transform_point3(spot_light_descriptor.position);
238        let frag_position = fragment_world_position;
239        let frag_to_light = light_position - frag_position;
240        let frag_to_light_distance = frag_to_light.length();
241        if frag_to_light_distance == 0.0 {
242            crate::println!("frag_to_light_distance: {frag_to_light_distance}");
243            return Self::default();
244        }
245        let frag_to_light = frag_to_light.alt_norm_or_zero();
246        let light_direction = node_transform
247            .transform_vector3(spot_light_descriptor.direction)
248            .alt_norm_or_zero();
249        let cos_inner_cutoff = spot_light_descriptor.inner_cutoff.cos();
250        let cos_outer_cutoff = spot_light_descriptor.outer_cutoff.cos();
251        let epsilon = cos_inner_cutoff - cos_outer_cutoff;
252        let cos_theta = frag_to_light.dot(-light_direction);
253        let fragment_is_inside_inner_cone = cos_theta > cos_inner_cutoff;
254        let fragment_is_inside_outer_cone = cos_theta > cos_outer_cutoff;
255        let contribution_unclamped = (cos_theta - cos_outer_cutoff) / epsilon;
256        let contribution = contribution_unclamped.clamp(0.0, 1.0);
257        Self {
258            light_position,
259            frag_position,
260            frag_to_light,
261            frag_to_light_distance,
262            light_direction,
263            cos_inner_cutoff,
264            cos_outer_cutoff,
265            fragment_is_inside_inner_cone,
266            fragment_is_inside_outer_cone,
267            epsilon,
268            cos_theta,
269            contribution_unclamped,
270            contribution,
271        }
272    }
273}
274
275/// Description of a spot light.
276///
277/// ## Tips
278///
279/// If your spotlight is not illuminating your scenery, ensure that the
280/// `inner_cutoff` and `outer_cutoff` values are "correct". `outer_cutoff`
281/// should be _greater than_ `inner_cutoff` and the values should be a large
282/// enough to cover at least one pixel at the distance between the light and
283/// the scenery.
284#[repr(C)]
285#[derive(Copy, Clone, SlabItem, core::fmt::Debug)]
286pub struct SpotLightDescriptor {
287    // TODO: add `range` to SpotLightDescriptor
288    // See <https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_lights_punctual/README.md#light-shared-properties>
289    pub position: Vec3,
290    pub direction: Vec3,
291    pub inner_cutoff: f32,
292    pub outer_cutoff: f32,
293    pub color: Vec4,
294    pub intensity: Candela,
295}
296
297impl Default for SpotLightDescriptor {
298    fn default() -> Self {
299        let white = Vec4::splat(1.0);
300        let inner_cutoff = 0.077143565;
301        let outer_cutoff = 0.09075713;
302        let direction = Vec3::new(0.0, -1.0, 0.0);
303        let color = white;
304        let intensity = Candela::COMMON_WAX_CANDLE;
305
306        Self {
307            position: Default::default(),
308            direction,
309            inner_cutoff,
310            outer_cutoff,
311            color,
312            intensity,
313        }
314    }
315}
316
317impl SpotLightDescriptor {
318    pub fn shadow_mapping_projection_and_view(
319        &self,
320        parent_light_transform: &Mat4,
321        z_near: f32,
322        z_far: f32,
323    ) -> (Mat4, Mat4) {
324        let fovy = 2.0 * self.outer_cutoff;
325        let aspect = 1.0;
326        let projection = Mat4::perspective_rh(fovy, aspect, z_near, z_far);
327        let direction = parent_light_transform
328            .transform_vector3(self.direction)
329            .alt_norm_or_zero();
330        let position = parent_light_transform.transform_point3(self.position);
331        let up = direction.orthonormal_vectors()[0];
332        let view = Mat4::look_to_rh(position, direction, up);
333        (projection, view)
334    }
335}
336
337/// A unit of luminous intensity, lumen per steradian (lm/sr).
338///
339/// Candelas measure the luminous power per unit solid angle emitted in a
340/// particular direction. A common wax candle has a luminous intensity of
341/// roughly 1 candela.
342///
343/// The type provides a collection of const `Candela` lighting levels for use in
344/// constructing analytical lights.
345#[repr(transparent)]
346#[derive(Clone, Copy, Default, Debug, SlabItem)]
347pub struct Candela(pub f32);
348
349impl core::fmt::Display for Candela {
350    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
351        self.0.fmt(f)?;
352        if self.0 == 1.0 {
353            f.write_str(" candela (lm/sr)")
354        } else {
355            f.write_str(" candelas (lm/sr)")
356        }
357    }
358}
359
360impl Candela {
361    pub const COMMON_WAX_CANDLE: Self = Candela(1.0);
362}
363
364/// A unit of illuminance, lumen per meter squared (lm/m^2).
365///
366/// Lux measures the amount of light that falls on a surface, which is
367/// appropriate for directional lights, as they simulate light coming from a
368/// specific direction, like sunlight.
369///
370/// The type provides a collection of const Lux lighting levels for use in
371/// constructing analytical lights.
372#[repr(transparent)]
373#[derive(Clone, Copy, Default, Debug, SlabItem)]
374pub struct Lux(pub f32);
375
376impl core::fmt::Display for Lux {
377    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
378        self.0.fmt(f)?;
379        f.write_str(" lux (lm/m^2)")
380    }
381}
382
383impl Lux {
384    pub const OUTDOOR_TWILIGHT: Self = Lux(1.0);
385    pub const OUTDOOR_STREET_LIGHT_MIN: Self = Lux(5.0);
386    pub const OUTDOOR_SUNSET: Self = Lux(10.0);
387    pub const INDOOR_LOUNGE: Self = Lux(50.0);
388    pub const INDOOR_HALLWAY: Self = Lux(80.0);
389    pub const OUTDOOR_OVERCAST_LOW: Self = Lux(100.0);
390    pub const INDOOR_OFFICE_LOW: Self = Lux(320.0);
391    pub const OUTDOOR_SUNRISE_OR_SUNSET: Self = Lux(400.0);
392    pub const INDOOR_OFFICE_HIGH: Self = Lux(500.0);
393    pub const OUTDOOR_OVERCAST_HIGH: Self = Lux(1000.0);
394    pub const OUTDOOR_FOXS_WEDDING: Self = Lux(3000.0);
395    pub const OUTDOOR_FULL_DAYLIGHT_LOW: Self = Lux(10_000.0);
396    pub const OUTDOOR_FULL_DAYLIGHT_HIGH: Self = Lux(25_000.0);
397    pub const OUTDOOR_DIRECT_SUNLIGHT_LOW: Self = Lux(32_000.0);
398    pub const OUTDOOR_DIRECT_SUNLIGHT_HIGH: Self = Lux(130_000.0);
399}
400
401#[repr(C)]
402#[derive(Copy, Clone, SlabItem, Debug)]
403pub struct DirectionalLightDescriptor {
404    pub direction: Vec3,
405    pub color: Vec4,
406    /// Intensity of the directional light in lux (lm/m^2).
407    pub intensity: Lux,
408}
409
410impl Default for DirectionalLightDescriptor {
411    fn default() -> Self {
412        let direction = Vec3::new(0.0, -1.0, 0.0);
413        let color = Vec4::splat(1.0);
414        let intensity = Lux::OUTDOOR_TWILIGHT;
415
416        Self {
417            direction,
418            color,
419            intensity,
420        }
421    }
422}
423
424impl DirectionalLightDescriptor {
425    pub fn shadow_mapping_projection_and_view(
426        &self,
427        parent_light_transform: &Mat4,
428        // Near limits of the light's reach
429        //
430        // The maximum should be the `Camera`'s `Frustum::depth()`.
431        // TODO: in `DirectionalLightDescriptor::shadow_mapping_projection_and_view`, take Frustum
432        // as a parameter and then figure out the minimal view projection that includes that
433        // frustum
434        z_near: f32,
435        // Far limits of the light's reach
436        z_far: f32,
437    ) -> (Mat4, Mat4) {
438        crate::println!("descriptor: {self:#?}");
439        let depth = (z_far - z_near).abs();
440        let hd = depth * 0.5;
441        let projection = Mat4::orthographic_rh(-hd, hd, -hd, hd, z_near, z_far);
442        let direction = parent_light_transform
443            .transform_vector3(self.direction)
444            .alt_norm_or_zero();
445        let position = -direction * depth * 0.5;
446        crate::println!("direction: {direction}");
447        crate::println!("position: {position}");
448        let up = direction.orthonormal_vectors()[0];
449        let view = Mat4::look_to_rh(position, direction, up);
450        (projection, view)
451    }
452}
453
454#[repr(C)]
455#[derive(Copy, Clone, SlabItem, core::fmt::Debug)]
456pub struct PointLightDescriptor {
457    // TODO: add `range` to PointLightDescriptor
458    // See <https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_lights_punctual/README.md#light-shared-properties>
459    pub position: Vec3,
460    pub color: Vec4,
461    /// Intensity as candelas.
462    pub intensity: Candela,
463}
464
465impl Default for PointLightDescriptor {
466    fn default() -> Self {
467        let color = Vec4::splat(1.0);
468        let intensity = Candela::COMMON_WAX_CANDLE;
469
470        Self {
471            position: Default::default(),
472            color,
473            intensity,
474        }
475    }
476}
477
478impl PointLightDescriptor {
479    pub fn shadow_mapping_view_matrix(
480        &self,
481        face_index: usize,
482        parent_light_transform: &Mat4,
483    ) -> Mat4 {
484        let eye = parent_light_transform.transform_point3(self.position);
485        let mut face = CubemapFaceDirection::FACES[face_index];
486        face.eye = eye;
487        face.view()
488    }
489
490    pub fn shadow_mapping_projection_matrix(z_near: f32, z_far: f32) -> Mat4 {
491        Mat4::perspective_lh(core::f32::consts::FRAC_PI_2, 1.0, z_near, z_far)
492    }
493
494    pub fn shadow_mapping_projection_and_view_matrices(
495        &self,
496        parent_light_transform: &Mat4,
497        z_near: f32,
498        z_far: f32,
499    ) -> (Mat4, [Mat4; 6]) {
500        let p = Self::shadow_mapping_projection_matrix(z_near, z_far);
501        let eye = parent_light_transform.transform_point3(self.position);
502        (
503            p,
504            CubemapFaceDirection::FACES.map(|mut face| {
505                face.eye = eye;
506                face.view()
507            }),
508        )
509    }
510}
511
512/// Returns the radius of illumination in meters.
513///
514/// * Moonlight: < 1 lux.
515///   - Full moon on a clear night: 0.25 lux.
516///   - Quarter moon: 0.01 lux
517///   - Starlight overcast moonless night sky: 0.0001 lux.
518/// * General indoor lighting: Around 100 to 300 lux.
519/// * Office lighting: Typically around 300 to 500 lux.
520/// * Reading or task lighting: Around 500 to 750 lux.
521/// * Detailed work (e.g., drafting, surgery): 1000 lux or more.
522pub fn radius_of_illumination(intensity_candelas: f32, minimum_illuminance_lux: f32) -> f32 {
523    (intensity_candelas / minimum_illuminance_lux).sqrt()
524}
525
526#[repr(u32)]
527#[derive(Copy, Clone, PartialEq, core::fmt::Debug)]
528pub enum LightStyle {
529    Directional = 0,
530    Point = 1,
531    Spot = 2,
532}
533
534impl core::fmt::Display for LightStyle {
535    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
536        match self {
537            LightStyle::Directional => f.write_str("directional"),
538            LightStyle::Point => f.write_str("point"),
539            LightStyle::Spot => f.write_str("spot"),
540        }
541    }
542}
543
544impl SlabItem for LightStyle {
545    const SLAB_SIZE: usize = { 1 };
546
547    fn read_slab(index: usize, slab: &[u32]) -> Self {
548        let proxy = u32::read_slab(index, slab);
549        match proxy {
550            0 => LightStyle::Directional,
551            1 => LightStyle::Point,
552            2 => LightStyle::Spot,
553            _ => LightStyle::Directional,
554        }
555    }
556
557    fn write_slab(&self, index: usize, slab: &mut [u32]) -> usize {
558        let proxy = *self as u32;
559        proxy.write_slab(index, slab)
560    }
561}
562
563/// A generic light that is used as a slab pointer to a
564/// specific light type.
565#[repr(C)]
566#[derive(Copy, Clone, PartialEq, SlabItem, core::fmt::Debug)]
567pub struct LightDescriptor {
568    /// The type of the light
569    pub light_type: LightStyle,
570    /// The index of the light in the lighting slab
571    pub index: u32,
572    /// The id of a transform to apply to the position and direction of the
573    /// light.
574    ///
575    /// This `Id` points to a transform on the lighting slab.
576    ///
577    /// The value of this descriptor can be synchronized with that of a node
578    /// transform on the geometry slab from
579    /// [`crate::light::AnalyticalLight::link_node_transform`].
580    pub transform_id: Id<TransformDescriptor>,
581    /// The id of the shadow map in use by this light.
582    pub shadow_map_desc_id: Id<ShadowMapDescriptor>,
583}
584
585impl Default for LightDescriptor {
586    fn default() -> Self {
587        Self {
588            light_type: LightStyle::Directional,
589            index: Id::<()>::NONE.inner(),
590            transform_id: Id::NONE,
591            shadow_map_desc_id: Id::NONE,
592        }
593    }
594}
595
596impl From<Id<DirectionalLightDescriptor>> for LightDescriptor {
597    fn from(id: Id<DirectionalLightDescriptor>) -> Self {
598        Self {
599            light_type: LightStyle::Directional,
600            index: id.inner(),
601            transform_id: Id::NONE,
602            shadow_map_desc_id: Id::NONE,
603        }
604    }
605}
606
607impl From<Id<SpotLightDescriptor>> for LightDescriptor {
608    fn from(id: Id<SpotLightDescriptor>) -> Self {
609        Self {
610            light_type: LightStyle::Spot,
611            index: id.inner(),
612            transform_id: Id::NONE,
613            shadow_map_desc_id: Id::NONE,
614        }
615    }
616}
617
618impl From<Id<PointLightDescriptor>> for LightDescriptor {
619    fn from(id: Id<PointLightDescriptor>) -> Self {
620        Self {
621            light_type: LightStyle::Point,
622            index: id.inner(),
623            transform_id: Id::NONE,
624            shadow_map_desc_id: Id::NONE,
625        }
626    }
627}
628
629impl LightDescriptor {
630    pub fn into_directional_id(self) -> Id<DirectionalLightDescriptor> {
631        Id::from(self.index)
632    }
633
634    pub fn into_spot_id(self) -> Id<SpotLightDescriptor> {
635        Id::from(self.index)
636    }
637
638    pub fn into_point_id(self) -> Id<PointLightDescriptor> {
639        Id::from(self.index)
640    }
641}
642
643/// Parameters to the shadow mapping calculation function.
644///
645/// This is mostly just to appease clippy.
646pub struct ShadowCalculation {
647    pub shadow_map_desc: ShadowMapDescriptor,
648    pub shadow_map_atlas_size: UVec2,
649    pub surface_normal_in_world_space: Vec3,
650    pub frag_pos_in_world_space: Vec3,
651    pub frag_to_light_in_world_space: Vec3,
652    pub bias_min: f32,
653    pub bias_max: f32,
654    pub pcf_samples: u32,
655}
656
657impl ShadowCalculation {
658    /// Reads various required parameters from the slab and creates a
659    /// `ShadowCalculation`.
660    pub fn new(
661        light_slab: &[u32],
662        light: LightDescriptor,
663        in_pos: Vec3,
664        surface_normal: Vec3,
665        light_direction: Vec3,
666    ) -> Self {
667        let shadow_map_desc = light_slab.read_unchecked(light.shadow_map_desc_id);
668        let atlas_size = {
669            let lighting_desc_id = Id::<LightingDescriptor>::new(0);
670            let atlas_desc_id = light_slab.read_unchecked(
671                lighting_desc_id + LightingDescriptor::OFFSET_OF_SHADOW_MAP_ATLAS_DESCRIPTOR_ID,
672            );
673            let atlas_desc = light_slab.read_unchecked(atlas_desc_id);
674            atlas_desc.size
675        };
676
677        ShadowCalculation {
678            shadow_map_desc,
679            shadow_map_atlas_size: atlas_size.xy(),
680            surface_normal_in_world_space: surface_normal,
681            frag_pos_in_world_space: in_pos,
682            frag_to_light_in_world_space: light_direction,
683            bias_min: shadow_map_desc.bias_min,
684            bias_max: shadow_map_desc.bias_max,
685            pcf_samples: shadow_map_desc.pcf_samples,
686        }
687    }
688
689    fn get_atlas_texture_at(&self, light_slab: &[u32], index: usize) -> AtlasTextureDescriptor {
690        let atlas_texture_id =
691            light_slab.read_unchecked(self.shadow_map_desc.atlas_textures_array.at(index));
692        light_slab.read_unchecked(atlas_texture_id)
693    }
694
695    fn get_frag_pos_in_light_space(&self, light_slab: &[u32], index: usize) -> Vec3 {
696        let light_space_transform_id = self.shadow_map_desc.light_space_transforms_array.at(index);
697        let light_space_transform = light_slab.read_unchecked(light_space_transform_id);
698        light_space_transform.project_point3(self.frag_pos_in_world_space)
699    }
700
701    /// Returns shadow _intensity_ for directional and spot lights.
702    ///
703    /// Returns `0.0` when the fragment is in full light.
704    /// Returns `1.0` when the fragment is in full shadow.
705    pub fn run_directional_or_spot<T, S>(
706        &self,
707        light_slab: &[u32],
708        shadow_map: &T,
709        shadow_map_sampler: &S,
710    ) -> f32
711    where
712        S: IsSampler,
713        T: Sample2dArray<Sampler = S>,
714    {
715        let ShadowCalculation {
716            shadow_map_desc: _,
717            shadow_map_atlas_size,
718            frag_pos_in_world_space: _,
719            surface_normal_in_world_space: surface_normal,
720            frag_to_light_in_world_space: light_direction,
721            bias_min,
722            bias_max,
723            pcf_samples,
724        } = self;
725        let frag_pos_in_light_space = self.get_frag_pos_in_light_space(light_slab, 0);
726        crate::println!("frag_pos_in_light_space: {frag_pos_in_light_space}");
727        if !crate::math::is_inside_clip_space(frag_pos_in_light_space.xyz()) {
728            return 0.0;
729        }
730        // The range of coordinates in the light's clip space is -1.0 to 1.0 for
731        // x and y, but the texture space is [0, 1], and Y increases
732        // downward, so we do this conversion to flip Y and also
733        // normalize to the range [0.0, 1.0]. Z should already be 0.0 to
734        // 1.0.
735        let proj_coords_uv = (frag_pos_in_light_space.xy() * Vec2::new(1.0, -1.0)
736            + Vec2::splat(1.0))
737            * Vec2::splat(0.5);
738        crate::println!("proj_coords_uv: {proj_coords_uv}");
739
740        let shadow_map_atlas_texture = self.get_atlas_texture_at(light_slab, 0);
741        // With these projected coordinates we can sample the depth map as the
742        // resulting [0,1] coordinates from proj_coords directly correspond to
743        // the transformed NDC coordinates from the `ShadowMap::update` render
744        // pass. This gives us the closest depth from the light's point
745        // of view:
746        let pcf_samples_2 = *pcf_samples as i32 / 2;
747        let texel_size = 1.0
748            / Vec2::new(
749                shadow_map_atlas_texture.size_px.x as f32,
750                shadow_map_atlas_texture.size_px.y as f32,
751            );
752        let mut shadow = 0.0f32;
753        let mut total = 0.0f32;
754        for x in -pcf_samples_2..=pcf_samples_2 {
755            for y in -pcf_samples_2..=pcf_samples_2 {
756                let proj_coords = shadow_map_atlas_texture.uv(
757                    proj_coords_uv + Vec2::new(x as f32, y as f32) * texel_size,
758                    *shadow_map_atlas_size,
759                );
760                let shadow_map_depth = shadow_map
761                    .sample_by_lod(*shadow_map_sampler, proj_coords, 0.0)
762                    .x;
763                // To get the current depth at this fragment we simply retrieve
764                // the projected vector's z coordinate which
765                // equals the depth of this fragment
766                // from the light's perspective.
767                let fragment_depth = frag_pos_in_light_space.z;
768
769                // If the `current_depth`, which is the depth of the fragment
770                // from the lights POV, is greater than the
771                // `closest_depth` of the shadow map at
772                // that fragment, the fragment is in shadow
773                crate::println!("current_depth: {fragment_depth}");
774                crate::println!("closest_depth: {shadow_map_depth}");
775                let bias = (bias_max * (1.0 - surface_normal.dot(*light_direction))).max(*bias_min);
776
777                if (fragment_depth - bias) >= shadow_map_depth {
778                    shadow += 1.0
779                }
780                total += 1.0;
781            }
782        }
783        shadow / total.max(1.0)
784    }
785
786    pub const POINT_SAMPLE_OFFSET_DIRECTIONS: [Vec3; 21] = [
787        Vec3::ZERO,
788        Vec3::new(1.0, 1.0, 1.0),
789        Vec3::new(1.0, -1.0, 1.0),
790        Vec3::new(-1.0, -1.0, 1.0),
791        Vec3::new(-1.0, 1.0, 1.0),
792        Vec3::new(1.0, 1.0, -1.0),
793        Vec3::new(1.0, -1.0, -1.0),
794        Vec3::new(-1.0, -1.0, -1.0),
795        Vec3::new(-1.0, 1.0, -1.0),
796        Vec3::new(1.0, 1.0, 0.0),
797        Vec3::new(1.0, -1.0, 0.0),
798        Vec3::new(-1.0, -1.0, 0.0),
799        Vec3::new(-1.0, 1.0, 0.0),
800        Vec3::new(1.0, 0.0, 1.0),
801        Vec3::new(-1.0, 0.0, 1.0),
802        Vec3::new(1.0, 0.0, -1.0),
803        Vec3::new(-1.0, 0.0, -1.0),
804        Vec3::new(0.0, 1.0, 1.0),
805        Vec3::new(0.0, -1.0, 1.0),
806        Vec3::new(0.0, -1.0, -1.0),
807        Vec3::new(0.0, 1.0, -1.0),
808    ];
809    /// Returns shadow _intensity_ for point lights.
810    ///
811    /// Returns `0.0` when the fragment is in full light.
812    /// Returns `1.0` when the fragment is in full shadow.
813    pub fn run_point<T, S>(
814        &self,
815        light_slab: &[u32],
816        shadow_map: &T,
817        shadow_map_sampler: &S,
818        light_pos_in_world_space: Vec3,
819    ) -> f32
820    where
821        S: IsSampler,
822        T: Sample2dArray<Sampler = S>,
823    {
824        let ShadowCalculation {
825            shadow_map_desc,
826            shadow_map_atlas_size,
827            frag_pos_in_world_space,
828            surface_normal_in_world_space: surface_normal,
829            frag_to_light_in_world_space: frag_to_light,
830            bias_min,
831            bias_max,
832            pcf_samples,
833        } = self;
834
835        let light_to_frag_dir = frag_pos_in_world_space - light_pos_in_world_space;
836        crate::println!("light_to_frag_dir: {light_to_frag_dir}");
837
838        let pcf_samplesf = (*pcf_samples as f32)
839            .max(1.0)
840            .min(Self::POINT_SAMPLE_OFFSET_DIRECTIONS.len() as f32);
841        let pcf_samples = pcf_samplesf as usize;
842        let view_distance = light_to_frag_dir.length();
843        let disk_radius = (1.0 + view_distance / shadow_map_desc.z_far) / 25.0;
844        let mut shadow = 0.0f32;
845        for i in 0..pcf_samples {
846            let sample_offset = Self::POINT_SAMPLE_OFFSET_DIRECTIONS[i] * disk_radius;
847            crate::println!("sample_offset: {sample_offset}");
848            let sample_dir = (light_to_frag_dir + sample_offset).alt_norm_or_zero();
849            let (face_index, uv) = CubemapDescriptor::get_face_index_and_uv(sample_dir);
850            crate::println!("face_index: {face_index}",);
851            crate::println!("uv: {uv}");
852            let frag_pos_in_light_space = self.get_frag_pos_in_light_space(light_slab, face_index);
853            let face_texture = self.get_atlas_texture_at(light_slab, face_index);
854            let uv_tex = face_texture.uv(uv, *shadow_map_atlas_size);
855            let shadow_map_depth = shadow_map.sample_by_lod(*shadow_map_sampler, uv_tex, 0.0).x;
856            let fragment_depth = frag_pos_in_light_space.z;
857            let bias = (bias_max * (1.0 - surface_normal.dot(*frag_to_light))).max(*bias_min);
858            if (fragment_depth - bias) > shadow_map_depth {
859                shadow += 1.0
860            }
861        }
862
863        shadow / pcf_samplesf
864    }
865}
866
867/// Depth pre-pass for the light tiling feature.
868///
869/// This shader writes all staged [`PrimitiveDescriptor`]'s depth into a buffer.
870///
871/// This shader is very much like [`shadow_mapping_vertex`], except that
872/// shader gets its projection+view matrix from the light stored in a
873/// `ShadowMapDescriptor`.
874///
875/// Here we want to render as normal forward pass would, with the
876/// `PrimitiveDescriptor` and the `Camera`'s view projection matrix.
877/// ## Note
878/// This shader will likely be expanded to include parts of occlusion culling
879/// and order independent transparency.
880#[spirv(vertex)]
881pub fn light_tiling_depth_pre_pass(
882    // Points at a `Renderlet`.
883    #[spirv(instance_index)] renderlet_id: Id<PrimitiveDescriptor>,
884    // Which vertex within the renderlet are we rendering?
885    #[spirv(vertex_index)] vertex_index: u32,
886    // The slab where the renderlet's geometry is staged
887    #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] geometry_slab: &[u32],
888    // Output clip coords
889    #[spirv(position)] out_clip_pos: &mut Vec4,
890) {
891    let renderlet = geometry_slab.read_unchecked(renderlet_id);
892    if !renderlet.visible {
893        // put it outside the clipping frustum
894        *out_clip_pos = Vec3::splat(100.0).extend(1.0);
895        return;
896    }
897
898    let camera_id = geometry_slab
899        .read_unchecked(Id::<GeometryDescriptor>::new(0) + GeometryDescriptor::OFFSET_OF_CAMERA_ID);
900    let camera = geometry_slab.read_unchecked(camera_id);
901
902    let VertexInfo { world_pos, .. } = renderlet.get_vertex_info(vertex_index, geometry_slab);
903
904    *out_clip_pos = camera.view_projection() * world_pos.extend(1.0);
905}
906
907pub type DepthImage2d = Image!(2D, type=f32, sampled, depth);
908
909pub type DepthImage2dMultisampled = Image!(2D, type=f32, sampled, depth, multisampled=true);
910
911/// A tile of screen space used to cull lights.
912#[derive(Clone, Copy, Default, SlabItem)]
913#[offsets]
914pub struct LightTile {
915    /// Minimum depth of objects found within the frustum of the tile.
916    pub depth_min: u32,
917    /// Maximum depth of objects foudn within the frustum of the tile.
918    pub depth_max: u32,
919    /// The count of lights in this tile.
920    ///
921    /// Also, the next available light index.
922    pub next_light_index: u32,
923    /// List of light ids that intersect this tile's frustum.
924    pub lights_array: Array<Id<LightDescriptor>>,
925}
926
927impl core::fmt::Debug for LightTile {
928    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
929        f.debug_struct("LightTile")
930            .field("depth_min", &dequantize_depth_u32_to_f32(self.depth_min))
931            .field("depth_max", &dequantize_depth_u32_to_f32(self.depth_max))
932            .field("next_light_index", &self.next_light_index)
933            .field("lights_array", &self.lights_array)
934            .finish()
935    }
936}
937
938/// Descriptor of the light tiling operation, which culls lights by accumulating
939/// them into lists that illuminate tiles of the screen.
940#[derive(Clone, Copy, SlabItem, core::fmt::Debug)]
941pub struct LightTilingDescriptor {
942    /// Size of the [`Stage`](crate::stage::Stage)'s depth texture.
943    pub depth_texture_size: UVec2,
944    /// Configurable tile size.
945    pub tile_size: u32,
946    /// Array pointing to the lighting "tiles".
947    pub tiles_array: Array<LightTile>,
948    /// Minimum illuminance.
949    ///
950    /// Used to determine whether a light illuminates a tile.
951    pub minimum_illuminance_lux: f32,
952}
953
954impl Default for LightTilingDescriptor {
955    fn default() -> Self {
956        Self {
957            depth_texture_size: Default::default(),
958            tile_size: 16,
959            tiles_array: Default::default(),
960            minimum_illuminance_lux: 0.1,
961        }
962    }
963}
964
965impl LightTilingDescriptor {
966    /// Returns the dimensions of the grid of tiles.
967    pub fn tile_grid_size(&self) -> UVec2 {
968        let dims_f32 = self.depth_texture_size.as_vec2() / self.tile_size as f32;
969        dims_f32.ceil().as_uvec2()
970    }
971
972    pub fn tile_coord_for_fragment(&self, frag_coord: Vec2) -> UVec2 {
973        let frag_coord = frag_coord.as_uvec2();
974        frag_coord / self.tile_size
975    }
976
977    pub fn tile_index_for_fragment(&self, frag_coord: Vec2) -> usize {
978        let tile_coord = self.tile_coord_for_fragment(frag_coord);
979        let tile_dimensions = self.tile_grid_size();
980        (tile_coord.y * tile_dimensions.x + tile_coord.x) as usize
981    }
982}
983
984/// Quantizes a fragment depth from `f32` to `u32`.
985pub fn quantize_depth_f32_to_u32(depth: f32) -> u32 {
986    (u32::MAX as f32 * depth).round() as u32
987}
988
989/// Reconstructs a previously quantized depth from a `u32`.
990pub fn dequantize_depth_u32_to_f32(depth: u32) -> f32 {
991    depth as f32 / u32::MAX as f32
992}
993
994/// Helper for determining the next light to check during an
995/// invocation of the light list computation.
996pub(crate) struct NextLightIndex {
997    current_step: usize,
998    tile_size: u32,
999    lights: Array<Id<LightDescriptor>>,
1000    global_id: UVec3,
1001}
1002
1003impl Iterator for NextLightIndex {
1004    type Item = Id<Id<LightDescriptor>>;
1005
1006    fn next(&mut self) -> Option<Self::Item> {
1007        let next_index = self.next_index();
1008        self.current_step += 1;
1009        if next_index < self.lights.len() {
1010            Some(self.lights.at(next_index))
1011        } else {
1012            None
1013        }
1014    }
1015}
1016
1017impl NextLightIndex {
1018    pub fn new(
1019        global_id: UVec3,
1020        tile_size: u32,
1021        analytical_lights_array: Array<Id<LightDescriptor>>,
1022    ) -> Self {
1023        Self {
1024            current_step: 0,
1025            tile_size,
1026            lights: analytical_lights_array,
1027            global_id,
1028        }
1029    }
1030
1031    pub fn next_index(&self) -> usize {
1032        // Determine the xy coord of this invocation within the _tile_
1033        let frag_tile_xy = self.global_id.xy() % self.tile_size;
1034        // Determine the index of this invocation within the _tile_
1035        let offset = frag_tile_xy.y * self.tile_size + frag_tile_xy.x;
1036        let stride = (self.tile_size * self.tile_size) as usize;
1037        self.current_step * stride + offset as usize
1038    }
1039}
1040
1041struct LightTilingInvocation {
1042    global_id: UVec3,
1043    descriptor: LightTilingDescriptor,
1044}
1045
1046impl LightTilingInvocation {
1047    fn new(global_id: UVec3, descriptor: LightTilingDescriptor) -> Self {
1048        Self {
1049            global_id,
1050            descriptor,
1051        }
1052    }
1053
1054    /// The fragment's position.
1055    ///
1056    /// X range is 0 to (width - 1), Y range is 0 to (height - 1).
1057    fn frag_pos(&self) -> UVec2 {
1058        self.global_id.xy()
1059    }
1060
1061    /// The number of tiles in X and Y within the depth texture.
1062    fn tile_grid_size(&self) -> UVec2 {
1063        self.descriptor.tile_grid_size()
1064    }
1065
1066    /// The tile's coordinate among all tiles in the tile grid.
1067    ///
1068    /// The units are in tile x y.
1069    fn tile_coord(&self) -> UVec2 {
1070        self.global_id.xy() / self.descriptor.tile_size
1071    }
1072
1073    /// The tile's index in all the [`LightTilingDescriptor`]'s `tile_array`.
1074    fn tile_index(&self) -> usize {
1075        let tile_pos = self.tile_coord();
1076        let tile_dimensions = self.tile_grid_size();
1077        (tile_pos.y * tile_dimensions.x + tile_pos.x) as usize
1078    }
1079
1080    /// The tile's normalized midpoint.
1081    fn tile_ndc_midpoint(&self) -> Vec2 {
1082        let min_coord = self.tile_coord().as_vec2();
1083        let mid_coord = min_coord + 0.5;
1084        crate::math::convert_pixel_to_ndc(mid_coord, self.tile_grid_size())
1085    }
1086
1087    /// Compute the min and max depth of one fragment/invocation for light
1088    /// tiling.
1089    ///
1090    /// The min and max is stored in a tile on lighting slab.
1091    fn compute_min_and_max_depth(
1092        &self,
1093        depth_texture: &impl Fetch<UVec2, Output = Vec4>,
1094        lighting_slab: &mut [u32],
1095    ) {
1096        let frag_pos = self.frag_pos();
1097        let depth_texture_size = self.descriptor.depth_texture_size;
1098        if frag_pos.x >= depth_texture_size.x || frag_pos.y >= depth_texture_size.y {
1099            return;
1100        }
1101        // Depth frag value at the fragment position
1102        let frag_depth: f32 = depth_texture.fetch(frag_pos).x;
1103        // Fragment depth scaled to min/max of u32 values
1104        //
1105        // This is so we can compare with normal atomic ops instead of using the
1106        // float extension
1107        let frag_depth_u32 = quantize_depth_f32_to_u32(frag_depth);
1108
1109        // The tile's index in all the tiles
1110        let tile_index = self.tile_index();
1111        let lighting_desc = lighting_slab.read_unchecked(Id::<LightingDescriptor>::new(0));
1112        let tiling_desc = lighting_slab.read_unchecked(lighting_desc.light_tiling_descriptor_id);
1113        // index of the tile's min depth atomic value in the lighting slab
1114        let tile_id = tiling_desc.tiles_array.at(tile_index);
1115        let min_depth_index = tile_id + LightTile::OFFSET_OF_DEPTH_MIN;
1116        // index of the tile's max depth atomic value in the lighting slab
1117        let max_depth_index = tile_id + LightTile::OFFSET_OF_DEPTH_MAX;
1118
1119        let _prev_min_depth = crate::sync::atomic_u_min::<
1120            { spirv_std::memory::Scope::Workgroup as u32 },
1121            { spirv_std::memory::Semantics::WORKGROUP_MEMORY.bits() },
1122        >(lighting_slab, min_depth_index, frag_depth_u32);
1123        let _prev_max_depth = crate::sync::atomic_u_max::<
1124            { spirv_std::memory::Scope::Workgroup as u32 },
1125            { spirv_std::memory::Semantics::WORKGROUP_MEMORY.bits() },
1126        >(lighting_slab, max_depth_index, frag_depth_u32);
1127    }
1128
1129    /// Determine whether this invocation should run.
1130    fn should_invoke(&self) -> bool {
1131        self.global_id.x < self.descriptor.depth_texture_size.x
1132            && self.global_id.y < self.descriptor.depth_texture_size.y
1133    }
1134
1135    /// Clears one tile.
1136    ///
1137    /// ## Note
1138    /// This is only valid to call from the [`light_tiling_clear_tiles`] shader.
1139    fn clear_tile(&self, lighting_slab: &mut [u32]) {
1140        let dimensions = self.tile_grid_size();
1141        let index = (self.global_id.y * dimensions.x + self.global_id.x) as usize;
1142        if index < self.descriptor.tiles_array.len() {
1143            let tile_id = self.descriptor.tiles_array.at(index);
1144            let mut tile = lighting_slab.read(tile_id);
1145            tile.depth_min = u32::MAX;
1146            tile.depth_max = 0;
1147            tile.next_light_index = 0;
1148            lighting_slab.write(tile_id, &tile);
1149            // Zero out the light list and the ratings
1150            for id in tile.lights_array.iter() {
1151                lighting_slab.write(id, &Id::NONE);
1152            }
1153        }
1154    }
1155
1156    // The difficulty here is that in SPIRV we can access `lighting_slab`
1157    // atomically without wrapping it in a type, but on CPU we must pass an
1158    // array of (something like) `AtomicU32`. I'm not sure how to model this
1159    // interaction to test it on the CPU.
1160    fn compute_light_lists(&self, geometry_slab: &[u32], lighting_slab: &mut [u32]) {
1161        let index = self.tile_index();
1162        let tile_id = self.descriptor.tiles_array.at(index);
1163        // Construct the tile's frustum in clip space.
1164        let depth_min_u32 = lighting_slab.read_unchecked(tile_id + LightTile::OFFSET_OF_DEPTH_MIN);
1165        let depth_max_u32 = lighting_slab.read_unchecked(tile_id + LightTile::OFFSET_OF_DEPTH_MAX);
1166        let depth_min = dequantize_depth_u32_to_f32(depth_min_u32);
1167        let depth_max = dequantize_depth_u32_to_f32(depth_max_u32);
1168
1169        if depth_min == depth_max {
1170            // If we would construct a frustum with zero volume, abort.
1171            //
1172            // See <http://renderling.xyz/articles/live/light_tiling.html#zero-volume-frustum-optimization>
1173            // for more info.
1174            return;
1175        }
1176
1177        let camera_id = geometry_slab.read_unchecked(
1178            Id::<GeometryDescriptor>::new(0) + GeometryDescriptor::OFFSET_OF_CAMERA_ID,
1179        );
1180        let camera = geometry_slab.read_unchecked(camera_id);
1181
1182        // let (ndc_tile_min, ndc_tile_max) = self.tile_ndc_min_max();
1183        // // This is the AABB frustum, in NDC coords
1184        // let ndc_tile_aabb = Aabb::new(
1185        //     ndc_tile_min.extend(depth_min),
1186        //     ndc_tile_max.extend(depth_max),
1187        // );
1188
1189        // Get the frustum (here simplified to a line) in world coords, since
1190        // we'll be using it to compare against the radius of
1191        // illumination of each light
1192        let tile_ndc_midpoint = self.tile_ndc_midpoint();
1193        let tile_line_ndc = (
1194            tile_ndc_midpoint.extend(depth_min),
1195            tile_ndc_midpoint.extend(depth_max),
1196        );
1197        let inverse_viewproj = camera.view_projection().inverse();
1198        let tile_line = (
1199            inverse_viewproj.project_point3(tile_line_ndc.0),
1200            inverse_viewproj.project_point3(tile_line_ndc.1),
1201        );
1202
1203        let tile_index = self.tile_index();
1204        let tile_id = self.descriptor.tiles_array.at(tile_index);
1205        let tile_lights_array = lighting_slab.read(tile_id + LightTile::OFFSET_OF_LIGHTS_ARRAY);
1206        let next_light_id = tile_id + LightTile::OFFSET_OF_NEXT_LIGHT_INDEX;
1207
1208        // List of all analytical lights in the scene
1209        let analytical_lights_array = lighting_slab.read_unchecked(
1210            Id::<LightingDescriptor>::new(0)
1211                + LightingDescriptor::OFFSET_OF_ANALYTICAL_LIGHTS_ARRAY,
1212        );
1213
1214        // Each invocation will calculate a few lights' contribution to the
1215        // tile, until all lights have been visited
1216        let next_light = NextLightIndex::new(
1217            self.global_id,
1218            self.descriptor.tile_size,
1219            analytical_lights_array,
1220        );
1221        for id_of_light_id in next_light {
1222            let light_id = lighting_slab.read_unchecked(id_of_light_id);
1223            let light = lighting_slab.read_unchecked(light_id);
1224            let transform = lighting_slab.read(light.transform_id);
1225            // Get the distance to the light in world coords, and the
1226            // intensity of the light.
1227            let (distance, intensity_candelas) = match light.light_type {
1228                LightStyle::Directional => {
1229                    let directional_light = lighting_slab.read(light.into_directional_id());
1230                    // Very hand-wavey conversion
1231                    (0.0, directional_light.intensity.0 / 683.0)
1232                }
1233                LightStyle::Point => {
1234                    let point_light = lighting_slab.read(light.into_point_id());
1235                    let center = Mat4::from(transform).transform_point3(point_light.position);
1236                    let distance = crate::math::distance_to_line(center, tile_line.0, tile_line.1);
1237                    // Again, very hand-wavey
1238                    (distance, point_light.intensity.0 / 683.0)
1239                }
1240                LightStyle::Spot => {
1241                    // TODO: take into consideration the direction the spot
1242                    // light is pointing
1243                    let spot_light = lighting_slab.read(light.into_spot_id());
1244                    let center = Mat4::from(transform).transform_point3(spot_light.position);
1245                    let distance = crate::math::distance_to_line(center, tile_line.0, tile_line.1);
1246                    // Again, very hand-wavey
1247                    (distance, spot_light.intensity.0 / 683.0)
1248                }
1249            };
1250
1251            let radius =
1252                radius_of_illumination(intensity_candelas, self.descriptor.minimum_illuminance_lux);
1253            let should_add = radius >= distance;
1254            if should_add {
1255                // If the light should be added to the bin, get the next
1256                // available index in the bin, then write the id
1257                // of the light into that index.
1258                let next_index = crate::sync::atomic_i_increment::<
1259                    { spirv_std::memory::Scope::Workgroup as u32 },
1260                    { spirv_std::memory::Semantics::WORKGROUP_MEMORY.bits() },
1261                >(lighting_slab, next_light_id);
1262                if next_index as usize >= tile_lights_array.len() {
1263                    // We've already filled the bin, so abort.
1264                    //
1265                    // TODO: Figure out a better way to handle light tile list
1266                    // overrun.
1267                    break;
1268                } else {
1269                    // Get the id that corresponds to the next available index
1270                    // in the ratings bin
1271                    let binned_light_id = tile_lights_array.at(next_index as usize);
1272                    // Write to that location
1273                    lighting_slab.write(binned_light_id, &light_id);
1274                }
1275            }
1276        }
1277    }
1278}
1279
1280#[spirv(compute(threads(16, 16, 1)))]
1281pub fn light_tiling_clear_tiles(
1282    #[spirv(storage_buffer, descriptor_set = 0, binding = 1)] lighting_slab: &mut [u32],
1283    #[spirv(global_invocation_id)] global_id: UVec3,
1284) {
1285    let lighting_descriptor = lighting_slab.read(Id::<LightingDescriptor>::new(0));
1286    let light_tiling_descriptor =
1287        lighting_slab.read(lighting_descriptor.light_tiling_descriptor_id);
1288    let invocation = LightTilingInvocation::new(global_id, light_tiling_descriptor);
1289    invocation.clear_tile(lighting_slab);
1290}
1291
1292/// Compute the min and max depth value for a tile.
1293///
1294/// This shader must be called **once for each fragment in the depth texture**.
1295#[spirv(compute(threads(16, 16, 1)))]
1296pub fn light_tiling_compute_tile_min_and_max_depth(
1297    #[spirv(storage_buffer, descriptor_set = 0, binding = 1)] lighting_slab: &mut [u32],
1298    #[spirv(descriptor_set = 0, binding = 2)] depth_texture: &DepthImage2d,
1299    #[spirv(global_invocation_id)] global_id: UVec3,
1300) {
1301    let lighting_descriptor = lighting_slab.read(Id::<LightingDescriptor>::new(0));
1302    let light_tiling_descriptor =
1303        lighting_slab.read(lighting_descriptor.light_tiling_descriptor_id);
1304    let invocation = LightTilingInvocation::new(global_id, light_tiling_descriptor);
1305    invocation.compute_min_and_max_depth(depth_texture, lighting_slab);
1306}
1307
1308/// Compute the min and max depth value for a tile, multisampled.
1309///
1310/// This shader must be called **once for each fragment in the depth texture**.
1311#[spirv(compute(threads(16, 16, 1)))]
1312pub fn light_tiling_compute_tile_min_and_max_depth_multisampled(
1313    #[spirv(storage_buffer, descriptor_set = 0, binding = 1)] lighting_slab: &mut [u32],
1314    #[spirv(descriptor_set = 0, binding = 2)] depth_texture: &DepthImage2dMultisampled,
1315    #[spirv(global_invocation_id)] global_id: UVec3,
1316) {
1317    let lighting_descriptor = lighting_slab.read(Id::<LightingDescriptor>::new(0));
1318    let light_tiling_descriptor =
1319        lighting_slab.read(lighting_descriptor.light_tiling_descriptor_id);
1320    let invocation = LightTilingInvocation::new(global_id, light_tiling_descriptor);
1321    invocation.compute_min_and_max_depth(depth_texture, lighting_slab);
1322}
1323
1324#[spirv(compute(threads(16, 16, 1)))]
1325pub fn light_tiling_bin_lights(
1326    #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] geometry_slab: &[u32],
1327    #[spirv(storage_buffer, descriptor_set = 0, binding = 1)] lighting_slab: &mut [u32],
1328    #[spirv(global_invocation_id)] global_id: UVec3,
1329) {
1330    let lighting_descriptor = lighting_slab.read(Id::<LightingDescriptor>::new(0));
1331    let light_tiling_descriptor =
1332        lighting_slab.read(lighting_descriptor.light_tiling_descriptor_id);
1333    let invocation = LightTilingInvocation::new(global_id, light_tiling_descriptor);
1334    if invocation.should_invoke() {
1335        invocation.compute_light_lists(geometry_slab, lighting_slab);
1336    }
1337}