bevy_pbr/render/
light.rs

1use self::assign::ClusterableObjectType;
2use crate::material_bind_groups::MaterialBindGroupAllocator;
3use crate::*;
4use bevy_asset::UntypedAssetId;
5use bevy_color::ColorToComponents;
6use bevy_core_pipeline::core_3d::{Camera3d, CORE_3D_DEPTH_FORMAT};
7use bevy_derive::{Deref, DerefMut};
8use bevy_ecs::component::Tick;
9use bevy_ecs::system::SystemChangeTick;
10use bevy_ecs::{
11    entity::{EntityHashMap, EntityHashSet},
12    prelude::*,
13    system::lifetimeless::Read,
14};
15use bevy_math::{ops, Mat4, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles};
16use bevy_platform::collections::{HashMap, HashSet};
17use bevy_platform::hash::FixedHasher;
18use bevy_render::experimental::occlusion_culling::{
19    OcclusionCulling, OcclusionCullingSubview, OcclusionCullingSubviewEntities,
20};
21use bevy_render::sync_world::MainEntityHashMap;
22use bevy_render::{
23    batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport},
24    camera::SortedCameras,
25    mesh::allocator::MeshAllocator,
26    view::{NoIndirectDrawing, RetainedViewEntity},
27};
28use bevy_render::{
29    diagnostic::RecordDiagnostics,
30    mesh::RenderMesh,
31    primitives::{CascadesFrusta, CubemapFrusta, Frustum, HalfSpace},
32    render_asset::RenderAssets,
33    render_graph::{Node, NodeRunError, RenderGraphContext},
34    render_phase::*,
35    render_resource::*,
36    renderer::{RenderContext, RenderDevice, RenderQueue},
37    texture::*,
38    view::{ExtractedView, RenderLayers, ViewVisibility},
39    Extract,
40};
41use bevy_render::{
42    mesh::allocator::SlabId,
43    sync_world::{MainEntity, RenderEntity},
44};
45use bevy_transform::{components::GlobalTransform, prelude::Transform};
46use bevy_utils::default;
47use core::{hash::Hash, marker::PhantomData, ops::Range};
48#[cfg(feature = "trace")]
49use tracing::info_span;
50use tracing::{error, warn};
51
52#[derive(Component)]
53pub struct ExtractedPointLight {
54    pub color: LinearRgba,
55    /// luminous intensity in lumens per steradian
56    pub intensity: f32,
57    pub range: f32,
58    pub radius: f32,
59    pub transform: GlobalTransform,
60    pub shadows_enabled: bool,
61    pub shadow_depth_bias: f32,
62    pub shadow_normal_bias: f32,
63    pub shadow_map_near_z: f32,
64    pub spot_light_angles: Option<(f32, f32)>,
65    pub volumetric: bool,
66    pub soft_shadows_enabled: bool,
67    /// whether this point light contributes diffuse light to lightmapped meshes
68    pub affects_lightmapped_mesh_diffuse: bool,
69}
70
71#[derive(Component, Debug)]
72pub struct ExtractedDirectionalLight {
73    pub color: LinearRgba,
74    pub illuminance: f32,
75    pub transform: GlobalTransform,
76    pub shadows_enabled: bool,
77    pub volumetric: bool,
78    /// whether this directional light contributes diffuse light to lightmapped
79    /// meshes
80    pub affects_lightmapped_mesh_diffuse: bool,
81    pub shadow_depth_bias: f32,
82    pub shadow_normal_bias: f32,
83    pub cascade_shadow_config: CascadeShadowConfig,
84    pub cascades: EntityHashMap<Vec<Cascade>>,
85    pub frusta: EntityHashMap<Vec<Frustum>>,
86    pub render_layers: RenderLayers,
87    pub soft_shadow_size: Option<f32>,
88    /// True if this light is using two-phase occlusion culling.
89    pub occlusion_culling: bool,
90}
91
92// NOTE: These must match the bit flags in bevy_pbr/src/render/mesh_view_types.wgsl!
93bitflags::bitflags! {
94    #[repr(transparent)]
95    struct PointLightFlags: u32 {
96        const SHADOWS_ENABLED                   = 1 << 0;
97        const SPOT_LIGHT_Y_NEGATIVE             = 1 << 1;
98        const VOLUMETRIC                        = 1 << 2;
99        const AFFECTS_LIGHTMAPPED_MESH_DIFFUSE  = 1 << 3;
100        const NONE                              = 0;
101        const UNINITIALIZED                     = 0xFFFF;
102    }
103}
104
105#[derive(Copy, Clone, ShaderType, Default, Debug)]
106pub struct GpuDirectionalCascade {
107    clip_from_world: Mat4,
108    texel_size: f32,
109    far_bound: f32,
110}
111
112#[derive(Copy, Clone, ShaderType, Default, Debug)]
113pub struct GpuDirectionalLight {
114    cascades: [GpuDirectionalCascade; MAX_CASCADES_PER_LIGHT],
115    color: Vec4,
116    dir_to_light: Vec3,
117    flags: u32,
118    soft_shadow_size: f32,
119    shadow_depth_bias: f32,
120    shadow_normal_bias: f32,
121    num_cascades: u32,
122    cascades_overlap_proportion: f32,
123    depth_texture_base_index: u32,
124    skip: u32,
125}
126
127// NOTE: These must match the bit flags in bevy_pbr/src/render/mesh_view_types.wgsl!
128bitflags::bitflags! {
129    #[repr(transparent)]
130    struct DirectionalLightFlags: u32 {
131        const SHADOWS_ENABLED                   = 1 << 0;
132        const VOLUMETRIC                        = 1 << 1;
133        const AFFECTS_LIGHTMAPPED_MESH_DIFFUSE  = 1 << 2;
134        const NONE                              = 0;
135        const UNINITIALIZED                     = 0xFFFF;
136    }
137}
138
139#[derive(Copy, Clone, Debug, ShaderType)]
140pub struct GpuLights {
141    directional_lights: [GpuDirectionalLight; MAX_DIRECTIONAL_LIGHTS],
142    ambient_color: Vec4,
143    // xyz are x/y/z cluster dimensions and w is the number of clusters
144    cluster_dimensions: UVec4,
145    // xy are vec2<f32>(cluster_dimensions.xy) / vec2<f32>(view.width, view.height)
146    // z is cluster_dimensions.z / log(far / near)
147    // w is cluster_dimensions.z * log(near) / log(far / near)
148    cluster_factors: Vec4,
149    n_directional_lights: u32,
150    // offset from spot light's light index to spot light's shadow map index
151    spot_light_shadowmap_offset: i32,
152    ambient_light_affects_lightmapped_meshes: u32,
153}
154
155// NOTE: When running bevy on Adreno GPU chipsets in WebGL, any value above 1 will result in a crash
156// when loading the wgsl "pbr_functions.wgsl" in the function apply_fog.
157#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
158pub const MAX_DIRECTIONAL_LIGHTS: usize = 1;
159#[cfg(any(
160    not(feature = "webgl"),
161    not(target_arch = "wasm32"),
162    feature = "webgpu"
163))]
164pub const MAX_DIRECTIONAL_LIGHTS: usize = 10;
165#[cfg(any(
166    not(feature = "webgl"),
167    not(target_arch = "wasm32"),
168    feature = "webgpu"
169))]
170pub const MAX_CASCADES_PER_LIGHT: usize = 4;
171#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
172pub const MAX_CASCADES_PER_LIGHT: usize = 1;
173
174#[derive(Resource, Clone)]
175pub struct ShadowSamplers {
176    pub point_light_comparison_sampler: Sampler,
177    #[cfg(feature = "experimental_pbr_pcss")]
178    pub point_light_linear_sampler: Sampler,
179    pub directional_light_comparison_sampler: Sampler,
180    #[cfg(feature = "experimental_pbr_pcss")]
181    pub directional_light_linear_sampler: Sampler,
182}
183
184// TODO: this pattern for initializing the shaders / pipeline isn't ideal. this should be handled by the asset system
185impl FromWorld for ShadowSamplers {
186    fn from_world(world: &mut World) -> Self {
187        let render_device = world.resource::<RenderDevice>();
188
189        let base_sampler_descriptor = SamplerDescriptor {
190            address_mode_u: AddressMode::ClampToEdge,
191            address_mode_v: AddressMode::ClampToEdge,
192            address_mode_w: AddressMode::ClampToEdge,
193            mag_filter: FilterMode::Linear,
194            min_filter: FilterMode::Linear,
195            mipmap_filter: FilterMode::Nearest,
196            ..default()
197        };
198
199        ShadowSamplers {
200            point_light_comparison_sampler: render_device.create_sampler(&SamplerDescriptor {
201                compare: Some(CompareFunction::GreaterEqual),
202                ..base_sampler_descriptor
203            }),
204            #[cfg(feature = "experimental_pbr_pcss")]
205            point_light_linear_sampler: render_device.create_sampler(&base_sampler_descriptor),
206            directional_light_comparison_sampler: render_device.create_sampler(
207                &SamplerDescriptor {
208                    compare: Some(CompareFunction::GreaterEqual),
209                    ..base_sampler_descriptor
210                },
211            ),
212            #[cfg(feature = "experimental_pbr_pcss")]
213            directional_light_linear_sampler: render_device
214                .create_sampler(&base_sampler_descriptor),
215        }
216    }
217}
218
219pub fn extract_lights(
220    mut commands: Commands,
221    point_light_shadow_map: Extract<Res<PointLightShadowMap>>,
222    directional_light_shadow_map: Extract<Res<DirectionalLightShadowMap>>,
223    global_point_lights: Extract<Res<GlobalVisibleClusterableObjects>>,
224    point_lights: Extract<
225        Query<(
226            Entity,
227            RenderEntity,
228            &PointLight,
229            &CubemapVisibleEntities,
230            &GlobalTransform,
231            &ViewVisibility,
232            &CubemapFrusta,
233            Option<&VolumetricLight>,
234        )>,
235    >,
236    spot_lights: Extract<
237        Query<(
238            Entity,
239            RenderEntity,
240            &SpotLight,
241            &VisibleMeshEntities,
242            &GlobalTransform,
243            &ViewVisibility,
244            &Frustum,
245            Option<&VolumetricLight>,
246        )>,
247    >,
248    directional_lights: Extract<
249        Query<
250            (
251                Entity,
252                RenderEntity,
253                &DirectionalLight,
254                &CascadesVisibleEntities,
255                &Cascades,
256                &CascadeShadowConfig,
257                &CascadesFrusta,
258                &GlobalTransform,
259                &ViewVisibility,
260                Option<&RenderLayers>,
261                Option<&VolumetricLight>,
262                Has<OcclusionCulling>,
263            ),
264            Without<SpotLight>,
265        >,
266    >,
267    mapper: Extract<Query<RenderEntity>>,
268    mut previous_point_lights_len: Local<usize>,
269    mut previous_spot_lights_len: Local<usize>,
270) {
271    // NOTE: These shadow map resources are extracted here as they are used here too so this avoids
272    // races between scheduling of ExtractResourceSystems and this system.
273    if point_light_shadow_map.is_changed() {
274        commands.insert_resource(point_light_shadow_map.clone());
275    }
276    if directional_light_shadow_map.is_changed() {
277        commands.insert_resource(directional_light_shadow_map.clone());
278    }
279    // This is the point light shadow map texel size for one face of the cube as a distance of 1.0
280    // world unit from the light.
281    // point_light_texel_size = 2.0 * 1.0 * tan(PI / 4.0) / cube face width in texels
282    // PI / 4.0 is half the cube face fov, tan(PI / 4.0) = 1.0, so this simplifies to:
283    // point_light_texel_size = 2.0 / cube face width in texels
284    // NOTE: When using various PCF kernel sizes, this will need to be adjusted, according to:
285    // https://catlikecoding.com/unity/tutorials/custom-srp/point-and-spot-shadows/
286    let point_light_texel_size = 2.0 / point_light_shadow_map.size as f32;
287
288    let mut point_lights_values = Vec::with_capacity(*previous_point_lights_len);
289    for entity in global_point_lights.iter().copied() {
290        let Ok((
291            main_entity,
292            render_entity,
293            point_light,
294            cubemap_visible_entities,
295            transform,
296            view_visibility,
297            frusta,
298            volumetric_light,
299        )) = point_lights.get(entity)
300        else {
301            continue;
302        };
303        if !view_visibility.get() {
304            continue;
305        }
306        let render_cubemap_visible_entities = RenderCubemapVisibleEntities {
307            data: cubemap_visible_entities
308                .iter()
309                .map(|v| create_render_visible_mesh_entities(&mapper, v))
310                .collect::<Vec<_>>()
311                .try_into()
312                .unwrap(),
313        };
314
315        let extracted_point_light = ExtractedPointLight {
316            color: point_light.color.into(),
317            // NOTE: Map from luminous power in lumens to luminous intensity in lumens per steradian
318            // for a point light. See https://google.github.io/filament/Filament.html#mjx-eqn-pointLightLuminousPower
319            // for details.
320            intensity: point_light.intensity / (4.0 * core::f32::consts::PI),
321            range: point_light.range,
322            radius: point_light.radius,
323            transform: *transform,
324            shadows_enabled: point_light.shadows_enabled,
325            shadow_depth_bias: point_light.shadow_depth_bias,
326            // The factor of SQRT_2 is for the worst-case diagonal offset
327            shadow_normal_bias: point_light.shadow_normal_bias
328                * point_light_texel_size
329                * core::f32::consts::SQRT_2,
330            shadow_map_near_z: point_light.shadow_map_near_z,
331            spot_light_angles: None,
332            volumetric: volumetric_light.is_some(),
333            affects_lightmapped_mesh_diffuse: point_light.affects_lightmapped_mesh_diffuse,
334            #[cfg(feature = "experimental_pbr_pcss")]
335            soft_shadows_enabled: point_light.soft_shadows_enabled,
336            #[cfg(not(feature = "experimental_pbr_pcss"))]
337            soft_shadows_enabled: false,
338        };
339        point_lights_values.push((
340            render_entity,
341            (
342                extracted_point_light,
343                render_cubemap_visible_entities,
344                (*frusta).clone(),
345                MainEntity::from(main_entity),
346            ),
347        ));
348    }
349    *previous_point_lights_len = point_lights_values.len();
350    commands.try_insert_batch(point_lights_values);
351
352    let mut spot_lights_values = Vec::with_capacity(*previous_spot_lights_len);
353    for entity in global_point_lights.iter().copied() {
354        if let Ok((
355            main_entity,
356            render_entity,
357            spot_light,
358            visible_entities,
359            transform,
360            view_visibility,
361            frustum,
362            volumetric_light,
363        )) = spot_lights.get(entity)
364        {
365            if !view_visibility.get() {
366                continue;
367            }
368            let render_visible_entities =
369                create_render_visible_mesh_entities(&mapper, visible_entities);
370
371            let texel_size =
372                2.0 * ops::tan(spot_light.outer_angle) / directional_light_shadow_map.size as f32;
373
374            spot_lights_values.push((
375                render_entity,
376                (
377                    ExtractedPointLight {
378                        color: spot_light.color.into(),
379                        // NOTE: Map from luminous power in lumens to luminous intensity in lumens per steradian
380                        // for a point light. See https://google.github.io/filament/Filament.html#mjx-eqn-pointLightLuminousPower
381                        // for details.
382                        // Note: Filament uses a divisor of PI for spot lights. We choose to use the same 4*PI divisor
383                        // in both cases so that toggling between point light and spot light keeps lit areas lit equally,
384                        // which seems least surprising for users
385                        intensity: spot_light.intensity / (4.0 * core::f32::consts::PI),
386                        range: spot_light.range,
387                        radius: spot_light.radius,
388                        transform: *transform,
389                        shadows_enabled: spot_light.shadows_enabled,
390                        shadow_depth_bias: spot_light.shadow_depth_bias,
391                        // The factor of SQRT_2 is for the worst-case diagonal offset
392                        shadow_normal_bias: spot_light.shadow_normal_bias
393                            * texel_size
394                            * core::f32::consts::SQRT_2,
395                        shadow_map_near_z: spot_light.shadow_map_near_z,
396                        spot_light_angles: Some((spot_light.inner_angle, spot_light.outer_angle)),
397                        volumetric: volumetric_light.is_some(),
398                        affects_lightmapped_mesh_diffuse: spot_light
399                            .affects_lightmapped_mesh_diffuse,
400                        #[cfg(feature = "experimental_pbr_pcss")]
401                        soft_shadows_enabled: spot_light.soft_shadows_enabled,
402                        #[cfg(not(feature = "experimental_pbr_pcss"))]
403                        soft_shadows_enabled: false,
404                    },
405                    render_visible_entities,
406                    *frustum,
407                    MainEntity::from(main_entity),
408                ),
409            ));
410        }
411    }
412    *previous_spot_lights_len = spot_lights_values.len();
413    commands.try_insert_batch(spot_lights_values);
414
415    for (
416        main_entity,
417        entity,
418        directional_light,
419        visible_entities,
420        cascades,
421        cascade_config,
422        frusta,
423        transform,
424        view_visibility,
425        maybe_layers,
426        volumetric_light,
427        occlusion_culling,
428    ) in &directional_lights
429    {
430        if !view_visibility.get() {
431            commands
432                .get_entity(entity)
433                .expect("Light entity wasn't synced.")
434                .remove::<(ExtractedDirectionalLight, RenderCascadesVisibleEntities)>();
435            continue;
436        }
437
438        // TODO: update in place instead of reinserting.
439        let mut extracted_cascades = EntityHashMap::default();
440        let mut extracted_frusta = EntityHashMap::default();
441        let mut cascade_visible_entities = EntityHashMap::default();
442        for (e, v) in cascades.cascades.iter() {
443            if let Ok(entity) = mapper.get(*e) {
444                extracted_cascades.insert(entity, v.clone());
445            } else {
446                break;
447            }
448        }
449        for (e, v) in frusta.frusta.iter() {
450            if let Ok(entity) = mapper.get(*e) {
451                extracted_frusta.insert(entity, v.clone());
452            } else {
453                break;
454            }
455        }
456        for (e, v) in visible_entities.entities.iter() {
457            if let Ok(entity) = mapper.get(*e) {
458                cascade_visible_entities.insert(
459                    entity,
460                    v.iter()
461                        .map(|v| create_render_visible_mesh_entities(&mapper, v))
462                        .collect(),
463                );
464            } else {
465                break;
466            }
467        }
468
469        commands
470            .get_entity(entity)
471            .expect("Light entity wasn't synced.")
472            .insert((
473                ExtractedDirectionalLight {
474                    color: directional_light.color.into(),
475                    illuminance: directional_light.illuminance,
476                    transform: *transform,
477                    volumetric: volumetric_light.is_some(),
478                    affects_lightmapped_mesh_diffuse: directional_light
479                        .affects_lightmapped_mesh_diffuse,
480                    #[cfg(feature = "experimental_pbr_pcss")]
481                    soft_shadow_size: directional_light.soft_shadow_size,
482                    #[cfg(not(feature = "experimental_pbr_pcss"))]
483                    soft_shadow_size: None,
484                    shadows_enabled: directional_light.shadows_enabled,
485                    shadow_depth_bias: directional_light.shadow_depth_bias,
486                    // The factor of SQRT_2 is for the worst-case diagonal offset
487                    shadow_normal_bias: directional_light.shadow_normal_bias
488                        * core::f32::consts::SQRT_2,
489                    cascade_shadow_config: cascade_config.clone(),
490                    cascades: extracted_cascades,
491                    frusta: extracted_frusta,
492                    render_layers: maybe_layers.unwrap_or_default().clone(),
493                    occlusion_culling,
494                },
495                RenderCascadesVisibleEntities {
496                    entities: cascade_visible_entities,
497                },
498                MainEntity::from(main_entity),
499            ));
500    }
501}
502
503fn create_render_visible_mesh_entities(
504    mapper: &Extract<Query<RenderEntity>>,
505    visible_entities: &VisibleMeshEntities,
506) -> RenderVisibleMeshEntities {
507    RenderVisibleMeshEntities {
508        entities: visible_entities
509            .iter()
510            .map(|e| {
511                let render_entity = mapper.get(*e).unwrap_or(Entity::PLACEHOLDER);
512                (render_entity, MainEntity::from(*e))
513            })
514            .collect(),
515    }
516}
517
518#[derive(Component, Default, Deref, DerefMut)]
519/// Component automatically attached to a light entity to track light-view entities
520/// for each view.
521pub struct LightViewEntities(EntityHashMap<Vec<Entity>>);
522
523// TODO: using required component
524pub(crate) fn add_light_view_entities(
525    trigger: Trigger<OnAdd, (ExtractedDirectionalLight, ExtractedPointLight)>,
526    mut commands: Commands,
527) {
528    if let Ok(mut v) = commands.get_entity(trigger.target()) {
529        v.insert(LightViewEntities::default());
530    }
531}
532
533/// Removes [`LightViewEntities`] when light is removed. See [`add_light_view_entities`].
534pub(crate) fn extracted_light_removed(
535    trigger: Trigger<OnRemove, (ExtractedDirectionalLight, ExtractedPointLight)>,
536    mut commands: Commands,
537) {
538    if let Ok(mut v) = commands.get_entity(trigger.target()) {
539        v.try_remove::<LightViewEntities>();
540    }
541}
542
543pub(crate) fn remove_light_view_entities(
544    trigger: Trigger<OnRemove, LightViewEntities>,
545    query: Query<&LightViewEntities>,
546    mut commands: Commands,
547) {
548    if let Ok(entities) = query.get(trigger.target()) {
549        for v in entities.0.values() {
550            for e in v.iter().copied() {
551                if let Ok(mut v) = commands.get_entity(e) {
552                    v.despawn();
553                }
554            }
555        }
556    }
557}
558
559pub(crate) struct CubeMapFace {
560    pub(crate) target: Vec3,
561    pub(crate) up: Vec3,
562}
563
564// Cubemap faces are [+X, -X, +Y, -Y, +Z, -Z], per https://www.w3.org/TR/webgpu/#texture-view-creation
565// Note: Cubemap coordinates are left-handed y-up, unlike the rest of Bevy.
566// See https://registry.khronos.org/vulkan/specs/1.2/html/chap16.html#_cube_map_face_selection
567//
568// For each cubemap face, we take care to specify the appropriate target/up axis such that the rendered
569// texture using Bevy's right-handed y-up coordinate space matches the expected cubemap face in
570// left-handed y-up cubemap coordinates.
571pub(crate) const CUBE_MAP_FACES: [CubeMapFace; 6] = [
572    // +X
573    CubeMapFace {
574        target: Vec3::X,
575        up: Vec3::Y,
576    },
577    // -X
578    CubeMapFace {
579        target: Vec3::NEG_X,
580        up: Vec3::Y,
581    },
582    // +Y
583    CubeMapFace {
584        target: Vec3::Y,
585        up: Vec3::Z,
586    },
587    // -Y
588    CubeMapFace {
589        target: Vec3::NEG_Y,
590        up: Vec3::NEG_Z,
591    },
592    // +Z (with left-handed conventions, pointing forwards)
593    CubeMapFace {
594        target: Vec3::NEG_Z,
595        up: Vec3::Y,
596    },
597    // -Z (with left-handed conventions, pointing backwards)
598    CubeMapFace {
599        target: Vec3::Z,
600        up: Vec3::Y,
601    },
602];
603
604fn face_index_to_name(face_index: usize) -> &'static str {
605    match face_index {
606        0 => "+x",
607        1 => "-x",
608        2 => "+y",
609        3 => "-y",
610        4 => "+z",
611        5 => "-z",
612        _ => "invalid",
613    }
614}
615
616#[derive(Component)]
617pub struct ShadowView {
618    pub depth_attachment: DepthAttachment,
619    pub pass_name: String,
620}
621
622#[derive(Component)]
623pub struct ViewShadowBindings {
624    pub point_light_depth_texture: Texture,
625    pub point_light_depth_texture_view: TextureView,
626    pub directional_light_depth_texture: Texture,
627    pub directional_light_depth_texture_view: TextureView,
628}
629
630/// A component that holds the shadow cascade views for all shadow cascades
631/// associated with a camera.
632///
633/// Note: Despite the name, this component actually holds the shadow cascade
634/// views, not the lights themselves.
635#[derive(Component)]
636pub struct ViewLightEntities {
637    /// The shadow cascade views for all shadow cascades associated with a
638    /// camera.
639    ///
640    /// Note: Despite the name, this component actually holds the shadow cascade
641    /// views, not the lights themselves.
642    pub lights: Vec<Entity>,
643}
644
645#[derive(Component)]
646pub struct ViewLightsUniformOffset {
647    pub offset: u32,
648}
649
650#[derive(Resource, Default)]
651pub struct LightMeta {
652    pub view_gpu_lights: DynamicUniformBuffer<GpuLights>,
653}
654
655#[derive(Component)]
656pub enum LightEntity {
657    Directional {
658        light_entity: Entity,
659        cascade_index: usize,
660    },
661    Point {
662        light_entity: Entity,
663        face_index: usize,
664    },
665    Spot {
666        light_entity: Entity,
667    },
668}
669pub fn calculate_cluster_factors(
670    near: f32,
671    far: f32,
672    z_slices: f32,
673    is_orthographic: bool,
674) -> Vec2 {
675    if is_orthographic {
676        Vec2::new(-near, z_slices / (-far - -near))
677    } else {
678        let z_slices_of_ln_zfar_over_znear = (z_slices - 1.0) / ops::ln(far / near);
679        Vec2::new(
680            z_slices_of_ln_zfar_over_znear,
681            ops::ln(near) * z_slices_of_ln_zfar_over_znear,
682        )
683    }
684}
685
686// this method of constructing a basis from a vec3 is used by glam::Vec3::any_orthonormal_pair
687// we will also construct it in the fragment shader and need our implementations to match,
688// so we reproduce it here to avoid a mismatch if glam changes. we also switch the handedness
689// could move this onto transform but it's pretty niche
690pub(crate) fn spot_light_world_from_view(transform: &GlobalTransform) -> Mat4 {
691    // the matrix z_local (opposite of transform.forward())
692    let fwd_dir = transform.back().extend(0.0);
693
694    let sign = 1f32.copysign(fwd_dir.z);
695    let a = -1.0 / (fwd_dir.z + sign);
696    let b = fwd_dir.x * fwd_dir.y * a;
697    let up_dir = Vec4::new(
698        1.0 + sign * fwd_dir.x * fwd_dir.x * a,
699        sign * b,
700        -sign * fwd_dir.x,
701        0.0,
702    );
703    let right_dir = Vec4::new(-b, -sign - fwd_dir.y * fwd_dir.y * a, fwd_dir.y, 0.0);
704
705    Mat4::from_cols(
706        right_dir,
707        up_dir,
708        fwd_dir,
709        transform.translation().extend(1.0),
710    )
711}
712
713pub(crate) fn spot_light_clip_from_view(angle: f32, near_z: f32) -> Mat4 {
714    // spot light projection FOV is 2x the angle from spot light center to outer edge
715    Mat4::perspective_infinite_reverse_rh(angle * 2.0, 1.0, near_z)
716}
717
718pub fn prepare_lights(
719    mut commands: Commands,
720    mut texture_cache: ResMut<TextureCache>,
721    (render_device, render_queue): (Res<RenderDevice>, Res<RenderQueue>),
722    mut global_light_meta: ResMut<GlobalClusterableObjectMeta>,
723    mut light_meta: ResMut<LightMeta>,
724    views: Query<
725        (
726            Entity,
727            MainEntity,
728            &ExtractedView,
729            &ExtractedClusterConfig,
730            Option<&RenderLayers>,
731            Has<NoIndirectDrawing>,
732            Option<&AmbientLight>,
733        ),
734        With<Camera3d>,
735    >,
736    ambient_light: Res<AmbientLight>,
737    point_light_shadow_map: Res<PointLightShadowMap>,
738    directional_light_shadow_map: Res<DirectionalLightShadowMap>,
739    mut shadow_render_phases: ResMut<ViewBinnedRenderPhases<Shadow>>,
740    (
741        mut max_directional_lights_warning_emitted,
742        mut max_cascades_per_light_warning_emitted,
743        mut live_shadow_mapping_lights,
744    ): (Local<bool>, Local<bool>, Local<HashSet<RetainedViewEntity>>),
745    point_lights: Query<(
746        Entity,
747        &MainEntity,
748        &ExtractedPointLight,
749        AnyOf<(&CubemapFrusta, &Frustum)>,
750    )>,
751    directional_lights: Query<(Entity, &MainEntity, &ExtractedDirectionalLight)>,
752    mut light_view_entities: Query<&mut LightViewEntities>,
753    sorted_cameras: Res<SortedCameras>,
754    gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
755) {
756    let views_iter = views.iter();
757    let views_count = views_iter.len();
758    let Some(mut view_gpu_lights_writer) =
759        light_meta
760            .view_gpu_lights
761            .get_writer(views_count, &render_device, &render_queue)
762    else {
763        return;
764    };
765
766    // Pre-calculate for PointLights
767    let cube_face_rotations = CUBE_MAP_FACES
768        .iter()
769        .map(|CubeMapFace { target, up }| Transform::IDENTITY.looking_at(*target, *up))
770        .collect::<Vec<_>>();
771
772    global_light_meta.entity_to_index.clear();
773
774    let mut point_lights: Vec<_> = point_lights.iter().collect::<Vec<_>>();
775    let mut directional_lights: Vec<_> = directional_lights.iter().collect::<Vec<_>>();
776
777    #[cfg(any(
778        not(feature = "webgl"),
779        not(target_arch = "wasm32"),
780        feature = "webgpu"
781    ))]
782    let max_texture_array_layers = render_device.limits().max_texture_array_layers as usize;
783    #[cfg(any(
784        not(feature = "webgl"),
785        not(target_arch = "wasm32"),
786        feature = "webgpu"
787    ))]
788    let max_texture_cubes = max_texture_array_layers / 6;
789    #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
790    let max_texture_array_layers = 1;
791    #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
792    let max_texture_cubes = 1;
793
794    if !*max_directional_lights_warning_emitted && directional_lights.len() > MAX_DIRECTIONAL_LIGHTS
795    {
796        warn!(
797            "The amount of directional lights of {} is exceeding the supported limit of {}.",
798            directional_lights.len(),
799            MAX_DIRECTIONAL_LIGHTS
800        );
801        *max_directional_lights_warning_emitted = true;
802    }
803
804    if !*max_cascades_per_light_warning_emitted
805        && directional_lights
806            .iter()
807            .any(|(_, _, light)| light.cascade_shadow_config.bounds.len() > MAX_CASCADES_PER_LIGHT)
808    {
809        warn!(
810            "The number of cascades configured for a directional light exceeds the supported limit of {}.",
811            MAX_CASCADES_PER_LIGHT
812        );
813        *max_cascades_per_light_warning_emitted = true;
814    }
815
816    let point_light_count = point_lights
817        .iter()
818        .filter(|light| light.2.spot_light_angles.is_none())
819        .count();
820
821    let point_light_volumetric_enabled_count = point_lights
822        .iter()
823        .filter(|(_, _, light, _)| light.volumetric && light.spot_light_angles.is_none())
824        .count()
825        .min(max_texture_cubes);
826
827    let point_light_shadow_maps_count = point_lights
828        .iter()
829        .filter(|light| light.2.shadows_enabled && light.2.spot_light_angles.is_none())
830        .count()
831        .min(max_texture_cubes);
832
833    let directional_volumetric_enabled_count = directional_lights
834        .iter()
835        .take(MAX_DIRECTIONAL_LIGHTS)
836        .filter(|(_, _, light)| light.volumetric)
837        .count()
838        .min(max_texture_array_layers / MAX_CASCADES_PER_LIGHT);
839
840    let directional_shadow_enabled_count = directional_lights
841        .iter()
842        .take(MAX_DIRECTIONAL_LIGHTS)
843        .filter(|(_, _, light)| light.shadows_enabled)
844        .count()
845        .min(max_texture_array_layers / MAX_CASCADES_PER_LIGHT);
846
847    let spot_light_count = point_lights
848        .iter()
849        .filter(|(_, _, light, _)| light.spot_light_angles.is_some())
850        .count()
851        .min(max_texture_array_layers - directional_shadow_enabled_count * MAX_CASCADES_PER_LIGHT);
852
853    let spot_light_volumetric_enabled_count = point_lights
854        .iter()
855        .filter(|(_, _, light, _)| light.volumetric && light.spot_light_angles.is_some())
856        .count()
857        .min(max_texture_array_layers - directional_shadow_enabled_count * MAX_CASCADES_PER_LIGHT);
858
859    let spot_light_shadow_maps_count = point_lights
860        .iter()
861        .filter(|(_, _, light, _)| light.shadows_enabled && light.spot_light_angles.is_some())
862        .count()
863        .min(max_texture_array_layers - directional_shadow_enabled_count * MAX_CASCADES_PER_LIGHT);
864
865    // Sort lights by
866    // - point-light vs spot-light, so that we can iterate point lights and spot lights in contiguous blocks in the fragment shader,
867    // - then those with shadows enabled first, so that the index can be used to render at most `point_light_shadow_maps_count`
868    //   point light shadows and `spot_light_shadow_maps_count` spot light shadow maps,
869    // - then by entity as a stable key to ensure that a consistent set of lights are chosen if the light count limit is exceeded.
870    point_lights.sort_by_cached_key(|(entity, _, light, _)| {
871        (
872            ClusterableObjectType::from_point_or_spot_light(light).ordering(),
873            *entity,
874        )
875    });
876
877    // Sort lights by
878    // - those with volumetric (and shadows) enabled first, so that the
879    //   volumetric lighting pass can quickly find the volumetric lights;
880    // - then those with shadows enabled second, so that the index can be used
881    //   to render at most `directional_light_shadow_maps_count` directional light
882    //   shadows
883    // - then by entity as a stable key to ensure that a consistent set of
884    //   lights are chosen if the light count limit is exceeded.
885    // - because entities are unique, we can use `sort_unstable_by_key`
886    //   and still end up with a stable order.
887    directional_lights.sort_unstable_by_key(|(entity, _, light)| {
888        (light.volumetric, light.shadows_enabled, *entity)
889    });
890
891    if global_light_meta.entity_to_index.capacity() < point_lights.len() {
892        global_light_meta
893            .entity_to_index
894            .reserve(point_lights.len());
895    }
896
897    let mut gpu_point_lights = Vec::new();
898    for (index, &(entity, _, light, _)) in point_lights.iter().enumerate() {
899        let mut flags = PointLightFlags::NONE;
900
901        // Lights are sorted, shadow enabled lights are first
902        if light.shadows_enabled
903            && (index < point_light_shadow_maps_count
904                || (light.spot_light_angles.is_some()
905                    && index - point_light_count < spot_light_shadow_maps_count))
906        {
907            flags |= PointLightFlags::SHADOWS_ENABLED;
908        }
909
910        let cube_face_projection = Mat4::perspective_infinite_reverse_rh(
911            core::f32::consts::FRAC_PI_2,
912            1.0,
913            light.shadow_map_near_z,
914        );
915        if light.shadows_enabled
916            && light.volumetric
917            && (index < point_light_volumetric_enabled_count
918                || (light.spot_light_angles.is_some()
919                    && index - point_light_count < spot_light_volumetric_enabled_count))
920        {
921            flags |= PointLightFlags::VOLUMETRIC;
922        }
923
924        if light.affects_lightmapped_mesh_diffuse {
925            flags |= PointLightFlags::AFFECTS_LIGHTMAPPED_MESH_DIFFUSE;
926        }
927
928        let (light_custom_data, spot_light_tan_angle) = match light.spot_light_angles {
929            Some((inner, outer)) => {
930                let light_direction = light.transform.forward();
931                if light_direction.y.is_sign_negative() {
932                    flags |= PointLightFlags::SPOT_LIGHT_Y_NEGATIVE;
933                }
934
935                let cos_outer = ops::cos(outer);
936                let spot_scale = 1.0 / f32::max(ops::cos(inner) - cos_outer, 1e-4);
937                let spot_offset = -cos_outer * spot_scale;
938
939                (
940                    // For spot lights: the direction (x,z), spot_scale and spot_offset
941                    light_direction.xz().extend(spot_scale).extend(spot_offset),
942                    ops::tan(outer),
943                )
944            }
945            None => {
946                (
947                    // For point lights: the lower-right 2x2 values of the projection matrix [2][2] [2][3] [3][2] [3][3]
948                    Vec4::new(
949                        cube_face_projection.z_axis.z,
950                        cube_face_projection.z_axis.w,
951                        cube_face_projection.w_axis.z,
952                        cube_face_projection.w_axis.w,
953                    ),
954                    // unused
955                    0.0,
956                )
957            }
958        };
959
960        gpu_point_lights.push(GpuClusterableObject {
961            light_custom_data,
962            // premultiply color by intensity
963            // we don't use the alpha at all, so no reason to multiply only [0..3]
964            color_inverse_square_range: (Vec4::from_slice(&light.color.to_f32_array())
965                * light.intensity)
966                .xyz()
967                .extend(1.0 / (light.range * light.range)),
968            position_radius: light.transform.translation().extend(light.radius),
969            flags: flags.bits(),
970            shadow_depth_bias: light.shadow_depth_bias,
971            shadow_normal_bias: light.shadow_normal_bias,
972            shadow_map_near_z: light.shadow_map_near_z,
973            spot_light_tan_angle,
974            pad_a: 0.0,
975            pad_b: 0.0,
976            soft_shadow_size: if light.soft_shadows_enabled {
977                light.radius
978            } else {
979                0.0
980            },
981        });
982        global_light_meta.entity_to_index.insert(entity, index);
983    }
984
985    let mut gpu_directional_lights = [GpuDirectionalLight::default(); MAX_DIRECTIONAL_LIGHTS];
986    let mut num_directional_cascades_enabled = 0usize;
987    for (index, (_light_entity, _, light)) in directional_lights
988        .iter()
989        .enumerate()
990        .take(MAX_DIRECTIONAL_LIGHTS)
991    {
992        let mut flags = DirectionalLightFlags::NONE;
993
994        // Lights are sorted, volumetric and shadow enabled lights are first
995        if light.volumetric
996            && light.shadows_enabled
997            && (index < directional_volumetric_enabled_count)
998        {
999            flags |= DirectionalLightFlags::VOLUMETRIC;
1000        }
1001        // Shadow enabled lights are second
1002        if light.shadows_enabled && (index < directional_shadow_enabled_count) {
1003            flags |= DirectionalLightFlags::SHADOWS_ENABLED;
1004        }
1005
1006        if light.affects_lightmapped_mesh_diffuse {
1007            flags |= DirectionalLightFlags::AFFECTS_LIGHTMAPPED_MESH_DIFFUSE;
1008        }
1009
1010        let num_cascades = light
1011            .cascade_shadow_config
1012            .bounds
1013            .len()
1014            .min(MAX_CASCADES_PER_LIGHT);
1015        gpu_directional_lights[index] = GpuDirectionalLight {
1016            // Set to true later when necessary.
1017            skip: 0u32,
1018            // Filled in later.
1019            cascades: [GpuDirectionalCascade::default(); MAX_CASCADES_PER_LIGHT],
1020            // premultiply color by illuminance
1021            // we don't use the alpha at all, so no reason to multiply only [0..3]
1022            color: Vec4::from_slice(&light.color.to_f32_array()) * light.illuminance,
1023            // direction is negated to be ready for N.L
1024            dir_to_light: light.transform.back().into(),
1025            flags: flags.bits(),
1026            soft_shadow_size: light.soft_shadow_size.unwrap_or_default(),
1027            shadow_depth_bias: light.shadow_depth_bias,
1028            shadow_normal_bias: light.shadow_normal_bias,
1029            num_cascades: num_cascades as u32,
1030            cascades_overlap_proportion: light.cascade_shadow_config.overlap_proportion,
1031            depth_texture_base_index: num_directional_cascades_enabled as u32,
1032        };
1033        if index < directional_shadow_enabled_count {
1034            num_directional_cascades_enabled += num_cascades;
1035        }
1036    }
1037
1038    global_light_meta
1039        .gpu_clusterable_objects
1040        .set(gpu_point_lights);
1041    global_light_meta
1042        .gpu_clusterable_objects
1043        .write_buffer(&render_device, &render_queue);
1044
1045    live_shadow_mapping_lights.clear();
1046
1047    let mut point_light_depth_attachments = HashMap::<u32, DepthAttachment>::default();
1048    let mut directional_light_depth_attachments = HashMap::<u32, DepthAttachment>::default();
1049
1050    let point_light_depth_texture = texture_cache.get(
1051        &render_device,
1052        TextureDescriptor {
1053            size: Extent3d {
1054                width: point_light_shadow_map.size as u32,
1055                height: point_light_shadow_map.size as u32,
1056                depth_or_array_layers: point_light_shadow_maps_count.max(1) as u32 * 6,
1057            },
1058            mip_level_count: 1,
1059            sample_count: 1,
1060            dimension: TextureDimension::D2,
1061            format: CORE_3D_DEPTH_FORMAT,
1062            label: Some("point_light_shadow_map_texture"),
1063            usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING,
1064            view_formats: &[],
1065        },
1066    );
1067
1068    let point_light_depth_texture_view =
1069        point_light_depth_texture
1070            .texture
1071            .create_view(&TextureViewDescriptor {
1072                label: Some("point_light_shadow_map_array_texture_view"),
1073                format: None,
1074                // NOTE: iOS Simulator is missing CubeArray support so we use Cube instead.
1075                // See https://github.com/bevyengine/bevy/pull/12052 - remove if support is added.
1076                #[cfg(all(
1077                    not(target_abi = "sim"),
1078                    any(
1079                        not(feature = "webgl"),
1080                        not(target_arch = "wasm32"),
1081                        feature = "webgpu"
1082                    )
1083                ))]
1084                dimension: Some(TextureViewDimension::CubeArray),
1085                #[cfg(any(
1086                    target_abi = "sim",
1087                    all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu"))
1088                ))]
1089                dimension: Some(TextureViewDimension::Cube),
1090                usage: None,
1091                aspect: TextureAspect::DepthOnly,
1092                base_mip_level: 0,
1093                mip_level_count: None,
1094                base_array_layer: 0,
1095                array_layer_count: None,
1096            });
1097
1098    let directional_light_depth_texture = texture_cache.get(
1099        &render_device,
1100        TextureDescriptor {
1101            size: Extent3d {
1102                width: (directional_light_shadow_map.size as u32)
1103                    .min(render_device.limits().max_texture_dimension_2d),
1104                height: (directional_light_shadow_map.size as u32)
1105                    .min(render_device.limits().max_texture_dimension_2d),
1106                depth_or_array_layers: (num_directional_cascades_enabled
1107                    + spot_light_shadow_maps_count)
1108                    .max(1) as u32,
1109            },
1110            mip_level_count: 1,
1111            sample_count: 1,
1112            dimension: TextureDimension::D2,
1113            format: CORE_3D_DEPTH_FORMAT,
1114            label: Some("directional_light_shadow_map_texture"),
1115            usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING,
1116            view_formats: &[],
1117        },
1118    );
1119
1120    let directional_light_depth_texture_view =
1121        directional_light_depth_texture
1122            .texture
1123            .create_view(&TextureViewDescriptor {
1124                label: Some("directional_light_shadow_map_array_texture_view"),
1125                format: None,
1126                #[cfg(any(
1127                    not(feature = "webgl"),
1128                    not(target_arch = "wasm32"),
1129                    feature = "webgpu"
1130                ))]
1131                dimension: Some(TextureViewDimension::D2Array),
1132                #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
1133                dimension: Some(TextureViewDimension::D2),
1134                usage: None,
1135                aspect: TextureAspect::DepthOnly,
1136                base_mip_level: 0,
1137                mip_level_count: None,
1138                base_array_layer: 0,
1139                array_layer_count: None,
1140            });
1141
1142    let mut live_views = EntityHashSet::with_capacity(views_count);
1143
1144    // set up light data for each view
1145    for (
1146        entity,
1147        camera_main_entity,
1148        extracted_view,
1149        clusters,
1150        maybe_layers,
1151        no_indirect_drawing,
1152        maybe_ambient_override,
1153    ) in sorted_cameras
1154        .0
1155        .iter()
1156        .filter_map(|sorted_camera| views.get(sorted_camera.entity).ok())
1157    {
1158        live_views.insert(entity);
1159
1160        let mut view_lights = Vec::new();
1161        let mut view_occlusion_culling_lights = Vec::new();
1162
1163        let gpu_preprocessing_mode = gpu_preprocessing_support.min(if !no_indirect_drawing {
1164            GpuPreprocessingMode::Culling
1165        } else {
1166            GpuPreprocessingMode::PreprocessingOnly
1167        });
1168
1169        let is_orthographic = extracted_view.clip_from_view.w_axis.w == 1.0;
1170        let cluster_factors_zw = calculate_cluster_factors(
1171            clusters.near,
1172            clusters.far,
1173            clusters.dimensions.z as f32,
1174            is_orthographic,
1175        );
1176
1177        let n_clusters = clusters.dimensions.x * clusters.dimensions.y * clusters.dimensions.z;
1178        let ambient_light = maybe_ambient_override.unwrap_or(&ambient_light);
1179        let mut gpu_lights = GpuLights {
1180            directional_lights: gpu_directional_lights,
1181            ambient_color: Vec4::from_slice(&LinearRgba::from(ambient_light.color).to_f32_array())
1182                * ambient_light.brightness,
1183            cluster_factors: Vec4::new(
1184                clusters.dimensions.x as f32 / extracted_view.viewport.z as f32,
1185                clusters.dimensions.y as f32 / extracted_view.viewport.w as f32,
1186                cluster_factors_zw.x,
1187                cluster_factors_zw.y,
1188            ),
1189            cluster_dimensions: clusters.dimensions.extend(n_clusters),
1190            n_directional_lights: directional_lights.iter().len().min(MAX_DIRECTIONAL_LIGHTS)
1191                as u32,
1192            // spotlight shadow maps are stored in the directional light array, starting at num_directional_cascades_enabled.
1193            // the spot lights themselves start in the light array at point_light_count. so to go from light
1194            // index to shadow map index, we need to subtract point light count and add directional shadowmap count.
1195            spot_light_shadowmap_offset: num_directional_cascades_enabled as i32
1196                - point_light_count as i32,
1197            ambient_light_affects_lightmapped_meshes: ambient_light.affects_lightmapped_meshes
1198                as u32,
1199        };
1200
1201        // TODO: this should select lights based on relevance to the view instead of the first ones that show up in a query
1202        for &(light_entity, light_main_entity, light, (point_light_frusta, _)) in point_lights
1203            .iter()
1204            // Lights are sorted, shadow enabled lights are first
1205            .take(point_light_count.min(max_texture_cubes))
1206        {
1207            let Ok(mut light_view_entities) = light_view_entities.get_mut(light_entity) else {
1208                continue;
1209            };
1210
1211            if !light.shadows_enabled {
1212                if let Some(entities) = light_view_entities.remove(&entity) {
1213                    despawn_entities(&mut commands, entities);
1214                }
1215                continue;
1216            }
1217
1218            let light_index = *global_light_meta
1219                .entity_to_index
1220                .get(&light_entity)
1221                .unwrap();
1222            // ignore scale because we don't want to effectively scale light radius and range
1223            // by applying those as a view transform to shadow map rendering of objects
1224            // and ignore rotation because we want the shadow map projections to align with the axes
1225            let view_translation = GlobalTransform::from_translation(light.transform.translation());
1226
1227            // for each face of a cube and each view we spawn a light entity
1228            let light_view_entities = light_view_entities
1229                .entry(entity)
1230                .or_insert_with(|| (0..6).map(|_| commands.spawn_empty().id()).collect());
1231
1232            let cube_face_projection = Mat4::perspective_infinite_reverse_rh(
1233                core::f32::consts::FRAC_PI_2,
1234                1.0,
1235                light.shadow_map_near_z,
1236            );
1237
1238            for (face_index, ((view_rotation, frustum), view_light_entity)) in cube_face_rotations
1239                .iter()
1240                .zip(&point_light_frusta.unwrap().frusta)
1241                .zip(light_view_entities.iter().copied())
1242                .enumerate()
1243            {
1244                let mut first = false;
1245                let base_array_layer = (light_index * 6 + face_index) as u32;
1246
1247                let depth_attachment = point_light_depth_attachments
1248                    .entry(base_array_layer)
1249                    .or_insert_with(|| {
1250                        first = true;
1251
1252                        let depth_texture_view =
1253                            point_light_depth_texture
1254                                .texture
1255                                .create_view(&TextureViewDescriptor {
1256                                    label: Some("point_light_shadow_map_texture_view"),
1257                                    format: None,
1258                                    dimension: Some(TextureViewDimension::D2),
1259                                    usage: None,
1260                                    aspect: TextureAspect::All,
1261                                    base_mip_level: 0,
1262                                    mip_level_count: None,
1263                                    base_array_layer,
1264                                    array_layer_count: Some(1u32),
1265                                });
1266
1267                        DepthAttachment::new(depth_texture_view, Some(0.0))
1268                    })
1269                    .clone();
1270
1271                let retained_view_entity = RetainedViewEntity::new(
1272                    *light_main_entity,
1273                    Some(camera_main_entity.into()),
1274                    face_index as u32,
1275                );
1276
1277                commands.entity(view_light_entity).insert((
1278                    ShadowView {
1279                        depth_attachment,
1280                        pass_name: format!(
1281                            "shadow pass point light {} {}",
1282                            light_index,
1283                            face_index_to_name(face_index)
1284                        ),
1285                    },
1286                    ExtractedView {
1287                        retained_view_entity,
1288                        viewport: UVec4::new(
1289                            0,
1290                            0,
1291                            point_light_shadow_map.size as u32,
1292                            point_light_shadow_map.size as u32,
1293                        ),
1294                        world_from_view: view_translation * *view_rotation,
1295                        clip_from_world: None,
1296                        clip_from_view: cube_face_projection,
1297                        hdr: false,
1298                        color_grading: Default::default(),
1299                    },
1300                    *frustum,
1301                    LightEntity::Point {
1302                        light_entity,
1303                        face_index,
1304                    },
1305                ));
1306
1307                if !matches!(gpu_preprocessing_mode, GpuPreprocessingMode::Culling) {
1308                    commands.entity(view_light_entity).insert(NoIndirectDrawing);
1309                }
1310
1311                view_lights.push(view_light_entity);
1312
1313                if first {
1314                    // Subsequent views with the same light entity will reuse the same shadow map
1315                    shadow_render_phases
1316                        .prepare_for_new_frame(retained_view_entity, gpu_preprocessing_mode);
1317                    live_shadow_mapping_lights.insert(retained_view_entity);
1318                }
1319            }
1320        }
1321
1322        // spot lights
1323        for (light_index, &(light_entity, light_main_entity, light, (_, spot_light_frustum))) in
1324            point_lights
1325                .iter()
1326                .skip(point_light_count)
1327                .take(spot_light_count)
1328                .enumerate()
1329        {
1330            let Ok(mut light_view_entities) = light_view_entities.get_mut(light_entity) else {
1331                continue;
1332            };
1333
1334            if !light.shadows_enabled {
1335                if let Some(entities) = light_view_entities.remove(&entity) {
1336                    despawn_entities(&mut commands, entities);
1337                }
1338                continue;
1339            }
1340
1341            let spot_world_from_view = spot_light_world_from_view(&light.transform);
1342            let spot_world_from_view = spot_world_from_view.into();
1343
1344            let angle = light.spot_light_angles.expect("lights should be sorted so that \
1345                [point_light_count..point_light_count + spot_light_shadow_maps_count] are spot lights").1;
1346            let spot_projection = spot_light_clip_from_view(angle, light.shadow_map_near_z);
1347
1348            let mut first = false;
1349            let base_array_layer = (num_directional_cascades_enabled + light_index) as u32;
1350
1351            let depth_attachment = directional_light_depth_attachments
1352                .entry(base_array_layer)
1353                .or_insert_with(|| {
1354                    first = true;
1355
1356                    let depth_texture_view = directional_light_depth_texture.texture.create_view(
1357                        &TextureViewDescriptor {
1358                            label: Some("spot_light_shadow_map_texture_view"),
1359                            format: None,
1360                            dimension: Some(TextureViewDimension::D2),
1361                            usage: None,
1362                            aspect: TextureAspect::All,
1363                            base_mip_level: 0,
1364                            mip_level_count: None,
1365                            base_array_layer,
1366                            array_layer_count: Some(1u32),
1367                        },
1368                    );
1369
1370                    DepthAttachment::new(depth_texture_view, Some(0.0))
1371                })
1372                .clone();
1373
1374            let light_view_entities = light_view_entities
1375                .entry(entity)
1376                .or_insert_with(|| vec![commands.spawn_empty().id()]);
1377
1378            let view_light_entity = light_view_entities[0];
1379
1380            let retained_view_entity =
1381                RetainedViewEntity::new(*light_main_entity, Some(camera_main_entity.into()), 0);
1382
1383            commands.entity(view_light_entity).insert((
1384                ShadowView {
1385                    depth_attachment,
1386                    pass_name: format!("shadow pass spot light {light_index}"),
1387                },
1388                ExtractedView {
1389                    retained_view_entity,
1390                    viewport: UVec4::new(
1391                        0,
1392                        0,
1393                        directional_light_shadow_map.size as u32,
1394                        directional_light_shadow_map.size as u32,
1395                    ),
1396                    world_from_view: spot_world_from_view,
1397                    clip_from_view: spot_projection,
1398                    clip_from_world: None,
1399                    hdr: false,
1400                    color_grading: Default::default(),
1401                },
1402                *spot_light_frustum.unwrap(),
1403                LightEntity::Spot { light_entity },
1404            ));
1405
1406            if !matches!(gpu_preprocessing_mode, GpuPreprocessingMode::Culling) {
1407                commands.entity(view_light_entity).insert(NoIndirectDrawing);
1408            }
1409
1410            view_lights.push(view_light_entity);
1411
1412            if first {
1413                // Subsequent views with the same light entity will reuse the same shadow map
1414                shadow_render_phases
1415                    .prepare_for_new_frame(retained_view_entity, gpu_preprocessing_mode);
1416                live_shadow_mapping_lights.insert(retained_view_entity);
1417            }
1418        }
1419
1420        // directional lights
1421        let mut directional_depth_texture_array_index = 0u32;
1422        let view_layers = maybe_layers.unwrap_or_default();
1423        for (light_index, &(light_entity, light_main_entity, light)) in directional_lights
1424            .iter()
1425            .enumerate()
1426            .take(MAX_DIRECTIONAL_LIGHTS)
1427        {
1428            let gpu_light = &mut gpu_lights.directional_lights[light_index];
1429
1430            let Ok(mut light_view_entities) = light_view_entities.get_mut(light_entity) else {
1431                continue;
1432            };
1433
1434            // Check if the light intersects with the view.
1435            if !view_layers.intersects(&light.render_layers) {
1436                gpu_light.skip = 1u32;
1437                if let Some(entities) = light_view_entities.remove(&entity) {
1438                    despawn_entities(&mut commands, entities);
1439                }
1440                continue;
1441            }
1442
1443            // Only deal with cascades when shadows are enabled.
1444            if (gpu_light.flags & DirectionalLightFlags::SHADOWS_ENABLED.bits()) == 0u32 {
1445                if let Some(entities) = light_view_entities.remove(&entity) {
1446                    despawn_entities(&mut commands, entities);
1447                }
1448                continue;
1449            }
1450
1451            let cascades = light
1452                .cascades
1453                .get(&entity)
1454                .unwrap()
1455                .iter()
1456                .take(MAX_CASCADES_PER_LIGHT);
1457            let frusta = light
1458                .frusta
1459                .get(&entity)
1460                .unwrap()
1461                .iter()
1462                .take(MAX_CASCADES_PER_LIGHT);
1463
1464            let iter = cascades
1465                .zip(frusta)
1466                .zip(&light.cascade_shadow_config.bounds);
1467
1468            let light_view_entities = light_view_entities.entry(entity).or_insert_with(|| {
1469                (0..iter.len())
1470                    .map(|_| commands.spawn_empty().id())
1471                    .collect()
1472            });
1473            if light_view_entities.len() != iter.len() {
1474                let entities = core::mem::take(light_view_entities);
1475                despawn_entities(&mut commands, entities);
1476                light_view_entities.extend((0..iter.len()).map(|_| commands.spawn_empty().id()));
1477            }
1478
1479            for (cascade_index, (((cascade, frustum), bound), view_light_entity)) in
1480                iter.zip(light_view_entities.iter().copied()).enumerate()
1481            {
1482                gpu_lights.directional_lights[light_index].cascades[cascade_index] =
1483                    GpuDirectionalCascade {
1484                        clip_from_world: cascade.clip_from_world,
1485                        texel_size: cascade.texel_size,
1486                        far_bound: *bound,
1487                    };
1488
1489                let depth_texture_view =
1490                    directional_light_depth_texture
1491                        .texture
1492                        .create_view(&TextureViewDescriptor {
1493                            label: Some("directional_light_shadow_map_array_texture_view"),
1494                            format: None,
1495                            dimension: Some(TextureViewDimension::D2),
1496                            usage: None,
1497                            aspect: TextureAspect::All,
1498                            base_mip_level: 0,
1499                            mip_level_count: None,
1500                            base_array_layer: directional_depth_texture_array_index,
1501                            array_layer_count: Some(1u32),
1502                        });
1503
1504                // NOTE: For point and spotlights, we reuse the same depth attachment for all views.
1505                // However, for directional lights, we want a new depth attachment for each view,
1506                // so that the view is cleared for each view.
1507                let depth_attachment = DepthAttachment::new(depth_texture_view.clone(), Some(0.0));
1508
1509                directional_depth_texture_array_index += 1;
1510
1511                let mut frustum = *frustum;
1512                // Push the near clip plane out to infinity for directional lights
1513                frustum.half_spaces[4] =
1514                    HalfSpace::new(frustum.half_spaces[4].normal().extend(f32::INFINITY));
1515
1516                let retained_view_entity = RetainedViewEntity::new(
1517                    *light_main_entity,
1518                    Some(camera_main_entity.into()),
1519                    cascade_index as u32,
1520                );
1521
1522                commands.entity(view_light_entity).insert((
1523                    ShadowView {
1524                        depth_attachment,
1525                        pass_name: format!(
1526                            "shadow pass directional light {light_index} cascade {cascade_index}"
1527                        ),
1528                    },
1529                    ExtractedView {
1530                        retained_view_entity,
1531                        viewport: UVec4::new(
1532                            0,
1533                            0,
1534                            directional_light_shadow_map.size as u32,
1535                            directional_light_shadow_map.size as u32,
1536                        ),
1537                        world_from_view: GlobalTransform::from(cascade.world_from_cascade),
1538                        clip_from_view: cascade.clip_from_cascade,
1539                        clip_from_world: Some(cascade.clip_from_world),
1540                        hdr: false,
1541                        color_grading: Default::default(),
1542                    },
1543                    frustum,
1544                    LightEntity::Directional {
1545                        light_entity,
1546                        cascade_index,
1547                    },
1548                ));
1549
1550                if !matches!(gpu_preprocessing_mode, GpuPreprocessingMode::Culling) {
1551                    commands.entity(view_light_entity).insert(NoIndirectDrawing);
1552                }
1553
1554                view_lights.push(view_light_entity);
1555
1556                // If this light is using occlusion culling, add the appropriate components.
1557                if light.occlusion_culling {
1558                    commands.entity(view_light_entity).insert((
1559                        OcclusionCulling,
1560                        OcclusionCullingSubview {
1561                            depth_texture_view,
1562                            depth_texture_size: directional_light_shadow_map.size as u32,
1563                        },
1564                    ));
1565                    view_occlusion_culling_lights.push(view_light_entity);
1566                }
1567
1568                // Subsequent views with the same light entity will **NOT** reuse the same shadow map
1569                // (Because the cascades are unique to each view)
1570                // TODO: Implement GPU culling for shadow passes.
1571                shadow_render_phases
1572                    .prepare_for_new_frame(retained_view_entity, gpu_preprocessing_mode);
1573                live_shadow_mapping_lights.insert(retained_view_entity);
1574            }
1575        }
1576
1577        commands.entity(entity).insert((
1578            ViewShadowBindings {
1579                point_light_depth_texture: point_light_depth_texture.texture.clone(),
1580                point_light_depth_texture_view: point_light_depth_texture_view.clone(),
1581                directional_light_depth_texture: directional_light_depth_texture.texture.clone(),
1582                directional_light_depth_texture_view: directional_light_depth_texture_view.clone(),
1583            },
1584            ViewLightEntities {
1585                lights: view_lights,
1586            },
1587            ViewLightsUniformOffset {
1588                offset: view_gpu_lights_writer.write(&gpu_lights),
1589            },
1590        ));
1591
1592        // Make a link from the camera to all shadow cascades with occlusion
1593        // culling enabled.
1594        if !view_occlusion_culling_lights.is_empty() {
1595            commands
1596                .entity(entity)
1597                .insert(OcclusionCullingSubviewEntities(
1598                    view_occlusion_culling_lights,
1599                ));
1600        }
1601    }
1602
1603    // Despawn light-view entities for views that no longer exist
1604    for mut entities in &mut light_view_entities {
1605        for (_, light_view_entities) in
1606            entities.extract_if(|entity, _| !live_views.contains(entity))
1607        {
1608            despawn_entities(&mut commands, light_view_entities);
1609        }
1610    }
1611
1612    shadow_render_phases.retain(|entity, _| live_shadow_mapping_lights.contains(entity));
1613}
1614
1615fn despawn_entities(commands: &mut Commands, entities: Vec<Entity>) {
1616    if entities.is_empty() {
1617        return;
1618    }
1619    commands.queue(move |world: &mut World| {
1620        for entity in entities {
1621            world.despawn(entity);
1622        }
1623    });
1624}
1625
1626// These will be extracted in the material extraction, which will also clear the needs_specialization
1627// collection.
1628pub fn check_light_entities_needing_specialization<M: Material>(
1629    needs_specialization: Query<Entity, (With<MeshMaterial3d<M>>, Changed<NotShadowCaster>)>,
1630    mut entities_needing_specialization: ResMut<EntitiesNeedingSpecialization<M>>,
1631    mut removed_components: RemovedComponents<NotShadowCaster>,
1632) {
1633    for entity in &needs_specialization {
1634        entities_needing_specialization.push(entity);
1635    }
1636
1637    for removed in removed_components.read() {
1638        entities_needing_specialization.entities.push(removed);
1639    }
1640}
1641
1642#[derive(Resource, Deref, DerefMut, Default, Debug, Clone)]
1643pub struct LightKeyCache(HashMap<RetainedViewEntity, MeshPipelineKey>);
1644
1645#[derive(Resource, Deref, DerefMut, Default, Debug, Clone)]
1646pub struct LightSpecializationTicks(HashMap<RetainedViewEntity, Tick>);
1647
1648#[derive(Resource, Deref, DerefMut)]
1649pub struct SpecializedShadowMaterialPipelineCache<M> {
1650    // view light entity -> view pipeline cache
1651    #[deref]
1652    map: HashMap<RetainedViewEntity, SpecializedShadowMaterialViewPipelineCache<M>>,
1653    marker: PhantomData<M>,
1654}
1655
1656#[derive(Deref, DerefMut)]
1657pub struct SpecializedShadowMaterialViewPipelineCache<M> {
1658    #[deref]
1659    map: MainEntityHashMap<(Tick, CachedRenderPipelineId)>,
1660    marker: PhantomData<M>,
1661}
1662
1663impl<M> Default for SpecializedShadowMaterialPipelineCache<M> {
1664    fn default() -> Self {
1665        Self {
1666            map: HashMap::default(),
1667            marker: PhantomData,
1668        }
1669    }
1670}
1671
1672impl<M> Default for SpecializedShadowMaterialViewPipelineCache<M> {
1673    fn default() -> Self {
1674        Self {
1675            map: MainEntityHashMap::default(),
1676            marker: PhantomData,
1677        }
1678    }
1679}
1680
1681pub fn check_views_lights_need_specialization(
1682    view_lights: Query<&ViewLightEntities, With<ExtractedView>>,
1683    view_light_entities: Query<(&LightEntity, &ExtractedView)>,
1684    shadow_render_phases: Res<ViewBinnedRenderPhases<Shadow>>,
1685    mut light_key_cache: ResMut<LightKeyCache>,
1686    mut light_specialization_ticks: ResMut<LightSpecializationTicks>,
1687    ticks: SystemChangeTick,
1688) {
1689    for view_lights in &view_lights {
1690        for view_light_entity in view_lights.lights.iter().copied() {
1691            let Ok((light_entity, extracted_view_light)) =
1692                view_light_entities.get(view_light_entity)
1693            else {
1694                continue;
1695            };
1696            if !shadow_render_phases.contains_key(&extracted_view_light.retained_view_entity) {
1697                continue;
1698            }
1699
1700            let is_directional_light = matches!(light_entity, LightEntity::Directional { .. });
1701            let mut light_key = MeshPipelineKey::DEPTH_PREPASS;
1702            light_key.set(MeshPipelineKey::UNCLIPPED_DEPTH_ORTHO, is_directional_light);
1703            if let Some(current_key) =
1704                light_key_cache.get_mut(&extracted_view_light.retained_view_entity)
1705            {
1706                if *current_key != light_key {
1707                    light_key_cache.insert(extracted_view_light.retained_view_entity, light_key);
1708                    light_specialization_ticks
1709                        .insert(extracted_view_light.retained_view_entity, ticks.this_run());
1710                }
1711            } else {
1712                light_key_cache.insert(extracted_view_light.retained_view_entity, light_key);
1713                light_specialization_ticks
1714                    .insert(extracted_view_light.retained_view_entity, ticks.this_run());
1715            }
1716        }
1717    }
1718}
1719
1720pub fn specialize_shadows<M: Material>(
1721    prepass_pipeline: Res<PrepassPipeline<M>>,
1722    (
1723        render_meshes,
1724        render_mesh_instances,
1725        render_materials,
1726        render_material_instances,
1727        material_bind_group_allocator,
1728    ): (
1729        Res<RenderAssets<RenderMesh>>,
1730        Res<RenderMeshInstances>,
1731        Res<RenderAssets<PreparedMaterial<M>>>,
1732        Res<RenderMaterialInstances>,
1733        Res<MaterialBindGroupAllocator<M>>,
1734    ),
1735    shadow_render_phases: Res<ViewBinnedRenderPhases<Shadow>>,
1736    mut pipelines: ResMut<SpecializedMeshPipelines<PrepassPipeline<M>>>,
1737    pipeline_cache: Res<PipelineCache>,
1738    render_lightmaps: Res<RenderLightmaps>,
1739    view_lights: Query<(Entity, &ViewLightEntities), With<ExtractedView>>,
1740    view_light_entities: Query<(&LightEntity, &ExtractedView)>,
1741    point_light_entities: Query<&RenderCubemapVisibleEntities, With<ExtractedPointLight>>,
1742    directional_light_entities: Query<
1743        &RenderCascadesVisibleEntities,
1744        With<ExtractedDirectionalLight>,
1745    >,
1746    spot_light_entities: Query<&RenderVisibleMeshEntities, With<ExtractedPointLight>>,
1747    light_key_cache: Res<LightKeyCache>,
1748    mut specialized_material_pipeline_cache: ResMut<SpecializedShadowMaterialPipelineCache<M>>,
1749    light_specialization_ticks: Res<LightSpecializationTicks>,
1750    entity_specialization_ticks: Res<EntitySpecializationTicks<M>>,
1751    ticks: SystemChangeTick,
1752) where
1753    M::Data: PartialEq + Eq + Hash + Clone,
1754{
1755    // Record the retained IDs of all shadow views so that we can expire old
1756    // pipeline IDs.
1757    let mut all_shadow_views: HashSet<RetainedViewEntity, FixedHasher> = HashSet::default();
1758
1759    for (entity, view_lights) in &view_lights {
1760        for view_light_entity in view_lights.lights.iter().copied() {
1761            let Ok((light_entity, extracted_view_light)) =
1762                view_light_entities.get(view_light_entity)
1763            else {
1764                continue;
1765            };
1766
1767            all_shadow_views.insert(extracted_view_light.retained_view_entity);
1768
1769            if !shadow_render_phases.contains_key(&extracted_view_light.retained_view_entity) {
1770                continue;
1771            }
1772            let Some(light_key) = light_key_cache.get(&extracted_view_light.retained_view_entity)
1773            else {
1774                continue;
1775            };
1776
1777            let visible_entities = match light_entity {
1778                LightEntity::Directional {
1779                    light_entity,
1780                    cascade_index,
1781                } => directional_light_entities
1782                    .get(*light_entity)
1783                    .expect("Failed to get directional light visible entities")
1784                    .entities
1785                    .get(&entity)
1786                    .expect("Failed to get directional light visible entities for view")
1787                    .get(*cascade_index)
1788                    .expect("Failed to get directional light visible entities for cascade"),
1789                LightEntity::Point {
1790                    light_entity,
1791                    face_index,
1792                } => point_light_entities
1793                    .get(*light_entity)
1794                    .expect("Failed to get point light visible entities")
1795                    .get(*face_index),
1796                LightEntity::Spot { light_entity } => spot_light_entities
1797                    .get(*light_entity)
1798                    .expect("Failed to get spot light visible entities"),
1799            };
1800
1801            // NOTE: Lights with shadow mapping disabled will have no visible entities
1802            // so no meshes will be queued
1803
1804            let view_tick = light_specialization_ticks
1805                .get(&extracted_view_light.retained_view_entity)
1806                .unwrap();
1807            let view_specialized_material_pipeline_cache = specialized_material_pipeline_cache
1808                .entry(extracted_view_light.retained_view_entity)
1809                .or_default();
1810
1811            for (_, visible_entity) in visible_entities.iter().copied() {
1812                let Some(material_instances) =
1813                    render_material_instances.instances.get(&visible_entity)
1814                else {
1815                    continue;
1816                };
1817                let Ok(material_asset_id) = material_instances.asset_id.try_typed::<M>() else {
1818                    continue;
1819                };
1820                let Some(mesh_instance) =
1821                    render_mesh_instances.render_mesh_queue_data(visible_entity)
1822                else {
1823                    continue;
1824                };
1825                let entity_tick = entity_specialization_ticks.get(&visible_entity).unwrap();
1826                let last_specialized_tick = view_specialized_material_pipeline_cache
1827                    .get(&visible_entity)
1828                    .map(|(tick, _)| *tick);
1829                let needs_specialization = last_specialized_tick.is_none_or(|tick| {
1830                    view_tick.is_newer_than(tick, ticks.this_run())
1831                        || entity_tick.is_newer_than(tick, ticks.this_run())
1832                });
1833                if !needs_specialization {
1834                    continue;
1835                }
1836                let Some(material) = render_materials.get(material_asset_id) else {
1837                    continue;
1838                };
1839                if !mesh_instance
1840                    .flags
1841                    .contains(RenderMeshInstanceFlags::SHADOW_CASTER)
1842                {
1843                    continue;
1844                }
1845                let Some(material_bind_group) =
1846                    material_bind_group_allocator.get(material.binding.group)
1847                else {
1848                    continue;
1849                };
1850                let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else {
1851                    continue;
1852                };
1853
1854                let mut mesh_key =
1855                    *light_key | MeshPipelineKey::from_bits_retain(mesh.key_bits.bits());
1856
1857                // Even though we don't use the lightmap in the shadow map, the
1858                // `SetMeshBindGroup` render command will bind the data for it. So
1859                // we need to include the appropriate flag in the mesh pipeline key
1860                // to ensure that the necessary bind group layout entries are
1861                // present.
1862                if render_lightmaps
1863                    .render_lightmaps
1864                    .contains_key(&visible_entity)
1865                {
1866                    mesh_key |= MeshPipelineKey::LIGHTMAPPED;
1867                }
1868
1869                mesh_key |= match material.properties.alpha_mode {
1870                    AlphaMode::Mask(_)
1871                    | AlphaMode::Blend
1872                    | AlphaMode::Premultiplied
1873                    | AlphaMode::Add
1874                    | AlphaMode::AlphaToCoverage => MeshPipelineKey::MAY_DISCARD,
1875                    _ => MeshPipelineKey::NONE,
1876                };
1877                let pipeline_id = pipelines.specialize(
1878                    &pipeline_cache,
1879                    &prepass_pipeline,
1880                    MaterialPipelineKey {
1881                        mesh_key,
1882                        bind_group_data: material_bind_group
1883                            .get_extra_data(material.binding.slot)
1884                            .clone(),
1885                    },
1886                    &mesh.layout,
1887                );
1888
1889                let pipeline_id = match pipeline_id {
1890                    Ok(id) => id,
1891                    Err(err) => {
1892                        error!("{}", err);
1893                        continue;
1894                    }
1895                };
1896
1897                view_specialized_material_pipeline_cache
1898                    .insert(visible_entity, (ticks.this_run(), pipeline_id));
1899            }
1900        }
1901    }
1902
1903    // Delete specialized pipelines belonging to views that have expired.
1904    specialized_material_pipeline_cache.retain(|view, _| all_shadow_views.contains(view));
1905}
1906
1907/// For each shadow cascade, iterates over all the meshes "visible" from it and
1908/// adds them to [`BinnedRenderPhase`]s or [`SortedRenderPhase`]s as
1909/// appropriate.
1910pub fn queue_shadows<M: Material>(
1911    shadow_draw_functions: Res<DrawFunctions<Shadow>>,
1912    render_mesh_instances: Res<RenderMeshInstances>,
1913    render_materials: Res<RenderAssets<PreparedMaterial<M>>>,
1914    render_material_instances: Res<RenderMaterialInstances>,
1915    mut shadow_render_phases: ResMut<ViewBinnedRenderPhases<Shadow>>,
1916    gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
1917    mesh_allocator: Res<MeshAllocator>,
1918    view_lights: Query<(Entity, &ViewLightEntities), With<ExtractedView>>,
1919    view_light_entities: Query<(&LightEntity, &ExtractedView)>,
1920    point_light_entities: Query<&RenderCubemapVisibleEntities, With<ExtractedPointLight>>,
1921    directional_light_entities: Query<
1922        &RenderCascadesVisibleEntities,
1923        With<ExtractedDirectionalLight>,
1924    >,
1925    spot_light_entities: Query<&RenderVisibleMeshEntities, With<ExtractedPointLight>>,
1926    specialized_material_pipeline_cache: Res<SpecializedShadowMaterialPipelineCache<M>>,
1927) where
1928    M::Data: PartialEq + Eq + Hash + Clone,
1929{
1930    let draw_shadow_mesh = shadow_draw_functions.read().id::<DrawPrepass<M>>();
1931    for (entity, view_lights) in &view_lights {
1932        for view_light_entity in view_lights.lights.iter().copied() {
1933            let Ok((light_entity, extracted_view_light)) =
1934                view_light_entities.get(view_light_entity)
1935            else {
1936                continue;
1937            };
1938            let Some(shadow_phase) =
1939                shadow_render_phases.get_mut(&extracted_view_light.retained_view_entity)
1940            else {
1941                continue;
1942            };
1943
1944            let Some(view_specialized_material_pipeline_cache) =
1945                specialized_material_pipeline_cache.get(&extracted_view_light.retained_view_entity)
1946            else {
1947                continue;
1948            };
1949
1950            let visible_entities = match light_entity {
1951                LightEntity::Directional {
1952                    light_entity,
1953                    cascade_index,
1954                } => directional_light_entities
1955                    .get(*light_entity)
1956                    .expect("Failed to get directional light visible entities")
1957                    .entities
1958                    .get(&entity)
1959                    .expect("Failed to get directional light visible entities for view")
1960                    .get(*cascade_index)
1961                    .expect("Failed to get directional light visible entities for cascade"),
1962                LightEntity::Point {
1963                    light_entity,
1964                    face_index,
1965                } => point_light_entities
1966                    .get(*light_entity)
1967                    .expect("Failed to get point light visible entities")
1968                    .get(*face_index),
1969                LightEntity::Spot { light_entity } => spot_light_entities
1970                    .get(*light_entity)
1971                    .expect("Failed to get spot light visible entities"),
1972            };
1973
1974            for (entity, main_entity) in visible_entities.iter().copied() {
1975                let Some((current_change_tick, pipeline_id)) =
1976                    view_specialized_material_pipeline_cache.get(&main_entity)
1977                else {
1978                    continue;
1979                };
1980
1981                // Skip the entity if it's cached in a bin and up to date.
1982                if shadow_phase.validate_cached_entity(main_entity, *current_change_tick) {
1983                    continue;
1984                }
1985
1986                let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(main_entity)
1987                else {
1988                    continue;
1989                };
1990                if !mesh_instance
1991                    .flags
1992                    .contains(RenderMeshInstanceFlags::SHADOW_CASTER)
1993                {
1994                    continue;
1995                }
1996
1997                let Some(material_instance) = render_material_instances.instances.get(&main_entity)
1998                else {
1999                    continue;
2000                };
2001                let Ok(material_asset_id) = material_instance.asset_id.try_typed::<M>() else {
2002                    continue;
2003                };
2004                let Some(material) = render_materials.get(material_asset_id) else {
2005                    continue;
2006                };
2007
2008                let (vertex_slab, index_slab) =
2009                    mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
2010
2011                let batch_set_key = ShadowBatchSetKey {
2012                    pipeline: *pipeline_id,
2013                    draw_function: draw_shadow_mesh,
2014                    material_bind_group_index: Some(material.binding.group.0),
2015                    vertex_slab: vertex_slab.unwrap_or_default(),
2016                    index_slab,
2017                };
2018
2019                shadow_phase.add(
2020                    batch_set_key,
2021                    ShadowBinKey {
2022                        asset_id: mesh_instance.mesh_asset_id.into(),
2023                    },
2024                    (entity, main_entity),
2025                    mesh_instance.current_uniform_index,
2026                    BinnedRenderPhaseType::mesh(
2027                        mesh_instance.should_batch(),
2028                        &gpu_preprocessing_support,
2029                    ),
2030                    *current_change_tick,
2031                );
2032            }
2033        }
2034    }
2035}
2036
2037pub struct Shadow {
2038    /// Determines which objects can be placed into a *batch set*.
2039    ///
2040    /// Objects in a single batch set can potentially be multi-drawn together,
2041    /// if it's enabled and the current platform supports it.
2042    pub batch_set_key: ShadowBatchSetKey,
2043    /// Information that separates items into bins.
2044    pub bin_key: ShadowBinKey,
2045    pub representative_entity: (Entity, MainEntity),
2046    pub batch_range: Range<u32>,
2047    pub extra_index: PhaseItemExtraIndex,
2048}
2049
2050/// Information that must be identical in order to place opaque meshes in the
2051/// same *batch set*.
2052///
2053/// A batch set is a set of batches that can be multi-drawn together, if
2054/// multi-draw is in use.
2055#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2056pub struct ShadowBatchSetKey {
2057    /// The identifier of the render pipeline.
2058    pub pipeline: CachedRenderPipelineId,
2059
2060    /// The function used to draw.
2061    pub draw_function: DrawFunctionId,
2062
2063    /// The ID of a bind group specific to the material.
2064    ///
2065    /// In the case of PBR, this is the `MaterialBindGroupIndex`.
2066    pub material_bind_group_index: Option<u32>,
2067
2068    /// The ID of the slab of GPU memory that contains vertex data.
2069    ///
2070    /// For non-mesh items, you can fill this with 0 if your items can be
2071    /// multi-drawn, or with a unique value if they can't.
2072    pub vertex_slab: SlabId,
2073
2074    /// The ID of the slab of GPU memory that contains index data, if present.
2075    ///
2076    /// For non-mesh items, you can safely fill this with `None`.
2077    pub index_slab: Option<SlabId>,
2078}
2079
2080impl PhaseItemBatchSetKey for ShadowBatchSetKey {
2081    fn indexed(&self) -> bool {
2082        self.index_slab.is_some()
2083    }
2084}
2085
2086/// Data used to bin each object in the shadow map phase.
2087#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2088pub struct ShadowBinKey {
2089    /// The object.
2090    pub asset_id: UntypedAssetId,
2091}
2092
2093impl PhaseItem for Shadow {
2094    #[inline]
2095    fn entity(&self) -> Entity {
2096        self.representative_entity.0
2097    }
2098
2099    fn main_entity(&self) -> MainEntity {
2100        self.representative_entity.1
2101    }
2102
2103    #[inline]
2104    fn draw_function(&self) -> DrawFunctionId {
2105        self.batch_set_key.draw_function
2106    }
2107
2108    #[inline]
2109    fn batch_range(&self) -> &Range<u32> {
2110        &self.batch_range
2111    }
2112
2113    #[inline]
2114    fn batch_range_mut(&mut self) -> &mut Range<u32> {
2115        &mut self.batch_range
2116    }
2117
2118    #[inline]
2119    fn extra_index(&self) -> PhaseItemExtraIndex {
2120        self.extra_index.clone()
2121    }
2122
2123    #[inline]
2124    fn batch_range_and_extra_index_mut(&mut self) -> (&mut Range<u32>, &mut PhaseItemExtraIndex) {
2125        (&mut self.batch_range, &mut self.extra_index)
2126    }
2127}
2128
2129impl BinnedPhaseItem for Shadow {
2130    type BatchSetKey = ShadowBatchSetKey;
2131    type BinKey = ShadowBinKey;
2132
2133    #[inline]
2134    fn new(
2135        batch_set_key: Self::BatchSetKey,
2136        bin_key: Self::BinKey,
2137        representative_entity: (Entity, MainEntity),
2138        batch_range: Range<u32>,
2139        extra_index: PhaseItemExtraIndex,
2140    ) -> Self {
2141        Shadow {
2142            batch_set_key,
2143            bin_key,
2144            representative_entity,
2145            batch_range,
2146            extra_index,
2147        }
2148    }
2149}
2150
2151impl CachedRenderPipelinePhaseItem for Shadow {
2152    #[inline]
2153    fn cached_pipeline(&self) -> CachedRenderPipelineId {
2154        self.batch_set_key.pipeline
2155    }
2156}
2157
2158/// The rendering node that renders meshes that were "visible" (so to speak)
2159/// from a light last frame.
2160///
2161/// If occlusion culling for a light is disabled, then this node simply renders
2162/// all meshes in range of the light.
2163#[derive(Deref, DerefMut)]
2164pub struct EarlyShadowPassNode(ShadowPassNode);
2165
2166/// The rendering node that renders meshes that became newly "visible" (so to
2167/// speak) from a light this frame.
2168///
2169/// If occlusion culling for a light is disabled, then this node does nothing.
2170#[derive(Deref, DerefMut)]
2171pub struct LateShadowPassNode(ShadowPassNode);
2172
2173/// Encapsulates rendering logic shared between the early and late shadow pass
2174/// nodes.
2175pub struct ShadowPassNode {
2176    /// The query that finds cameras in which shadows are visible.
2177    main_view_query: QueryState<Read<ViewLightEntities>>,
2178    /// The query that finds shadow cascades.
2179    view_light_query: QueryState<(Read<ShadowView>, Read<ExtractedView>, Has<OcclusionCulling>)>,
2180}
2181
2182impl FromWorld for EarlyShadowPassNode {
2183    fn from_world(world: &mut World) -> Self {
2184        Self(ShadowPassNode::from_world(world))
2185    }
2186}
2187
2188impl FromWorld for LateShadowPassNode {
2189    fn from_world(world: &mut World) -> Self {
2190        Self(ShadowPassNode::from_world(world))
2191    }
2192}
2193
2194impl FromWorld for ShadowPassNode {
2195    fn from_world(world: &mut World) -> Self {
2196        Self {
2197            main_view_query: QueryState::new(world),
2198            view_light_query: QueryState::new(world),
2199        }
2200    }
2201}
2202
2203impl Node for EarlyShadowPassNode {
2204    fn update(&mut self, world: &mut World) {
2205        self.0.update(world);
2206    }
2207
2208    fn run<'w>(
2209        &self,
2210        graph: &mut RenderGraphContext,
2211        render_context: &mut RenderContext<'w>,
2212        world: &'w World,
2213    ) -> Result<(), NodeRunError> {
2214        self.0.run(graph, render_context, world, false)
2215    }
2216}
2217
2218impl Node for LateShadowPassNode {
2219    fn update(&mut self, world: &mut World) {
2220        self.0.update(world);
2221    }
2222
2223    fn run<'w>(
2224        &self,
2225        graph: &mut RenderGraphContext,
2226        render_context: &mut RenderContext<'w>,
2227        world: &'w World,
2228    ) -> Result<(), NodeRunError> {
2229        self.0.run(graph, render_context, world, true)
2230    }
2231}
2232
2233impl ShadowPassNode {
2234    fn update(&mut self, world: &mut World) {
2235        self.main_view_query.update_archetypes(world);
2236        self.view_light_query.update_archetypes(world);
2237    }
2238
2239    /// Runs the node logic.
2240    ///
2241    /// `is_late` is true if this is the late shadow pass or false if this is
2242    /// the early shadow pass.
2243    fn run<'w>(
2244        &self,
2245        graph: &mut RenderGraphContext,
2246        render_context: &mut RenderContext<'w>,
2247        world: &'w World,
2248        is_late: bool,
2249    ) -> Result<(), NodeRunError> {
2250        let Some(shadow_render_phases) = world.get_resource::<ViewBinnedRenderPhases<Shadow>>()
2251        else {
2252            return Ok(());
2253        };
2254
2255        if let Ok(view_lights) = self.main_view_query.get_manual(world, graph.view_entity()) {
2256            for view_light_entity in view_lights.lights.iter().copied() {
2257                let Ok((view_light, extracted_light_view, occlusion_culling)) =
2258                    self.view_light_query.get_manual(world, view_light_entity)
2259                else {
2260                    continue;
2261                };
2262
2263                // There's no need for a late shadow pass if the light isn't
2264                // using occlusion culling.
2265                if is_late && !occlusion_culling {
2266                    continue;
2267                }
2268
2269                let Some(shadow_phase) =
2270                    shadow_render_phases.get(&extracted_light_view.retained_view_entity)
2271                else {
2272                    continue;
2273                };
2274
2275                let depth_stencil_attachment =
2276                    Some(view_light.depth_attachment.get_attachment(StoreOp::Store));
2277
2278                let diagnostics = render_context.diagnostic_recorder();
2279                render_context.add_command_buffer_generation_task(move |render_device| {
2280                    #[cfg(feature = "trace")]
2281                    let _shadow_pass_span = info_span!("", "{}", view_light.pass_name).entered();
2282                    let mut command_encoder =
2283                        render_device.create_command_encoder(&CommandEncoderDescriptor {
2284                            label: Some("shadow_pass_command_encoder"),
2285                        });
2286
2287                    let render_pass = command_encoder.begin_render_pass(&RenderPassDescriptor {
2288                        label: Some(&view_light.pass_name),
2289                        color_attachments: &[],
2290                        depth_stencil_attachment,
2291                        timestamp_writes: None,
2292                        occlusion_query_set: None,
2293                    });
2294
2295                    let mut render_pass = TrackedRenderPass::new(&render_device, render_pass);
2296                    let pass_span =
2297                        diagnostics.pass_span(&mut render_pass, view_light.pass_name.clone());
2298
2299                    if let Err(err) =
2300                        shadow_phase.render(&mut render_pass, world, view_light_entity)
2301                    {
2302                        error!("Error encountered while rendering the shadow phase {err:?}");
2303                    }
2304
2305                    pass_span.end(&mut render_pass);
2306                    drop(render_pass);
2307                    command_encoder.finish()
2308                });
2309            }
2310        }
2311
2312        Ok(())
2313    }
2314}