Skip to main content

bevy_pbr/render/
light.rs

1use crate::*;
2use alloc::sync::Arc;
3use bevy_asset::UntypedAssetId;
4use bevy_camera::primitives::{
5    face_index_to_name, CascadesFrusta, CubeMapFace, CubemapFrusta, Frustum, CUBE_MAP_FACES,
6};
7use bevy_camera::visibility::{
8    CascadesVisibleEntities, CubemapVisibleEntities, RenderLayers, ViewVisibility,
9    VisibleMeshEntities,
10};
11use bevy_camera::{Camera, Camera3d, RenderTarget, ShadowLodOrigin};
12use bevy_color::ColorToComponents;
13use bevy_core_pipeline::schedule::RootNonCameraView;
14use bevy_derive::{Deref, DerefMut};
15use bevy_ecs::schedule::ScheduleLabel;
16use bevy_ecs::system::SystemChangeTick;
17use bevy_ecs::{
18    entity::{EntityHashMap, EntityHashSet},
19    prelude::*,
20    system::{SystemParam, SystemState},
21};
22use bevy_light::cascade::Cascade;
23use bevy_light::cluster::assign::{calculate_cluster_factors, ClusterableObjectType};
24use bevy_light::SunDisk;
25use bevy_light::{
26    spot_light_clip_from_view, spot_light_world_from_view, AmbientLight, CascadeShadowConfig,
27    Cascades, DirectionalLight, DirectionalLightShadowMap, GlobalAmbientLight, PointLight,
28    PointLightShadowMap, RectLight, ShadowFilteringMethod, SpotLight, VolumetricLight,
29};
30use bevy_log::warn_once;
31use bevy_material::{
32    key::{ErasedMaterialPipelineKey, ErasedMeshPipelineKey},
33    MaterialProperties,
34};
35use bevy_math::{ops, proj, Mat4, UVec4, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles};
36use bevy_mesh::{Mesh3d, MeshVertexBufferLayoutRef};
37use bevy_platform::collections::{HashMap, HashSet};
38use bevy_platform::hash::FixedHasher;
39use bevy_render::batching::gpu_preprocessing::{
40    BuildIndirectParametersMetadata, IndirectParametersBuffers,
41};
42use bevy_render::camera::{DirtySpecializations, PendingQueues};
43use bevy_render::diagnostic::RecordDiagnostics;
44use bevy_render::erased_render_asset::ErasedRenderAssets;
45use bevy_render::mesh::allocator::MeshSlabs;
46use bevy_render::occlusion_culling::{
47    OcclusionCulling, OcclusionCullingSubview, OcclusionCullingSubviewEntities,
48};
49use bevy_render::sync_world::{MainEntity, MainEntityHashMap, MainEntityHashSet, RenderEntity};
50use bevy_render::view::{
51    RenderExtractedShadowMapVisibleEntities, RenderShadowLodOrigin, RenderShadowMapVisibleEntities,
52    RenderVisibleEntities, VisibilityExtractionSystemParam,
53};
54use bevy_render::{
55    batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport},
56    camera::SortedCameras,
57    mesh::allocator::MeshAllocator,
58    view::{NoIndirectDrawing, RetainedViewEntity},
59};
60use bevy_render::{
61    mesh::RenderMesh,
62    render_asset::RenderAssets,
63    render_phase::*,
64    render_resource::*,
65    renderer::{RenderContext, RenderDevice, RenderQueue, ViewQuery},
66    texture::*,
67    view::ExtractedView,
68    Extract,
69};
70use bevy_shape::{HalfSpace, ViewFrustum};
71use bevy_transform::{components::GlobalTransform, prelude::Transform};
72use bevy_utils::default;
73use core::{any::TypeId, hash::Hash, mem, ops::Range};
74use decal::clustered::RenderClusteredDecals;
75#[cfg(feature = "trace")]
76use tracing::info_span;
77use tracing::{error, warn};
78
79#[derive(Component)]
80#[require(PointAndSpotLightViewEntities)]
81pub struct ExtractedPointLight {
82    pub color: LinearRgba,
83    /// luminous intensity in lumens per steradian
84    pub intensity: f32,
85    pub range: f32,
86    pub radius: f32,
87    pub transform: GlobalTransform,
88    pub shadow_maps_enabled: bool,
89    pub contact_shadows_enabled: bool,
90    pub shadow_depth_bias: f32,
91    pub shadow_normal_bias: f32,
92    pub shadow_map_near_z: f32,
93    pub spot_light_angles: Option<(f32, f32)>,
94    pub volumetric: bool,
95    pub soft_shadows_enabled: bool,
96    /// whether this point light contributes diffuse light to lightmapped meshes
97    pub affects_lightmapped_mesh_diffuse: bool,
98}
99
100#[derive(Component, Debug)]
101pub struct ExtractedRectLight {
102    pub color: LinearRgba,
103    pub intensity: f32,
104    pub range: f32,
105    pub width: f32,
106    pub height: f32,
107    pub transform: GlobalTransform,
108}
109
110#[derive(Component, Debug)]
111#[require(DirectionalLightViewEntities)]
112pub struct ExtractedDirectionalLight {
113    pub color: LinearRgba,
114    pub illuminance: f32,
115    pub transform: GlobalTransform,
116    pub shadow_maps_enabled: bool,
117    pub contact_shadows_enabled: bool,
118    pub volumetric: bool,
119    /// whether this directional light contributes diffuse light to lightmapped
120    /// meshes
121    pub affects_lightmapped_mesh_diffuse: bool,
122    pub shadow_depth_bias: f32,
123    pub shadow_normal_bias: f32,
124    pub cascade_shadow_config: CascadeShadowConfig,
125    pub cascades: EntityHashMap<Vec<Cascade>>,
126    pub frusta: EntityHashMap<Vec<Frustum>>,
127    pub soft_shadow_size: Option<f32>,
128    /// True if this light is using two-phase occlusion culling.
129    pub occlusion_culling: bool,
130    pub sun_disk_angular_size: f32,
131    pub sun_disk_intensity: f32,
132}
133
134// NOTE: These must match the bit flags in bevy_pbr/src/render/mesh_view_types.wesl!
135bitflags::bitflags! {
136    #[repr(transparent)]
137    struct PointLightFlags: u32 {
138        const SHADOW_MAPS_ENABLED               = 1 << 0;
139        const SPOT_LIGHT_Y_NEGATIVE             = 1 << 1;
140        const VOLUMETRIC                        = 1 << 2;
141        const AFFECTS_LIGHTMAPPED_MESH_DIFFUSE  = 1 << 3;
142        const CONTACT_SHADOWS_ENABLED           = 1 << 4;
143        const SPOT_LIGHT                        = 1 << 5;
144        const RECT_LIGHT                        = 1 << 6;
145        const NONE                              = 0;
146        const UNINITIALIZED                     = 0xFFFF;
147    }
148}
149
150#[derive(Copy, Clone, ShaderType, Default, Debug)]
151pub struct GpuDirectionalCascade {
152    clip_from_world: Mat4,
153    texel_size: f32,
154    far_bound: f32,
155}
156
157#[derive(Copy, Clone, ShaderType, Default, Debug)]
158pub struct GpuDirectionalLight {
159    cascades: [GpuDirectionalCascade; MAX_CASCADES_PER_LIGHT],
160    color: Vec4,
161    dir_to_light: Vec3,
162    flags: u32,
163    soft_shadow_size: f32,
164    shadow_depth_bias: f32,
165    shadow_normal_bias: f32,
166    num_cascades: u32,
167    cascades_overlap_proportion: f32,
168    depth_texture_base_index: u32,
169    decal_index: u32,
170    sun_disk_angular_size: f32,
171    sun_disk_intensity: f32,
172}
173
174// NOTE: These must match the bit flags in bevy_pbr/src/render/mesh_view_types.wesl!
175bitflags::bitflags! {
176    #[repr(transparent)]
177    struct DirectionalLightFlags: u32 {
178        const SHADOW_MAPS_ENABLED               = 1 << 0;
179        const VOLUMETRIC                        = 1 << 1;
180        const AFFECTS_LIGHTMAPPED_MESH_DIFFUSE  = 1 << 2;
181        const CONTACT_SHADOWS_ENABLED           = 1 << 3;
182        const NONE                              = 0;
183        const UNINITIALIZED                     = 0xFFFF;
184    }
185}
186
187#[derive(Copy, Clone, ShaderType, Default, Debug)]
188pub struct GpuRectLight {
189    color: Vec4,
190    position: Vec3,
191    width: f32,
192    right: Vec3,
193    height: f32,
194    up: Vec3,
195    range: f32,
196}
197
198// NOTE: These must match the bit flags in bevy_pbr/src/render/mesh_view_types.wesl!
199bitflags::bitflags! {
200    #[repr(transparent)]
201    struct AmbientLightFlags: u32 {
202        const AFFECTS_LIGHTMAPPED_MESHES        = 1 << 0;
203        const NONE                              = 0;
204        const UNINITIALIZED                     = 0xFFFF;
205    }
206}
207
208#[derive(Copy, Clone, Debug, ShaderType)]
209pub struct GpuLights {
210    directional_lights: [GpuDirectionalLight; MAX_DIRECTIONAL_LIGHTS],
211    ambient_color: Vec4,
212    // xyz are x/y/z cluster dimensions and w is the number of clusters
213    cluster_dimensions: UVec4,
214    // xy are vec2<f32>(cluster_dimensions.xy) / vec2<f32>(view.width, view.height)
215    // z is cluster_dimensions.z / log(far / near)
216    // w is cluster_dimensions.z * log(near) / log(far / near)
217    cluster_factors: Vec4,
218    n_directional_lights: u32,
219    // offset from spot light's light index to spot light's shadow map index
220    spot_light_shadowmap_offset: i32,
221    ambient_light_flags: u32,
222    // this is unused if we have access to storage buffers, in which case rect lights are clustered
223    n_rect_lights: u32,
224    rect_lights: [GpuRectLight; MAX_RECT_LIGHTS],
225}
226
227// NOTE: When running bevy on Adreno GPU chipsets in WebGL, any value above 1 will result in a crash
228// when loading "pbr_functions.wesl" in the function apply_fog.
229#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
230pub const MAX_DIRECTIONAL_LIGHTS: usize = 1;
231#[cfg(any(
232    not(feature = "webgl"),
233    not(target_arch = "wasm32"),
234    feature = "webgpu"
235))]
236pub const MAX_DIRECTIONAL_LIGHTS: usize = 10;
237#[cfg(any(
238    not(feature = "webgl"),
239    not(target_arch = "wasm32"),
240    feature = "webgpu"
241))]
242pub const MAX_CASCADES_PER_LIGHT: usize = 4;
243#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
244pub const MAX_CASCADES_PER_LIGHT: usize = 1;
245
246pub const MAX_RECT_LIGHTS: usize = 8;
247
248#[derive(Resource, Clone)]
249pub struct ShadowSamplers {
250    pub point_light_comparison_sampler: Sampler,
251    #[cfg(feature = "experimental_pbr_pcss")]
252    pub point_light_linear_sampler: Sampler,
253    pub directional_light_comparison_sampler: Sampler,
254    #[cfg(feature = "experimental_pbr_pcss")]
255    pub directional_light_linear_sampler: Sampler,
256}
257
258pub fn init_shadow_samplers(mut commands: Commands, render_device: Res<RenderDevice>) {
259    let base_sampler_descriptor = SamplerDescriptor {
260        address_mode_u: AddressMode::ClampToEdge,
261        address_mode_v: AddressMode::ClampToEdge,
262        address_mode_w: AddressMode::ClampToEdge,
263        mag_filter: FilterMode::Linear,
264        min_filter: FilterMode::Linear,
265        mipmap_filter: MipmapFilterMode::Nearest,
266        ..default()
267    };
268
269    commands.insert_resource(ShadowSamplers {
270        point_light_comparison_sampler: render_device.create_sampler(&SamplerDescriptor {
271            compare: Some(CompareFunction::GreaterEqual),
272            ..base_sampler_descriptor
273        }),
274        #[cfg(feature = "experimental_pbr_pcss")]
275        point_light_linear_sampler: render_device.create_sampler(&base_sampler_descriptor),
276        directional_light_comparison_sampler: render_device.create_sampler(&SamplerDescriptor {
277            compare: Some(CompareFunction::GreaterEqual),
278            ..base_sampler_descriptor
279        }),
280        #[cfg(feature = "experimental_pbr_pcss")]
281        directional_light_linear_sampler: render_device.create_sampler(&base_sampler_descriptor),
282    });
283}
284
285// This is needed because of the orphan rule not allowing implementing
286// foreign trait ExtractComponent on foreign type ShadowFilteringMethod
287pub fn extract_shadow_filtering_method(
288    mut commands: Commands,
289    mut previous_len: Local<usize>,
290    query: Extract<Query<(RenderEntity, &ShadowFilteringMethod)>>,
291) {
292    let mut values = Vec::with_capacity(*previous_len);
293    for (entity, query_item) in &query {
294        values.push((entity, *query_item));
295    }
296    *previous_len = values.len();
297    commands.try_insert_batch(values);
298}
299
300// This is needed because of the orphan rule not allowing implementing
301// foreign trait ExtractResource on foreign type AmbientLight
302pub fn extract_ambient_light_resource(
303    mut commands: Commands,
304    main_resource: Extract<Option<Res<GlobalAmbientLight>>>,
305    target_resource: Option<ResMut<GlobalAmbientLight>>,
306) {
307    if let Some(main_resource) = main_resource.as_ref() {
308        if let Some(mut target_resource) = target_resource {
309            if main_resource.is_changed() {
310                *target_resource = (*main_resource).clone();
311            }
312        } else {
313            commands.insert_resource((*main_resource).clone());
314        }
315    }
316}
317
318// This is needed because of the orphan rule not allowing implementing
319// foreign trait ExtractComponent on foreign type AmbientLight
320pub fn extract_ambient_light(
321    mut commands: Commands,
322    mut previous_len: Local<usize>,
323    query: Extract<Query<(RenderEntity, &AmbientLight)>>,
324) {
325    let mut values = Vec::with_capacity(*previous_len);
326    for (entity, query_item) in &query {
327        values.push((entity, query_item.clone()));
328    }
329    *previous_len = values.len();
330    commands.try_insert_batch(values);
331}
332
333pub fn extract_lights(
334    mut commands: Commands,
335    point_light_shadow_map: Extract<Res<PointLightShadowMap>>,
336    directional_light_shadow_map: Extract<Res<DirectionalLightShadowMap>>,
337    point_lights: Extract<
338        Query<
339            (
340                Entity,
341                RenderEntity,
342                &PointLight,
343                &CubemapVisibleEntities,
344                &GlobalTransform,
345                &ViewVisibility,
346                &CubemapFrusta,
347                Option<&RenderLayers>,
348                Option<&VolumetricLight>,
349            ),
350            Or<(
351                Changed<PointLight>,
352                Changed<CubemapVisibleEntities>,
353                Changed<GlobalTransform>,
354                Changed<ViewVisibility>,
355                Changed<RenderLayers>,
356                Changed<CubemapFrusta>,
357                Changed<VolumetricLight>,
358            )>,
359        >,
360    >,
361    spot_lights: Extract<
362        Query<
363            (
364                Entity,
365                RenderEntity,
366                &SpotLight,
367                &VisibleMeshEntities,
368                &GlobalTransform,
369                &ViewVisibility,
370                &Frustum,
371                Option<&RenderLayers>,
372                Option<&VolumetricLight>,
373            ),
374            Or<(
375                Changed<SpotLight>,
376                Changed<VisibleMeshEntities>,
377                Changed<GlobalTransform>,
378                Changed<ViewVisibility>,
379                Changed<RenderLayers>,
380                Changed<Frustum>,
381                Changed<VolumetricLight>,
382            )>,
383        >,
384    >,
385    directional_lights: Extract<
386        Query<
387            (
388                Entity,
389                RenderEntity,
390                &DirectionalLight,
391                &CascadesVisibleEntities,
392                &Cascades,
393                &CascadeShadowConfig,
394                &CascadesFrusta,
395                &GlobalTransform,
396                &ViewVisibility,
397                Option<&RenderLayers>,
398                Option<&VolumetricLight>,
399                Has<OcclusionCulling>,
400                Option<&SunDisk>,
401            ),
402            (
403                Without<SpotLight>,
404                Or<(
405                    Changed<DirectionalLight>,
406                    Changed<CascadesVisibleEntities>,
407                    Changed<Cascades>,
408                    Changed<CascadeShadowConfig>,
409                    Changed<CascadesFrusta>,
410                    Changed<GlobalTransform>,
411                    Changed<ViewVisibility>,
412                    Changed<RenderLayers>,
413                    Changed<VolumetricLight>,
414                    Changed<OcclusionCulling>,
415                    Changed<SunDisk>,
416                )>,
417            ),
418        >,
419    >,
420    rect_lights: Extract<
421        Query<
422            (
423                Entity,
424                RenderEntity,
425                &RectLight,
426                &GlobalTransform,
427                &ViewVisibility,
428                Option<&RenderLayers>,
429            ),
430            Or<(
431                Changed<RectLight>,
432                Changed<GlobalTransform>,
433                Changed<ViewVisibility>,
434                Changed<RenderLayers>,
435            )>,
436        >,
437    >,
438    visibility_extraction_system_param: VisibilityExtractionSystemParam,
439    mut existing_render_shadow_map_visible_entities: Query<(
440        Entity,
441        &mut RenderExtractedShadowMapVisibleEntities,
442        &mut RenderShadowMapVisibleEntities,
443    )>,
444    mut all_lights_found: Local<EntityHashSet>,
445    mut rect_light_missing_luts_warning_emitted: Local<bool>,
446    system_change_tick: SystemChangeTick,
447) {
448    let mapper = &visibility_extraction_system_param.mapper;
449
450    // NOTE: These shadow map resources are extracted here as they are used here too so this avoids
451    // races between scheduling of ExtractResourceSystems and this system.
452    if point_light_shadow_map.is_changed() {
453        commands.insert_resource(point_light_shadow_map.clone());
454    }
455    if directional_light_shadow_map.is_changed() {
456        commands.insert_resource(directional_light_shadow_map.clone());
457    }
458
459    // This is the point light shadow map texel size for one face of the cube as a distance of 1.0
460    // world unit from the light.
461    // point_light_texel_size = 2.0 * 1.0 * tan(PI / 4.0) / cube face width in texels
462    // PI / 4.0 is half the cube face fov, tan(PI / 4.0) = 1.0, so this simplifies to:
463    // point_light_texel_size = 2.0 / cube face width in texels
464    // NOTE: When using various PCF kernel sizes, this will need to be adjusted, according to:
465    // https://catlikecoding.com/unity/tutorials/custom-srp/point-and-spot-shadows/
466    let point_light_texel_size = 2.0 / point_light_shadow_map.size as f32;
467
468    all_lights_found.clear();
469
470    for (
471        main_entity,
472        render_entity,
473        point_light,
474        cubemap_visible_entities,
475        transform,
476        view_visibility,
477        frusta,
478        maybe_render_layers,
479        volumetric_light,
480    ) in point_lights.iter()
481    {
482        if !view_visibility.get() {
483            if let Ok(mut entity_commands) = commands.get_entity(render_entity) {
484                entity_commands.remove::<ExtractedPointLight>();
485            }
486            continue;
487        }
488
489        if point_light.shadow_maps_enabled {
490            // Fetch or create the visible entities for each cubemap face.
491            let (
492                mut render_extracted_shadow_map_visible_entities,
493                mut render_shadow_map_visible_entities,
494            ) = match existing_render_shadow_map_visible_entities.get_mut(render_entity) {
495                Ok((
496                    _,
497                    ref mut existing_extracted_shadow_map_visible_entities,
498                    ref mut existing_shadow_map_visible_entities,
499                )) => (
500                    mem::take(&mut **existing_extracted_shadow_map_visible_entities),
501                    mem::take(&mut **existing_shadow_map_visible_entities),
502                ),
503                Err(_) => (
504                    RenderExtractedShadowMapVisibleEntities::default(),
505                    RenderShadowMapVisibleEntities::default(),
506                ),
507            };
508
509            all_lights_found.insert(render_entity);
510
511            for face_index in 0..6 {
512                let retained_view_entity = RetainedViewEntity {
513                    main_entity: MainEntity::from(main_entity),
514                    auxiliary_entity: None,
515                    subview_index: face_index,
516                };
517                render_shadow_map_visible_entities
518                    .subviews
519                    .entry(retained_view_entity)
520                    .or_insert_with(|| (system_change_tick.this_run(), Default::default()));
521
522                // Extract the visible entities to the list for this face.
523                let extracted_entities = &mut render_extracted_shadow_map_visible_entities
524                    .subviews
525                    .entry(retained_view_entity)
526                    .or_default()
527                    .classes
528                    .entry(TypeId::of::<Mesh3d>())
529                    .or_default()
530                    .entities;
531                extracted_entities.clear();
532                let visible_mesh_entities = cubemap_visible_entities.get(face_index as usize);
533                extracted_entities.extend(visible_mesh_entities.entities.iter().map(
534                    |main_entity| {
535                        let render_entity = match mapper.get(*main_entity) {
536                            Ok(render_entity) => **render_entity,
537                            Err(_) => Entity::PLACEHOLDER,
538                        };
539                        (render_entity, MainEntity::from(*main_entity))
540                    },
541                ));
542            }
543
544            // Clear out visible entity lists corresponding to subviews that no
545            // longer exist.
546            render_extracted_shadow_map_visible_entities
547                .subviews
548                .retain(|view_entity, _| {
549                    view_entity.main_entity.entity() == main_entity
550                        && view_entity.auxiliary_entity.is_none()
551                        && view_entity.subview_index < 6
552                });
553            render_shadow_map_visible_entities
554                .subviews
555                .retain(|view_entity, _| {
556                    view_entity.main_entity.entity() == main_entity
557                        && view_entity.auxiliary_entity.is_none()
558                        && view_entity.subview_index < 6
559                });
560
561            let mut entity_commands = commands.entity(render_entity);
562            entity_commands.insert((
563                render_extracted_shadow_map_visible_entities,
564                render_shadow_map_visible_entities,
565            ));
566        }
567
568        let mut entity_commands = commands.entity(render_entity);
569        let extracted_point_light = ExtractedPointLight {
570            color: point_light.color.into(),
571            // NOTE: Map from luminous power in lumens to luminous intensity in lumens per steradian
572            // for a point light. See https://google.github.io/filament/Filament.md.html#mjx-eqn-pointLightLuminousPower
573            // for details.
574            intensity: point_light.intensity / (4.0 * core::f32::consts::PI),
575            range: point_light.range,
576            radius: point_light.radius,
577            transform: *transform,
578            shadow_maps_enabled: point_light.shadow_maps_enabled,
579            contact_shadows_enabled: point_light.contact_shadows_enabled,
580            shadow_depth_bias: point_light.shadow_depth_bias,
581            // The factor of SQRT_2 is for the worst-case diagonal offset
582            shadow_normal_bias: point_light.shadow_normal_bias
583                * point_light_texel_size
584                * core::f32::consts::SQRT_2,
585            shadow_map_near_z: point_light.shadow_map_near_z,
586            spot_light_angles: None,
587            volumetric: volumetric_light.is_some(),
588            affects_lightmapped_mesh_diffuse: point_light.affects_lightmapped_mesh_diffuse,
589            #[cfg(feature = "experimental_pbr_pcss")]
590            soft_shadows_enabled: point_light.soft_shadows_enabled,
591            #[cfg(not(feature = "experimental_pbr_pcss"))]
592            soft_shadows_enabled: false,
593        };
594        entity_commands.insert((
595            extracted_point_light,
596            (*frusta).clone(),
597            MainEntity::from(main_entity),
598            maybe_render_layers.unwrap_or_default().clone(),
599        ));
600    }
601
602    for (
603        main_entity,
604        render_entity,
605        spot_light,
606        visible_entities,
607        transform,
608        view_visibility,
609        frustum,
610        maybe_render_layers,
611        volumetric_light,
612    ) in spot_lights.iter()
613    {
614        if !view_visibility.get() {
615            if let Ok(mut entity_commands) = commands.get_entity(render_entity) {
616                entity_commands.remove::<ExtractedPointLight>();
617            }
618            continue;
619        }
620
621        if spot_light.shadow_maps_enabled {
622            // Fetch or create the visible entities.
623            let (
624                mut render_extracted_shadow_map_visible_entities,
625                mut render_shadow_map_visible_entities,
626            ) = match existing_render_shadow_map_visible_entities.get_mut(render_entity) {
627                Ok((
628                    _,
629                    ref mut existing_extracted_shadow_map_visible_entities,
630                    ref mut existing_shadow_map_visible_entities,
631                )) => (
632                    mem::take(&mut **existing_extracted_shadow_map_visible_entities),
633                    mem::take(&mut **existing_shadow_map_visible_entities),
634                ),
635                Err(_) => (
636                    RenderExtractedShadowMapVisibleEntities::default(),
637                    RenderShadowMapVisibleEntities::default(),
638                ),
639            };
640
641            all_lights_found.insert(render_entity);
642
643            let retained_view_entity = RetainedViewEntity {
644                main_entity: MainEntity::from(main_entity),
645                auxiliary_entity: None,
646                subview_index: 0,
647            };
648            render_shadow_map_visible_entities
649                .subviews
650                .entry(retained_view_entity)
651                .or_insert_with(|| (system_change_tick.this_run(), Default::default()));
652
653            // Extract the visible CPU culled entities to the list.
654            let entities_cpu_culling = &mut render_extracted_shadow_map_visible_entities
655                .subviews
656                .entry(retained_view_entity)
657                .or_default()
658                .classes
659                .entry(TypeId::of::<Mesh3d>())
660                .or_default()
661                .entities;
662            entities_cpu_culling.clear();
663            entities_cpu_culling.extend(visible_entities.entities.iter().map(|main_entity| {
664                let render_entity = match mapper.get(*main_entity) {
665                    Ok(render_entity) => **render_entity,
666                    Err(_) => Entity::PLACEHOLDER,
667                };
668                (render_entity, MainEntity::from(*main_entity))
669            }));
670
671            // Clear out visible entity lists corresponding to subviews that no
672            // longer exist.
673            render_extracted_shadow_map_visible_entities
674                .subviews
675                .retain(|view_entity, _| retained_view_entity == *view_entity);
676            render_shadow_map_visible_entities
677                .subviews
678                .retain(|view_entity, _| retained_view_entity == *view_entity);
679
680            let mut entity_commands = commands.entity(render_entity);
681            entity_commands.insert((
682                render_extracted_shadow_map_visible_entities,
683                render_shadow_map_visible_entities,
684            ));
685        }
686
687        // `outer_angle` must be <Ï€/2.
688        let (inner_angle, outer_angle) = {
689            let mut outer_angle = spot_light.outer_angle;
690            if outer_angle >= core::f32::consts::FRAC_PI_2 {
691                warn_once!(
692                    "A `SpotLight` has `outer_angle` {} >= π/2 (~1.57079), clamping it to just below π/2. \
693                     Spot lights require `outer_angle < π/2`",
694                    outer_angle
695                );
696                outer_angle = core::f32::consts::FRAC_PI_2 - 1e-4;
697            }
698            // Keep inner_angle <= outer_angle after clamping.
699            (spot_light.inner_angle.min(outer_angle), outer_angle)
700        };
701
702        let texel_size = 2.0 * ops::tan(outer_angle) / directional_light_shadow_map.size as f32;
703
704        let mut entity_commands = commands.entity(render_entity);
705        let extracted_spot_light = ExtractedPointLight {
706            color: spot_light.color.into(),
707            // NOTE: Map from luminous power in lumens to luminous intensity in lumens per steradian
708            // for a point light. See https://google.github.io/filament/Filament.md.html#mjx-eqn-pointLightLuminousPower
709            // for details.
710            // Note: Filament uses a divisor of PI for spot lights. We choose to use the same 4*PI divisor
711            // in both cases so that toggling between point light and spot light keeps lit areas lit equally,
712            // which seems least surprising for users
713            intensity: spot_light.intensity / (4.0 * core::f32::consts::PI),
714            range: spot_light.range,
715            radius: spot_light.radius,
716            transform: *transform,
717            shadow_maps_enabled: spot_light.shadow_maps_enabled,
718            contact_shadows_enabled: spot_light.contact_shadows_enabled,
719            shadow_depth_bias: spot_light.shadow_depth_bias,
720            // The factor of SQRT_2 is for the worst-case diagonal offset
721            shadow_normal_bias: spot_light.shadow_normal_bias
722                * texel_size
723                * core::f32::consts::SQRT_2,
724            shadow_map_near_z: spot_light.shadow_map_near_z,
725            spot_light_angles: Some((inner_angle, outer_angle)),
726            volumetric: volumetric_light.is_some(),
727            affects_lightmapped_mesh_diffuse: spot_light.affects_lightmapped_mesh_diffuse,
728            #[cfg(feature = "experimental_pbr_pcss")]
729            soft_shadows_enabled: spot_light.soft_shadows_enabled,
730            #[cfg(not(feature = "experimental_pbr_pcss"))]
731            soft_shadows_enabled: false,
732        };
733        entity_commands.insert((
734            extracted_spot_light,
735            *frustum,
736            MainEntity::from(main_entity),
737            maybe_render_layers.unwrap_or_default().clone(),
738        ));
739    }
740
741    for (
742        main_entity,
743        render_entity,
744        directional_light,
745        visible_entities,
746        cascades,
747        cascade_config,
748        frusta,
749        transform,
750        view_visibility,
751        maybe_render_layers,
752        volumetric_light,
753        occlusion_culling,
754        sun_disk,
755    ) in &directional_lights
756    {
757        if !view_visibility.get() {
758            commands
759                .get_entity(render_entity)
760                .expect("Light entity wasn't synced.")
761                .remove::<(
762                    ExtractedDirectionalLight,
763                    RenderExtractedShadowMapVisibleEntities,
764                )>();
765            continue;
766        }
767
768        // TODO: update in place instead of reinserting.
769        let mut extracted_cascades = EntityHashMap::default();
770        let mut extracted_frusta = EntityHashMap::default();
771
772        if directional_light.shadow_maps_enabled {
773            // Fetch or create the visible entities set for each cascade.
774            let (
775                mut existing_extracted_shadow_map_visible_entities,
776                mut existing_shadow_map_visible_entities,
777            ) = match existing_render_shadow_map_visible_entities.get_mut(render_entity) {
778                Ok((
779                    _,
780                    ref mut existing_extracted_shadow_map_visible_entities,
781                    ref mut existing_shadow_map_visible_entities,
782                )) => (
783                    mem::take(&mut **existing_extracted_shadow_map_visible_entities),
784                    mem::take(&mut **existing_shadow_map_visible_entities),
785                ),
786                Err(_) => (
787                    RenderExtractedShadowMapVisibleEntities::default(),
788                    RenderShadowMapVisibleEntities::default(),
789                ),
790            };
791
792            all_lights_found.insert(render_entity);
793
794            for (e, v) in cascades.cascades.iter() {
795                if let Ok(entity) = mapper.get(*e) {
796                    extracted_cascades.insert(**entity, v.clone());
797                }
798            }
799            for (e, v) in frusta.frusta.iter() {
800                if let Ok(entity) = mapper.get(*e) {
801                    extracted_frusta.insert(**entity, v.clone());
802                }
803            }
804            // Calculate the added and removed entities for each cascade.
805            let mut all_cascades_seen = HashSet::new();
806            for (main_auxiliary_entity, visible_mesh_entities_list) in
807                visible_entities.entities.iter()
808            {
809                for subview_index in 0..(cascade_config.bounds.len() as u32) {
810                    let retained_view_entity = RetainedViewEntity {
811                        main_entity: MainEntity::from(main_entity),
812                        auxiliary_entity: (*main_auxiliary_entity != Entity::PLACEHOLDER)
813                            .then_some(MainEntity::from(*main_auxiliary_entity)),
814                        subview_index,
815                    };
816                    all_cascades_seen.insert(retained_view_entity);
817
818                    existing_shadow_map_visible_entities
819                        .subviews
820                        .entry(retained_view_entity)
821                        .or_insert_with(|| (system_change_tick.this_run(), Default::default()));
822
823                    // Extract the visible CPU culled entities to the list.
824                    let extracted_entities = &mut existing_extracted_shadow_map_visible_entities
825                        .subviews
826                        .entry(retained_view_entity)
827                        .or_default()
828                        .classes
829                        .entry(TypeId::of::<Mesh3d>())
830                        .or_default()
831                        .entities;
832                    extracted_entities.clear();
833                    let Some(visible_mesh_entities) =
834                        visible_mesh_entities_list.get(subview_index as usize)
835                    else {
836                        continue;
837                    };
838                    extracted_entities.extend(visible_mesh_entities.entities.iter().map(
839                        |main_entity| {
840                            let render_entity = match mapper.get(*main_entity) {
841                                Ok(render_entity) => **render_entity,
842                                Err(_) => Entity::PLACEHOLDER,
843                            };
844                            (render_entity, MainEntity::from(*main_entity))
845                        },
846                    ));
847                }
848            }
849
850            // Clear out visible entity lists corresponding to cascades that no
851            // longer exist.
852            existing_extracted_shadow_map_visible_entities
853                .subviews
854                .retain(|cascade_entity, _| all_cascades_seen.contains(cascade_entity));
855            existing_shadow_map_visible_entities
856                .subviews
857                .retain(|cascade_entity, _| all_cascades_seen.contains(cascade_entity));
858
859            let mut entity_commands = commands.entity(render_entity);
860            entity_commands.insert((
861                existing_extracted_shadow_map_visible_entities,
862                existing_shadow_map_visible_entities,
863            ));
864        }
865
866        let extracted_directional_light = ExtractedDirectionalLight {
867            color: directional_light.color.into(),
868            illuminance: directional_light.illuminance,
869            transform: *transform,
870            volumetric: volumetric_light.is_some(),
871            affects_lightmapped_mesh_diffuse: directional_light.affects_lightmapped_mesh_diffuse,
872            #[cfg(feature = "experimental_pbr_pcss")]
873            soft_shadow_size: directional_light.soft_shadow_size,
874            #[cfg(not(feature = "experimental_pbr_pcss"))]
875            soft_shadow_size: None,
876            shadow_maps_enabled: directional_light.shadow_maps_enabled,
877            contact_shadows_enabled: directional_light.contact_shadows_enabled,
878            shadow_depth_bias: directional_light.shadow_depth_bias,
879            // The factor of SQRT_2 is for the worst-case diagonal offset
880            shadow_normal_bias: directional_light.shadow_normal_bias * core::f32::consts::SQRT_2,
881            cascade_shadow_config: cascade_config.clone(),
882            cascades: extracted_cascades,
883            frusta: extracted_frusta,
884            occlusion_culling,
885            sun_disk_angular_size: sun_disk.unwrap_or_default().angular_size,
886            sun_disk_intensity: sun_disk.unwrap_or_default().intensity,
887        };
888
889        let mut entity_commands = commands
890            .get_entity(render_entity)
891            .expect("Light entity wasn't synced.");
892        entity_commands.insert((
893            extracted_directional_light,
894            MainEntity::from(main_entity),
895            maybe_render_layers.unwrap_or_default().clone(),
896        ));
897    }
898
899    for (main_entity, render_entity, rect_light, transform, view_visibility, maybe_render_layers) in
900        &rect_lights
901    {
902        if !cfg!(feature = "area_light_luts") && !*rect_light_missing_luts_warning_emitted {
903            warn!(
904                "RectLight will not work properly because the `area_light_luts` cargo feature is not enabled."
905            );
906            *rect_light_missing_luts_warning_emitted = true;
907        }
908        if !view_visibility.get() {
909            if let Ok(mut entity_commands) = commands.get_entity(render_entity) {
910                entity_commands.remove::<ExtractedRectLight>();
911            }
912            continue;
913        }
914
915        all_lights_found.insert(render_entity);
916
917        let affine = transform.affine();
918        let effective_width = rect_light.width * affine.matrix3.x_axis.length();
919        let effective_height = rect_light.height * affine.matrix3.y_axis.length();
920        let mut entity_commands = commands
921            .get_entity(render_entity)
922            .expect("RectLight entity wasn't synced.");
923        entity_commands.insert((
924            ExtractedRectLight {
925                color: rect_light.color.into(),
926                intensity: rect_light.intensity
927                    / (effective_width * effective_height * core::f32::consts::PI),
928                width: effective_width,
929                height: effective_height,
930                range: rect_light.range,
931                transform: *transform,
932            },
933            MainEntity::from(main_entity),
934            maybe_render_layers.unwrap_or_default().clone(),
935        ));
936    }
937
938    // Clear out any shadow maps that correspond to lights that have become
939    // invisible or have their shadow mapping turned off.
940    for (render_entity, _, _) in existing_render_shadow_map_visible_entities
941        .iter()
942        .filter(|(render_entity, _, _)| !all_lights_found.contains(render_entity))
943    {
944        let Ok(mut entity_commands) = commands.get_entity(render_entity) else {
945            return;
946        };
947        entity_commands.remove::<(
948            RenderExtractedShadowMapVisibleEntities,
949            RenderShadowMapVisibleEntities,
950        )>();
951    }
952}
953
954#[derive(Component, Default, Deref, DerefMut)]
955/// Component automatically attached to a light entity to track light-view entities
956/// for each view.
957pub struct DirectionalLightViewEntities(EntityHashMap<Vec<Entity>>);
958
959pub(crate) fn remove_light_view_entities(
960    remove: On<Remove<DirectionalLightViewEntities>>,
961    query: Query<&DirectionalLightViewEntities>,
962    mut commands: Commands,
963) {
964    if let Ok(entities) = query.get(remove.entity) {
965        for v in entities.0.values() {
966            for e in v.iter().copied() {
967                if let Ok(mut v) = commands.get_entity(e) {
968                    v.despawn();
969                }
970            }
971        }
972    }
973}
974
975pub(crate) fn remove_point_and_spot_light_view_entities(
976    remove: On<Remove<PointAndSpotLightViewEntities>>,
977    query: Query<&PointAndSpotLightViewEntities>,
978    mut commands: Commands,
979) {
980    if let Ok(entities) = query.get(remove.entity) {
981        for e in entities.0.iter().copied() {
982            if let Ok(mut v) = commands.get_entity(e) {
983                v.despawn();
984            }
985        }
986    }
987}
988
989/// A component that stores the shadow maps associated with a point or spot
990/// light.
991///
992/// This component is placed on the light, because these types of shadow maps
993/// aren't associated with views.
994#[derive(Component, Default, Deref, DerefMut)]
995pub struct PointAndSpotLightViewEntities(Vec<Entity>);
996
997#[derive(Component)]
998pub struct ShadowView {
999    pub depth_attachment: DepthStencilViewAttachment,
1000    pub pass_name: String,
1001}
1002
1003#[derive(Component)]
1004pub struct ViewShadowBindings {
1005    pub point_light_depth_texture: Texture,
1006    pub point_light_depth_texture_view: TextureView,
1007    pub directional_light_depth_texture: Texture,
1008    pub directional_light_depth_texture_view: TextureView,
1009}
1010
1011/// A component that holds the shadow cascade views for all shadow cascades
1012/// associated with a camera.
1013///
1014/// Note: Despite the name, this component actually holds the shadow cascade
1015/// views, not the lights themselves.
1016#[derive(Component)]
1017pub struct ViewLightEntities {
1018    /// The shadow cascade views for all shadow cascades associated with a
1019    /// camera.
1020    ///
1021    /// Note: Despite the name, this component actually holds the shadow cascade
1022    /// views, not the lights themselves.
1023    pub lights: Vec<Entity>,
1024}
1025
1026#[derive(Component)]
1027pub struct ViewLightsUniformOffset {
1028    pub offset: u32,
1029}
1030
1031#[derive(Resource, Default)]
1032pub struct LightMeta {
1033    pub view_gpu_lights: DynamicUniformBuffer<GpuLights>,
1034}
1035
1036#[derive(Component)]
1037pub enum LightEntity {
1038    Directional {
1039        light_entity: Entity,
1040        cascade_index: usize,
1041    },
1042    Point {
1043        light_entity: Entity,
1044        face_index: usize,
1045    },
1046    Spot {
1047        light_entity: Entity,
1048    },
1049}
1050
1051pub fn prepare_lights(
1052    mut commands: Commands,
1053    mut texture_cache: ResMut<TextureCache>,
1054    (render_device, render_queue): (Res<RenderDevice>, Res<RenderQueue>),
1055    mut global_clusterable_object_meta: ResMut<GlobalClusterableObjectMeta>,
1056    mut light_meta: ResMut<LightMeta>,
1057    views: Query<
1058        (
1059            Entity,
1060            MainEntity,
1061            &ExtractedView,
1062            &ExtractedClusterConfig,
1063            Option<&RenderLayers>,
1064            Has<NoIndirectDrawing>,
1065            Option<&AmbientLight>,
1066        ),
1067        With<Camera3d>,
1068    >,
1069    ambient_light: Res<GlobalAmbientLight>,
1070    point_light_shadow_map: Res<PointLightShadowMap>,
1071    directional_light_shadow_map: Res<DirectionalLightShadowMap>,
1072    mut shadow_render_phases: ResMut<ViewBinnedRenderPhases<Shadow>>,
1073    (
1074        mut max_directional_lights_warning_emitted,
1075        mut max_rect_lights_warning_emitted,
1076        mut max_cascades_per_light_warning_emitted,
1077        mut live_shadow_mapping_lights,
1078    ): (
1079        Local<bool>,
1080        Local<bool>,
1081        Local<bool>,
1082        Local<HashSet<RetainedViewEntity>>,
1083    ),
1084    (
1085        mut point_lights,
1086        changed_point_lights,
1087        directional_lights,
1088        rect_lights,
1089        mut directional_light_view_entities,
1090    ): (
1091        Query<(
1092            Entity,
1093            &MainEntity,
1094            &ExtractedPointLight,
1095            &mut PointAndSpotLightViewEntities,
1096            &RenderLayers,
1097            AnyOf<(&CubemapFrusta, &Frustum)>,
1098        )>,
1099        Query<
1100            (),
1101            Or<(
1102                Changed<ExtractedPointLight>,
1103                Changed<CubemapFrusta>,
1104                Changed<Frustum>,
1105                Changed<RenderLayers>,
1106            )>,
1107        >,
1108        Query<(
1109            Entity,
1110            &MainEntity,
1111            &ExtractedDirectionalLight,
1112            &RenderLayers,
1113        )>,
1114        Query<(Entity, &MainEntity, &ExtractedRectLight, &RenderLayers)>,
1115        Query<&mut DirectionalLightViewEntities>,
1116    ),
1117    sorted_cameras: Res<SortedCameras>,
1118    (gpu_preprocessing_support, decals): (
1119        Res<GpuPreprocessingSupport>,
1120        Option<Res<RenderClusteredDecals>>,
1121    ),
1122    (existing_shadow_views, mut light_key_cache, mut specialized_shadow_material_pipeline_cache): (
1123        Query<&ShadowView>,
1124        ResMut<LightKeyCache>,
1125        ResMut<SpecializedShadowMaterialPipelineCache>,
1126    ),
1127) {
1128    let views_iter = views.iter();
1129    let views_count = views_iter.len();
1130    let Some(mut view_gpu_lights_writer) =
1131        light_meta
1132            .view_gpu_lights
1133            .get_writer(views_count, &render_device, &render_queue)
1134    else {
1135        return;
1136    };
1137
1138    // Pre-calculate for PointLights
1139    let cube_face_rotations = CUBE_MAP_FACES
1140        .iter()
1141        .map(|CubeMapFace { target, up }| Transform::IDENTITY.looking_at(*target, *up))
1142        .collect::<Vec<_>>();
1143
1144    global_clusterable_object_meta.entity_to_index.clear();
1145
1146    let mut point_light_entities: Vec<_> = point_lights
1147        .iter()
1148        .map(|(entity, _, _, _, _, _)| entity)
1149        .collect::<Vec<_>>();
1150    let mut directional_light_entities: Vec<_> = directional_lights
1151        .iter()
1152        .map(|(entity, _, _, _)| entity)
1153        .collect::<Vec<_>>();
1154    let rect_light_entities: Vec<_> = rect_lights
1155        .iter()
1156        .map(|(entity, _, _, _)| entity)
1157        .collect::<Vec<_>>();
1158
1159    #[cfg(any(
1160        not(feature = "webgl"),
1161        not(target_arch = "wasm32"),
1162        feature = "webgpu"
1163    ))]
1164    let max_texture_array_layers = render_device.limits().max_texture_array_layers as usize;
1165    #[cfg(any(
1166        not(feature = "webgl"),
1167        not(target_arch = "wasm32"),
1168        feature = "webgpu"
1169    ))]
1170    let max_texture_cubes = max_texture_array_layers / 6;
1171    #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
1172    let max_texture_array_layers = 1;
1173    #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
1174    let max_texture_cubes = 1;
1175
1176    // When storage buffers are available, rect lights are clustered.
1177    // Otherwise they fall back to a non-clustered uniform capped at `MAX_RECT_LIGHTS`.
1178    let rect_lights_are_clustered = global_clusterable_object_meta
1179        .gpu_clustered_lights
1180        .is_storage_buffer();
1181
1182    if !*max_directional_lights_warning_emitted
1183        && directional_light_entities.len() > MAX_DIRECTIONAL_LIGHTS
1184    {
1185        warn!(
1186            "The amount of directional lights of {} is exceeding the supported limit of {}.",
1187            directional_light_entities.len(),
1188            MAX_DIRECTIONAL_LIGHTS
1189        );
1190        *max_directional_lights_warning_emitted = true;
1191    }
1192
1193    // The `MAX_RECT_LIGHTS` cap only applies on the non-clustered path.
1194    if !rect_lights_are_clustered
1195        && !*max_rect_lights_warning_emitted
1196        && rect_light_entities.len() > MAX_RECT_LIGHTS
1197    {
1198        warn!(
1199            "The amount of rectangle area lights of {} is exceeding the supported limit of {}.",
1200            rect_light_entities.len(),
1201            MAX_RECT_LIGHTS
1202        );
1203        *max_rect_lights_warning_emitted = true;
1204    }
1205
1206    if !*max_cascades_per_light_warning_emitted
1207        && directional_lights.iter().any(|(_, _, light, _)| {
1208            light.cascade_shadow_config.bounds.len() > MAX_CASCADES_PER_LIGHT
1209        })
1210    {
1211        warn!(
1212            "The number of cascades configured for a directional light exceeds the supported limit of {}.",
1213            MAX_CASCADES_PER_LIGHT
1214        );
1215        *max_cascades_per_light_warning_emitted = true;
1216    }
1217
1218    let point_light_count = point_lights
1219        .iter()
1220        .filter(|light| light.2.spot_light_angles.is_none())
1221        .count();
1222
1223    let point_light_volumetric_enabled_count = point_lights
1224        .iter()
1225        .filter(|(_, _, light, _, _, _)| light.volumetric && light.spot_light_angles.is_none())
1226        .count()
1227        .min(max_texture_cubes);
1228
1229    let point_light_shadow_maps_count = point_lights
1230        .iter()
1231        .filter(|light| light.2.shadow_maps_enabled && light.2.spot_light_angles.is_none())
1232        .count()
1233        .min(max_texture_cubes);
1234
1235    let directional_volumetric_enabled_count = directional_lights
1236        .iter()
1237        .take(MAX_DIRECTIONAL_LIGHTS)
1238        .filter(|(_, _, light, _)| light.volumetric)
1239        .count()
1240        .min(max_texture_array_layers / MAX_CASCADES_PER_LIGHT);
1241
1242    let directional_shadow_enabled_count = directional_lights
1243        .iter()
1244        .take(MAX_DIRECTIONAL_LIGHTS)
1245        .filter(|(_, _, light, _)| light.shadow_maps_enabled)
1246        .count()
1247        .min(max_texture_array_layers / MAX_CASCADES_PER_LIGHT);
1248
1249    let spot_light_count = point_lights
1250        .iter()
1251        .filter(|(_, _, light, _, _, _)| light.spot_light_angles.is_some())
1252        .count()
1253        .min(max_texture_array_layers - directional_shadow_enabled_count * MAX_CASCADES_PER_LIGHT);
1254
1255    let spot_light_volumetric_enabled_count = point_lights
1256        .iter()
1257        .filter(|(_, _, light, _, _, _)| light.volumetric && light.spot_light_angles.is_some())
1258        .count()
1259        .min(max_texture_array_layers - directional_shadow_enabled_count * MAX_CASCADES_PER_LIGHT);
1260
1261    let spot_light_shadow_maps_count = point_lights
1262        .iter()
1263        .filter(|(_, _, light, _, _, _)| {
1264            light.shadow_maps_enabled && light.spot_light_angles.is_some()
1265        })
1266        .count()
1267        .min(max_texture_array_layers - directional_shadow_enabled_count * MAX_CASCADES_PER_LIGHT);
1268
1269    // Sort lights by
1270    // - point-light vs spot-light, so that we can iterate point lights and spot lights in contiguous blocks in the fragment shader,
1271    // - then those with shadows enabled first, so that the index can be used to render at most `point_light_shadow_maps_count`
1272    //   point light shadows and `spot_light_shadow_maps_count` spot light shadow maps,
1273    // - then by entity as a stable key to ensure that a consistent set of lights are chosen if the light count limit is exceeded.
1274    point_light_entities.sort_by_cached_key(|entity| {
1275        (
1276            point_or_spot_light_to_clusterable(point_lights.get(*entity).unwrap().2).ordering(),
1277            *entity,
1278        )
1279    });
1280
1281    // Sort lights by
1282    // - those with volumetric (and shadows) enabled first, so that the
1283    //   volumetric lighting pass can quickly find the volumetric lights;
1284    // - then those with shadows enabled second, so that the index can be used
1285    //   to render at most `directional_light_shadow_maps_count` directional light
1286    //   shadows
1287    // - then by entity as a stable key to ensure that a consistent set of
1288    //   lights are chosen if the light count limit is exceeded.
1289    directional_light_entities.sort_by_cached_key(|entity| {
1290        let light = directional_lights.get(*entity).unwrap().2;
1291        (light.volumetric, light.shadow_maps_enabled, *entity)
1292    });
1293
1294    if global_clusterable_object_meta.entity_to_index.capacity() < point_light_entities.len() {
1295        global_clusterable_object_meta
1296            .entity_to_index
1297            .reserve(point_light_entities.len());
1298    }
1299
1300    global_clusterable_object_meta.gpu_clustered_lights.clear();
1301
1302    for (index, entity) in point_light_entities.iter().enumerate() {
1303        let light = point_lights.get(*entity).unwrap().2;
1304
1305        let mut flags = PointLightFlags::NONE;
1306
1307        // Lights are sorted, shadow enabled lights are first
1308        if light.shadow_maps_enabled
1309            && (index < point_light_shadow_maps_count
1310                || (light.spot_light_angles.is_some()
1311                    && index - point_light_count < spot_light_shadow_maps_count))
1312        {
1313            flags |= PointLightFlags::SHADOW_MAPS_ENABLED;
1314        }
1315
1316        if light.contact_shadows_enabled {
1317            flags |= PointLightFlags::CONTACT_SHADOWS_ENABLED;
1318        }
1319
1320        let cube_face_projection = proj::perspective_infinite_reverse(
1321            core::f32::consts::FRAC_PI_2,
1322            1.0,
1323            light.shadow_map_near_z,
1324        );
1325        if light.shadow_maps_enabled
1326            && light.volumetric
1327            && (index < point_light_volumetric_enabled_count
1328                || (light.spot_light_angles.is_some()
1329                    && index - point_light_count < spot_light_volumetric_enabled_count))
1330        {
1331            flags |= PointLightFlags::VOLUMETRIC;
1332        }
1333
1334        if light.affects_lightmapped_mesh_diffuse {
1335            flags |= PointLightFlags::AFFECTS_LIGHTMAPPED_MESH_DIFFUSE;
1336        }
1337
1338        let (light_custom_data, spot_light_tan_angle) = match light.spot_light_angles {
1339            Some((inner, outer)) => {
1340                flags |= PointLightFlags::SPOT_LIGHT;
1341
1342                let light_direction = light.transform.forward();
1343                if light_direction.y.is_sign_negative() {
1344                    flags |= PointLightFlags::SPOT_LIGHT_Y_NEGATIVE;
1345                }
1346
1347                let cos_outer = ops::cos(outer);
1348                let spot_scale = 1.0 / f32::max(ops::cos(inner) - cos_outer, 1e-4);
1349                let spot_offset = -cos_outer * spot_scale;
1350
1351                (
1352                    // For spot lights: the direction (x,z), spot_scale and spot_offset
1353                    light_direction.xz().extend(spot_scale).extend(spot_offset),
1354                    ops::tan(outer),
1355                )
1356            }
1357            None => {
1358                (
1359                    // For point lights: the lower-right 2x2 values of the projection matrix [2][2] [2][3] [3][2] [3][3]
1360                    Vec4::new(
1361                        cube_face_projection.z_axis.z,
1362                        cube_face_projection.z_axis.w,
1363                        cube_face_projection.w_axis.z,
1364                        cube_face_projection.w_axis.w,
1365                    ),
1366                    // unused
1367                    0.0,
1368                )
1369            }
1370        };
1371
1372        global_clusterable_object_meta
1373            .gpu_clustered_lights
1374            .add(GpuClusteredLight {
1375                light_custom_data,
1376                // premultiply color by intensity
1377                // we don't use the alpha at all, so no reason to multiply only [0..3]
1378                color_inverse_square_range: (light.color.to_vec4() * light.intensity)
1379                    .xyz()
1380                    .extend(1.0 / (light.range * light.range)),
1381                position_radius: light.transform.translation().extend(light.radius),
1382                flags: flags.bits(),
1383                shadow_depth_bias: light.shadow_depth_bias,
1384                shadow_normal_bias: light.shadow_normal_bias,
1385                shadow_map_near_z: light.shadow_map_near_z,
1386                spot_light_tan_angle,
1387                decal_index: decals
1388                    .as_ref()
1389                    .and_then(|decals| decals.get(*entity))
1390                    .and_then(|index| index.try_into().ok())
1391                    .unwrap_or(u32::MAX),
1392                range: light.range,
1393                soft_shadow_size: if light.soft_shadows_enabled {
1394                    light.radius
1395                } else {
1396                    0.0
1397                },
1398            });
1399        global_clusterable_object_meta
1400            .entity_to_index
1401            .insert(*entity, index);
1402        debug_assert_eq!(
1403            global_clusterable_object_meta.entity_to_index.len(),
1404            global_clusterable_object_meta.gpu_clustered_lights.len()
1405        );
1406    }
1407
1408    if rect_lights_are_clustered {
1409        for entity in &rect_light_entities {
1410            let light = rect_lights.get(*entity).unwrap().2;
1411
1412            let index = global_clusterable_object_meta.gpu_clustered_lights.len();
1413            global_clusterable_object_meta
1414                .gpu_clustered_lights
1415                .add(GpuClusteredLight {
1416                    light_custom_data: Vec4::from(light.transform.rotation()),
1417                    color_inverse_square_range: (light.color.to_vec4() * light.intensity)
1418                        .xyz()
1419                        .extend(light.height),
1420                    position_radius: light.transform.translation().extend(light.width),
1421                    flags: PointLightFlags::RECT_LIGHT.bits(),
1422                    shadow_depth_bias: 0.0,
1423                    shadow_normal_bias: 0.0,
1424                    shadow_map_near_z: 0.0,
1425                    spot_light_tan_angle: 0.0,
1426                    decal_index: u32::MAX,
1427                    range: light.range,
1428                    soft_shadow_size: 0.0,
1429                });
1430            global_clusterable_object_meta
1431                .entity_to_index
1432                .insert(*entity, index);
1433            debug_assert_eq!(
1434                global_clusterable_object_meta.entity_to_index.len(),
1435                global_clusterable_object_meta.gpu_clustered_lights.len()
1436            );
1437        }
1438    }
1439
1440    // iterate the views once to find the maximum number of cascade shadowmaps we will need
1441    let mut num_directional_cascades_enabled = 0usize;
1442    for (
1443        _entity,
1444        _camera_main_entity,
1445        _extracted_view,
1446        _clusters,
1447        maybe_layers,
1448        _no_indirect_drawing,
1449        _maybe_ambient_override,
1450    ) in sorted_cameras
1451        .0
1452        .iter()
1453        .filter_map(|sorted_camera| views.get(sorted_camera.entity).ok())
1454    {
1455        let mut num_directional_cascades_for_this_view = 0usize;
1456        let render_layers = maybe_layers.unwrap_or_default();
1457
1458        for light_entity in directional_light_entities.iter() {
1459            let (_, _, light, light_render_layers) = directional_lights.get(*light_entity).unwrap();
1460            if light.shadow_maps_enabled && light_render_layers.intersects(render_layers) {
1461                num_directional_cascades_for_this_view += light
1462                    .cascade_shadow_config
1463                    .bounds
1464                    .len()
1465                    .min(MAX_CASCADES_PER_LIGHT);
1466            }
1467        }
1468
1469        num_directional_cascades_enabled = num_directional_cascades_enabled
1470            .max(num_directional_cascades_for_this_view)
1471            .min(max_texture_array_layers);
1472    }
1473
1474    global_clusterable_object_meta
1475        .gpu_clustered_lights
1476        .write_buffer(&render_device, &render_queue);
1477
1478    live_shadow_mapping_lights.clear();
1479
1480    let mut point_light_depth_attachments = HashMap::<u32, DepthStencilViewAttachment>::default();
1481    let mut directional_light_depth_attachments =
1482        HashMap::<u32, DepthStencilViewAttachment>::default();
1483
1484    let point_light_depth_texture = texture_cache.get(
1485        &render_device,
1486        TextureDescriptor {
1487            size: Extent3d {
1488                width: point_light_shadow_map.size as u32,
1489                height: point_light_shadow_map.size as u32,
1490                depth_or_array_layers: point_light_shadow_maps_count.max(1) as u32 * 6,
1491            },
1492            mip_level_count: 1,
1493            sample_count: 1,
1494            dimension: TextureDimension::D2,
1495            format: CORE_3D_SHADOW_MAP_FORMAT,
1496            label: Some("point_light_shadow_map_texture"),
1497            usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING,
1498            view_formats: &[],
1499        },
1500    );
1501
1502    let point_light_depth_texture_view =
1503        point_light_depth_texture
1504            .texture
1505            .create_view(&TextureViewDescriptor {
1506                label: Some("point_light_shadow_map_array_texture_view"),
1507                format: None,
1508                // NOTE: iOS Simulator is missing CubeArray support so we use Cube instead.
1509                // See https://github.com/bevyengine/bevy/pull/12052 - remove if support is added.
1510                #[cfg(all(
1511                    not(target_abi = "sim"),
1512                    any(
1513                        not(feature = "webgl"),
1514                        not(target_arch = "wasm32"),
1515                        feature = "webgpu"
1516                    )
1517                ))]
1518                dimension: Some(TextureViewDimension::CubeArray),
1519                #[cfg(any(
1520                    target_abi = "sim",
1521                    all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu"))
1522                ))]
1523                dimension: Some(TextureViewDimension::Cube),
1524                usage: None,
1525                aspect: TextureAspect::DepthOnly,
1526                base_mip_level: 0,
1527                mip_level_count: None,
1528                base_array_layer: 0,
1529                array_layer_count: None,
1530            });
1531
1532    let directional_light_depth_texture = texture_cache.get(
1533        &render_device,
1534        TextureDescriptor {
1535            size: Extent3d {
1536                width: (directional_light_shadow_map.size as u32)
1537                    .min(render_device.limits().max_texture_dimension_2d),
1538                height: (directional_light_shadow_map.size as u32)
1539                    .min(render_device.limits().max_texture_dimension_2d),
1540                depth_or_array_layers: (num_directional_cascades_enabled
1541                    + spot_light_shadow_maps_count)
1542                    .max(1) as u32,
1543            },
1544            mip_level_count: 1,
1545            sample_count: 1,
1546            dimension: TextureDimension::D2,
1547            format: CORE_3D_SHADOW_MAP_FORMAT,
1548            label: Some("directional_light_shadow_map_texture"),
1549            usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING,
1550            view_formats: &[],
1551        },
1552    );
1553
1554    let directional_light_depth_texture_view =
1555        directional_light_depth_texture
1556            .texture
1557            .create_view(&TextureViewDescriptor {
1558                label: Some("directional_light_shadow_map_array_texture_view"),
1559                format: None,
1560                #[cfg(any(
1561                    not(feature = "webgl"),
1562                    not(target_arch = "wasm32"),
1563                    feature = "webgpu"
1564                ))]
1565                dimension: Some(TextureViewDimension::D2Array),
1566                #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
1567                dimension: Some(TextureViewDimension::D2),
1568                usage: None,
1569                aspect: TextureAspect::DepthOnly,
1570                base_mip_level: 0,
1571                mip_level_count: None,
1572                base_array_layer: 0,
1573                array_layer_count: None,
1574            });
1575
1576    let mut live_views = EntityHashSet::with_capacity(views_count);
1577
1578    // TODO: this should select lights based on relevance to the view instead of the first ones that show up in a query
1579    for light_entity in point_light_entities
1580        .iter()
1581        // Lights are sorted, shadow enabled lights are first
1582        .take(point_light_count.min(max_texture_cubes))
1583    {
1584        let (
1585            _,
1586            light_main_entity,
1587            light,
1588            mut point_and_spot_light_view_entities,
1589            light_render_layers,
1590            (point_light_frusta, _),
1591        ) = point_lights.get_mut(*light_entity).unwrap();
1592
1593        if !light.shadow_maps_enabled {
1594            despawn_entities(
1595                &mut commands,
1596                mem::take(&mut point_and_spot_light_view_entities.0),
1597            );
1598            continue;
1599        }
1600
1601        if point_and_spot_light_view_entities.0.is_empty() {
1602            // For each face of a cube we spawn a light entity
1603            let light_view_entities: Vec<_> = (0..6).map(|_| commands.spawn_empty().id()).collect();
1604
1605            create_point_shadow_maps(
1606                &mut commands,
1607                &mut point_light_depth_attachments,
1608                &global_clusterable_object_meta,
1609                (
1610                    &cube_face_rotations,
1611                    point_light_frusta,
1612                    &light_view_entities,
1613                ),
1614                &point_light_depth_texture,
1615                (light_entity, light_main_entity, light, light_render_layers),
1616                point_light_shadow_map.size as u32,
1617                gpu_preprocessing_support.max_supported_mode,
1618            );
1619
1620            point_and_spot_light_view_entities.0 = light_view_entities;
1621        } else if changed_point_lights.get(*light_entity).is_ok() {
1622            // Update the point shadow maps with the changes.
1623            create_point_shadow_maps(
1624                &mut commands,
1625                &mut point_light_depth_attachments,
1626                &global_clusterable_object_meta,
1627                (
1628                    &cube_face_rotations,
1629                    point_light_frusta,
1630                    &point_and_spot_light_view_entities.0,
1631                ),
1632                &point_light_depth_texture,
1633                (light_entity, light_main_entity, light, light_render_layers),
1634                point_light_shadow_map.size as u32,
1635                gpu_preprocessing_support.max_supported_mode,
1636            );
1637        }
1638
1639        // Initialize the shadow render phases. We have to do this even if we've
1640        // already created the views in order to clear out old data.
1641        for face_index in 0..6 {
1642            let retained_view_entity =
1643                RetainedViewEntity::new(*light_main_entity, None, face_index);
1644            shadow_render_phases.prepare_for_new_frame(
1645                retained_view_entity,
1646                gpu_preprocessing_support.max_supported_mode,
1647            );
1648            live_shadow_mapping_lights.insert(retained_view_entity);
1649        }
1650    }
1651
1652    // spot lights
1653    for (light_index, light_entity) in point_light_entities
1654        .iter()
1655        .skip(point_light_count)
1656        .take(spot_light_count)
1657        .enumerate()
1658    {
1659        let (
1660            _,
1661            light_main_entity,
1662            light,
1663            mut point_and_spot_light_view_entities,
1664            light_render_layers,
1665            (_, spot_light_frustum),
1666        ) = point_lights.get_mut(*light_entity).unwrap();
1667
1668        if !light.shadow_maps_enabled {
1669            despawn_entities(
1670                &mut commands,
1671                mem::take(&mut point_and_spot_light_view_entities.0),
1672            );
1673            continue;
1674        }
1675
1676        // Spot light shadow maps are shared across all cameras,
1677        // so the retained view entity must not include the camera.
1678        let retained_view_entity = RetainedViewEntity::new(*light_main_entity, None, 0);
1679
1680        if point_and_spot_light_view_entities.0.is_empty() {
1681            let view_light_entity = commands.spawn_empty().id();
1682
1683            create_spot_shadow_map(
1684                &mut commands,
1685                &mut directional_light_depth_attachments,
1686                (num_directional_cascades_enabled, light_index),
1687                &directional_light_depth_texture,
1688                view_light_entity,
1689                (light_entity, light_main_entity, light, light_render_layers),
1690                spot_light_frustum,
1691                directional_light_shadow_map.size as u32,
1692                gpu_preprocessing_support.max_supported_mode,
1693            );
1694
1695            point_and_spot_light_view_entities.0 = vec![view_light_entity];
1696        } else if changed_point_lights.get(*light_entity).is_ok() {
1697            create_spot_shadow_map(
1698                &mut commands,
1699                &mut directional_light_depth_attachments,
1700                (num_directional_cascades_enabled, light_index),
1701                &directional_light_depth_texture,
1702                // There should only be one view light entity for spotlights
1703                point_and_spot_light_view_entities.0[0],
1704                (light_entity, light_main_entity, light, light_render_layers),
1705                spot_light_frustum,
1706                directional_light_shadow_map.size as u32,
1707                gpu_preprocessing_support.max_supported_mode,
1708            );
1709        }
1710
1711        shadow_render_phases.prepare_for_new_frame(
1712            retained_view_entity,
1713            gpu_preprocessing_support.max_supported_mode,
1714        );
1715        live_shadow_mapping_lights.insert(retained_view_entity);
1716    }
1717
1718    // set up light data for each view
1719    for (
1720        entity,
1721        camera_main_entity,
1722        extracted_view,
1723        clusters,
1724        maybe_layers,
1725        no_indirect_drawing,
1726        maybe_ambient_override,
1727    ) in sorted_cameras
1728        .0
1729        .iter()
1730        .filter_map(|sorted_camera| views.get(sorted_camera.entity).ok())
1731    {
1732        live_views.insert(entity);
1733
1734        let view_layers = maybe_layers.unwrap_or_default();
1735        let mut view_lights = Vec::new();
1736        let mut view_occlusion_culling_lights = Vec::new();
1737
1738        let gpu_preprocessing_mode = gpu_preprocessing_support.min(if !no_indirect_drawing {
1739            GpuPreprocessingMode::Culling
1740        } else {
1741            GpuPreprocessingMode::PreprocessingOnly
1742        });
1743
1744        let is_orthographic = extracted_view.clip_from_view.w_axis.w == 1.0;
1745        let cluster_factors_zw = calculate_cluster_factors(
1746            clusters.near,
1747            clusters.far,
1748            clusters.dimensions.z as f32,
1749            is_orthographic,
1750        );
1751
1752        let n_clusters = clusters.dimensions.x * clusters.dimensions.y * clusters.dimensions.z;
1753        let ambient_light = AmbientLight {
1754            color: ambient_light.color,
1755            brightness: ambient_light.brightness,
1756            affects_lightmapped_meshes: ambient_light.affects_lightmapped_meshes,
1757        };
1758        let ambient_light = maybe_ambient_override.unwrap_or(&ambient_light);
1759
1760        let mut gpu_directional_lights = [GpuDirectionalLight::default(); MAX_DIRECTIONAL_LIGHTS];
1761        let mut num_directional_cascades_enabled_for_this_view = 0usize;
1762        let mut num_directional_lights_for_this_view = 0usize;
1763        for (index, light_entity) in directional_light_entities
1764            .iter()
1765            .filter(|light_entity| {
1766                directional_lights
1767                    .get(**light_entity)
1768                    .unwrap()
1769                    .3
1770                    .intersects(view_layers)
1771            })
1772            .enumerate()
1773            .take(MAX_DIRECTIONAL_LIGHTS)
1774        {
1775            let light = directional_lights.get(*light_entity).unwrap().2;
1776
1777            num_directional_lights_for_this_view += 1;
1778
1779            let mut flags = DirectionalLightFlags::NONE;
1780
1781            // Lights are sorted, volumetric and shadow enabled lights are first
1782            if light.volumetric
1783                && light.shadow_maps_enabled
1784                && (index < directional_volumetric_enabled_count)
1785            {
1786                flags |= DirectionalLightFlags::VOLUMETRIC;
1787            }
1788
1789            // Shadow enabled lights are second
1790            let mut num_cascades = 0;
1791            if light.shadow_maps_enabled {
1792                let cascades = light
1793                    .cascade_shadow_config
1794                    .bounds
1795                    .len()
1796                    .min(MAX_CASCADES_PER_LIGHT);
1797
1798                if num_directional_cascades_enabled_for_this_view + cascades
1799                    <= max_texture_array_layers
1800                {
1801                    flags |= DirectionalLightFlags::SHADOW_MAPS_ENABLED;
1802                    num_cascades += cascades;
1803                }
1804            }
1805
1806            if light.contact_shadows_enabled {
1807                flags |= DirectionalLightFlags::CONTACT_SHADOWS_ENABLED;
1808            }
1809
1810            if light.affects_lightmapped_mesh_diffuse {
1811                flags |= DirectionalLightFlags::AFFECTS_LIGHTMAPPED_MESH_DIFFUSE;
1812            }
1813
1814            gpu_directional_lights[index] = GpuDirectionalLight {
1815                // Filled in later.
1816                cascades: [GpuDirectionalCascade::default(); MAX_CASCADES_PER_LIGHT],
1817                // premultiply color by illuminance
1818                // we don't use the alpha at all, so no reason to multiply only [0..3]
1819                color: Vec4::from_slice(&light.color.to_f32_array()) * light.illuminance,
1820                // direction is negated to be ready for N.L
1821                dir_to_light: light.transform.back().into(),
1822                flags: flags.bits(),
1823                soft_shadow_size: light.soft_shadow_size.unwrap_or_default(),
1824                shadow_depth_bias: light.shadow_depth_bias,
1825                shadow_normal_bias: light.shadow_normal_bias,
1826                num_cascades: num_cascades as u32,
1827                cascades_overlap_proportion: light.cascade_shadow_config.overlap_proportion,
1828                depth_texture_base_index: num_directional_cascades_enabled_for_this_view as u32,
1829                sun_disk_angular_size: light.sun_disk_angular_size,
1830                sun_disk_intensity: light.sun_disk_intensity,
1831                decal_index: decals
1832                    .as_ref()
1833                    .and_then(|decals| decals.get(*light_entity))
1834                    .and_then(|index| index.try_into().ok())
1835                    .unwrap_or(u32::MAX),
1836            };
1837            num_directional_cascades_enabled_for_this_view += num_cascades;
1838        }
1839
1840        let mut ambient_light_flags = AmbientLightFlags::NONE;
1841
1842        if ambient_light.affects_lightmapped_meshes {
1843            ambient_light_flags |= AmbientLightFlags::AFFECTS_LIGHTMAPPED_MESHES;
1844        }
1845
1846        let mut gpu_lights = GpuLights {
1847            directional_lights: gpu_directional_lights,
1848            ambient_color: Vec4::from_slice(&LinearRgba::from(ambient_light.color).to_f32_array())
1849                * ambient_light.brightness,
1850            cluster_factors: Vec4::new(
1851                clusters.dimensions.x as f32 / extracted_view.viewport.z as f32,
1852                clusters.dimensions.y as f32 / extracted_view.viewport.w as f32,
1853                cluster_factors_zw.x,
1854                cluster_factors_zw.y,
1855            ),
1856            cluster_dimensions: clusters.dimensions.extend(n_clusters),
1857            n_directional_lights: num_directional_lights_for_this_view as u32,
1858            // spotlight shadow maps are stored in the directional light array, starting at num_directional_cascades_enabled.
1859            // the spot lights themselves start in the light array at point_light_count. so to go from light
1860            // index to shadow map index, we need to subtract point light count and add directional shadowmap count.
1861            spot_light_shadowmap_offset: num_directional_cascades_enabled as i32
1862                - point_light_count as i32,
1863            ambient_light_flags: ambient_light_flags.bits(),
1864            n_rect_lights: 0,
1865            rect_lights: [GpuRectLight::default(); MAX_RECT_LIGHTS],
1866        };
1867
1868        // directional lights
1869        // clear entities for lights that don't intersect the layer
1870        for light_entity in directional_light_entities.iter().filter(|light_entity| {
1871            !directional_lights
1872                .get(**light_entity)
1873                .unwrap()
1874                .3
1875                .intersects(view_layers)
1876        }) {
1877            let Ok(mut light_view_entities) =
1878                directional_light_view_entities.get_mut(*light_entity)
1879            else {
1880                continue;
1881            };
1882            if let Some(entities) = light_view_entities.remove(&entity) {
1883                despawn_entities(&mut commands, entities);
1884            }
1885        }
1886
1887        let mut directional_depth_texture_array_index = 0u32;
1888        for (light_index, light_entity) in directional_light_entities
1889            .iter()
1890            .filter(|light_entity| {
1891                directional_lights
1892                    .get(**light_entity)
1893                    .unwrap()
1894                    .3
1895                    .intersects(view_layers)
1896            })
1897            .enumerate()
1898            .take(MAX_DIRECTIONAL_LIGHTS)
1899        {
1900            let (_, light_main_entity, light, light_render_layers) =
1901                directional_lights.get(*light_entity).unwrap();
1902
1903            let Ok(mut light_view_entities) =
1904                directional_light_view_entities.get_mut(*light_entity)
1905            else {
1906                continue;
1907            };
1908
1909            let gpu_light = &mut gpu_lights.directional_lights[light_index];
1910
1911            // Only deal with cascades when shadows are enabled.
1912            if (gpu_light.flags & DirectionalLightFlags::SHADOW_MAPS_ENABLED.bits()) == 0u32 {
1913                if let Some(entities) = light_view_entities.remove(&entity) {
1914                    despawn_entities(&mut commands, entities);
1915                }
1916                continue;
1917            }
1918
1919            let cascades = light
1920                .cascades
1921                .get(&entity)
1922                .unwrap()
1923                .iter()
1924                .take(MAX_CASCADES_PER_LIGHT);
1925            let frusta = light
1926                .frusta
1927                .get(&entity)
1928                .unwrap()
1929                .iter()
1930                .take(MAX_CASCADES_PER_LIGHT);
1931
1932            let iter = cascades
1933                .zip(frusta)
1934                .zip(&light.cascade_shadow_config.bounds);
1935
1936            let light_view_entities = light_view_entities.entry(entity).or_insert_with(|| {
1937                (0..iter.len())
1938                    .map(|_| commands.spawn_empty().id())
1939                    .collect()
1940            });
1941            if light_view_entities.len() != iter.len() {
1942                let entities = mem::take(light_view_entities);
1943                despawn_entities(&mut commands, entities);
1944                light_view_entities.extend((0..iter.len()).map(|_| commands.spawn_empty().id()));
1945            }
1946
1947            for (cascade_index, (((cascade, frustum), bound), view_light_entity)) in
1948                iter.zip(light_view_entities.iter().copied()).enumerate()
1949            {
1950                gpu_lights.directional_lights[light_index].cascades[cascade_index] =
1951                    GpuDirectionalCascade {
1952                        clip_from_world: cascade.clip_from_world,
1953                        texel_size: cascade.texel_size,
1954                        far_bound: *bound,
1955                    };
1956
1957                let depth_texture_view =
1958                    directional_light_depth_texture
1959                        .texture
1960                        .create_view(&TextureViewDescriptor {
1961                            label: Some("directional_light_shadow_map_array_texture_view"),
1962                            format: None,
1963                            dimension: Some(TextureViewDimension::D2),
1964                            usage: None,
1965                            aspect: TextureAspect::All,
1966                            base_mip_level: 0,
1967                            mip_level_count: None,
1968                            base_array_layer: directional_depth_texture_array_index,
1969                            array_layer_count: Some(1u32),
1970                        });
1971
1972                // NOTE: For point and spotlights, we reuse the same depth attachment for all views.
1973                // However, for directional lights, we want a new depth attachment for each view,
1974                // so that the view is cleared for each view.
1975                let depth_attachment = DepthStencilViewAttachment::new(
1976                    DepthStencilViews::DepthOnly {
1977                        depth_view: depth_texture_view.clone(),
1978                    },
1979                    Some(0.0),
1980                    None,
1981                );
1982
1983                directional_depth_texture_array_index += 1;
1984
1985                let mut frustum = *frustum;
1986                // Push the near clip plane out to infinity for directional lights
1987                frustum.half_spaces[ViewFrustum::NEAR_PLANE_IDX] = HalfSpace::new(
1988                    frustum.half_spaces[ViewFrustum::NEAR_PLANE_IDX]
1989                        .normal()
1990                        .extend(f32::INFINITY),
1991                );
1992
1993                let retained_view_entity = RetainedViewEntity::new(
1994                    *light_main_entity,
1995                    Some(camera_main_entity.into()),
1996                    cascade_index as u32,
1997                );
1998
1999                commands.entity(view_light_entity).insert((
2000                    ShadowView {
2001                        depth_attachment,
2002                        pass_name: format!(
2003                            "shadow_directional_light_{light_index}_cascade_{cascade_index}"
2004                        ),
2005                    },
2006                    ExtractedView {
2007                        retained_view_entity,
2008                        viewport: UVec4::new(
2009                            0,
2010                            0,
2011                            directional_light_shadow_map.size as u32,
2012                            directional_light_shadow_map.size as u32,
2013                        ),
2014                        world_from_view: GlobalTransform::from(cascade.world_from_cascade),
2015                        clip_from_view: cascade.clip_from_cascade,
2016                        clip_from_world: Some(cascade.clip_from_world),
2017                        target_format: CORE_3D_SHADOW_MAP_FORMAT,
2018                        color_grading: Default::default(),
2019                        invert_culling: false,
2020                    },
2021                    frustum,
2022                    LightEntity::Directional {
2023                        light_entity: *light_entity,
2024                        cascade_index,
2025                    },
2026                    (*light_render_layers).clone(),
2027                ));
2028
2029                if !matches!(gpu_preprocessing_mode, GpuPreprocessingMode::Culling) {
2030                    commands.entity(view_light_entity).insert(NoIndirectDrawing);
2031                }
2032
2033                view_lights.push(view_light_entity);
2034
2035                // If this light is using occlusion culling, add the appropriate components.
2036                if light.occlusion_culling {
2037                    commands.entity(view_light_entity).insert((
2038                        OcclusionCulling,
2039                        OcclusionCullingSubview {
2040                            depth_texture_view,
2041                            depth_texture_size: directional_light_shadow_map.size as u32,
2042                        },
2043                    ));
2044                    view_occlusion_culling_lights.push(view_light_entity);
2045                }
2046
2047                // Subsequent views with the same light entity will **NOT** reuse the same shadow map
2048                // (Because the cascades are unique to each view)
2049                // TODO: Implement GPU culling for shadow passes.
2050                shadow_render_phases
2051                    .prepare_for_new_frame(retained_view_entity, gpu_preprocessing_mode);
2052                live_shadow_mapping_lights.insert(retained_view_entity);
2053            }
2054        }
2055
2056        // Make a link from the camera to all shadow cascades with occlusion
2057        // culling enabled.
2058        if !view_occlusion_culling_lights.is_empty() {
2059            commands
2060                .entity(entity)
2061                .insert(OcclusionCullingSubviewEntities(
2062                    view_occlusion_culling_lights,
2063                ));
2064        }
2065
2066        // Set up rect lights.
2067        //
2068        // These are per-view because we have no mechanism for "non-clustered but non-view-specific" lights.
2069        // We could introduce such a thing, but it may not be worth it for this fallback path that's only used
2070        // when we have too few storage buffers to cluster area lights.
2071        gpu_lights.n_rect_lights = 0;
2072        if !rect_lights_are_clustered {
2073            // TODO use light_render_layers whenever that is supported for rect_lights
2074            for (index, (_, _, rect_light, _)) in
2075                rect_lights.iter().enumerate().take(MAX_RECT_LIGHTS)
2076            {
2077                let right = rect_light.transform.right().into();
2078                let up = rect_light.transform.up().into();
2079                gpu_lights.rect_lights[index] = GpuRectLight {
2080                    color: rect_light.color.to_vec4() * rect_light.intensity,
2081                    position: rect_light.transform.translation(),
2082                    right,
2083                    up,
2084                    width: rect_light.width,
2085                    height: rect_light.height,
2086                    range: rect_light.range,
2087                };
2088                gpu_lights.n_rect_lights += 1;
2089            }
2090        }
2091
2092        commands.entity(entity).insert((
2093            ViewShadowBindings {
2094                point_light_depth_texture: point_light_depth_texture.texture.clone(),
2095                point_light_depth_texture_view: point_light_depth_texture_view.clone(),
2096                directional_light_depth_texture: directional_light_depth_texture.texture.clone(),
2097                directional_light_depth_texture_view: directional_light_depth_texture_view.clone(),
2098            },
2099            ViewLightEntities {
2100                lights: view_lights,
2101            },
2102            ViewLightsUniformOffset {
2103                offset: view_gpu_lights_writer.write(&gpu_lights),
2104            },
2105        ));
2106    }
2107
2108    // Mark the existing shadow maps as unused this frame so that the first
2109    // drawcall to them will clear them.
2110    for existing_shadow_view in &existing_shadow_views {
2111        existing_shadow_view
2112            .depth_attachment
2113            .prepare_for_new_frame();
2114    }
2115
2116    // Despawn light-view entities for views that no longer exist
2117    for mut entities in &mut directional_light_view_entities {
2118        for (_, light_view_entities) in
2119            entities.extract_if(|entity, _| !live_views.contains(entity))
2120        {
2121            despawn_entities(&mut commands, light_view_entities);
2122        }
2123    }
2124
2125    shadow_render_phases.retain(|entity, _| live_shadow_mapping_lights.contains(entity));
2126    light_key_cache.retain(|entity, _| live_shadow_mapping_lights.contains(entity));
2127    specialized_shadow_material_pipeline_cache
2128        .retain(|entity, _| live_shadow_mapping_lights.contains(entity));
2129}
2130
2131/// Creates six point shadow maps for a `RetainedViewEntity` identified by the `light_main_entity`
2132/// and the six cube face rotation indices. These shadow maps are shared across
2133/// all cameras.
2134fn create_point_shadow_maps(
2135    commands: &mut Commands,
2136    point_light_depth_attachments: &mut HashMap<u32, DepthStencilViewAttachment>,
2137    global_clusterable_object_meta: &ResMut<GlobalClusterableObjectMeta>,
2138    (cube_face_rotations, point_light_frusta, light_view_entities): (
2139        &Vec<Transform>,
2140        Option<&CubemapFrusta>,
2141        &Vec<Entity>,
2142    ),
2143    point_light_depth_texture: &CachedTexture,
2144    (light_entity, light_main_entity, light, light_render_layers): (
2145        &Entity,
2146        &MainEntity,
2147        &ExtractedPointLight,
2148        &RenderLayers,
2149    ),
2150    point_light_shadow_map_size: u32,
2151    gpu_preprocessing_support_max_supported_mode: GpuPreprocessingMode,
2152) {
2153    let light_index = *global_clusterable_object_meta
2154        .entity_to_index
2155        .get(light_entity)
2156        .unwrap();
2157    // ignore scale because we don't want to effectively scale light radius and range
2158    // by applying those as a view transform to shadow map rendering of objects
2159    // and ignore rotation because we want the shadow map projections to align with the axes
2160    let view_translation = GlobalTransform::from_translation(light.transform.translation());
2161
2162    let cube_face_projection = proj::perspective_infinite_reverse(
2163        core::f32::consts::FRAC_PI_2,
2164        1.0,
2165        light.shadow_map_near_z,
2166    );
2167
2168    for (face_index, ((view_rotation, frustum), view_light_entity)) in cube_face_rotations
2169        .iter()
2170        .zip(&point_light_frusta.unwrap().frusta)
2171        .zip(light_view_entities.iter().copied())
2172        .enumerate()
2173    {
2174        let base_array_layer = (light_index * 6 + face_index) as u32;
2175
2176        let depth_attachment = point_light_depth_attachments
2177            .entry(base_array_layer)
2178            .or_insert_with(|| {
2179                let depth_texture_view =
2180                    point_light_depth_texture
2181                        .texture
2182                        .create_view(&TextureViewDescriptor {
2183                            label: Some("point_light_shadow_map_texture_view"),
2184                            format: None,
2185                            dimension: Some(TextureViewDimension::D2),
2186                            usage: None,
2187                            aspect: TextureAspect::All,
2188                            base_mip_level: 0,
2189                            mip_level_count: None,
2190                            base_array_layer,
2191                            array_layer_count: Some(1u32),
2192                        });
2193
2194                DepthStencilViewAttachment::new(
2195                    DepthStencilViews::DepthOnly {
2196                        depth_view: depth_texture_view,
2197                    },
2198                    Some(0.0),
2199                    None,
2200                )
2201            })
2202            .clone();
2203
2204        // Point light shadow maps are shared across all cameras,
2205        // so the retained view entity must not include the camera.
2206        let retained_view_entity =
2207            RetainedViewEntity::new(*light_main_entity, None, face_index as u32);
2208
2209        commands.entity(view_light_entity).insert((
2210            ShadowView {
2211                depth_attachment,
2212                pass_name: format!(
2213                    "shadow_point_light_{}_{}",
2214                    light_index,
2215                    face_index_to_name(face_index)
2216                ),
2217            },
2218            ExtractedView {
2219                retained_view_entity,
2220                viewport: UVec4::new(
2221                    0,
2222                    0,
2223                    point_light_shadow_map_size,
2224                    point_light_shadow_map_size,
2225                ),
2226                world_from_view: view_translation * *view_rotation,
2227                clip_from_world: None,
2228                clip_from_view: cube_face_projection,
2229                target_format: CORE_3D_SHADOW_MAP_FORMAT,
2230                color_grading: Default::default(),
2231                invert_culling: false,
2232            },
2233            *frustum,
2234            LightEntity::Point {
2235                light_entity: *light_entity,
2236                face_index,
2237            },
2238            (*light_render_layers).clone(),
2239            RootNonCameraView(Core3d.intern()),
2240        ));
2241
2242        if !matches!(
2243            gpu_preprocessing_support_max_supported_mode,
2244            GpuPreprocessingMode::Culling
2245        ) {
2246            commands.entity(view_light_entity).insert(NoIndirectDrawing);
2247        }
2248    }
2249}
2250
2251/// Creates the spot shadow map for a `RetainedViewEntity` identified by the `light_main_entity`.
2252/// This shadow map is shared across all cameras.
2253fn create_spot_shadow_map(
2254    commands: &mut Commands,
2255    directional_light_depth_attachments: &mut HashMap<u32, DepthStencilViewAttachment>,
2256    (num_directional_cascades_enabled, light_index): (usize, usize),
2257    directional_light_depth_texture: &CachedTexture,
2258    view_light_entity: Entity,
2259    (light_entity, light_main_entity, light, light_render_layers): (
2260        &Entity,
2261        &MainEntity,
2262        &ExtractedPointLight,
2263        &RenderLayers,
2264    ),
2265    spot_light_frustum: Option<&Frustum>,
2266    directional_light_shadow_map_size: u32,
2267    gpu_preprocessing_support_max_supported_mode: GpuPreprocessingMode,
2268) {
2269    let spot_world_from_view = spot_light_world_from_view(&light.transform);
2270    let spot_world_from_view = spot_world_from_view.into();
2271
2272    let angle = light.spot_light_angles.expect("lights should be sorted so that \
2273                [point_light_count..point_light_count + spot_light_shadow_maps_count] are spot lights").1;
2274    let spot_projection = spot_light_clip_from_view(angle, light.shadow_map_near_z);
2275
2276    let base_array_layer = (num_directional_cascades_enabled + light_index) as u32;
2277
2278    let depth_attachment = directional_light_depth_attachments
2279        .entry(base_array_layer)
2280        .or_insert_with(|| {
2281            let depth_texture_view =
2282                directional_light_depth_texture
2283                    .texture
2284                    .create_view(&TextureViewDescriptor {
2285                        label: Some("spot_light_shadow_map_texture_view"),
2286                        format: None,
2287                        dimension: Some(TextureViewDimension::D2),
2288                        usage: None,
2289                        aspect: TextureAspect::All,
2290                        base_mip_level: 0,
2291                        mip_level_count: None,
2292                        base_array_layer,
2293                        array_layer_count: Some(1u32),
2294                    });
2295
2296            DepthStencilViewAttachment::new(
2297                DepthStencilViews::DepthOnly {
2298                    depth_view: depth_texture_view,
2299                },
2300                Some(0.0),
2301                None,
2302            )
2303        })
2304        .clone();
2305
2306    let retained_view_entity = RetainedViewEntity::new(*light_main_entity, None, 0);
2307    commands.entity(view_light_entity).insert((
2308        ShadowView {
2309            depth_attachment,
2310            pass_name: format!("shadow_spot_light_{light_index}"),
2311        },
2312        ExtractedView {
2313            retained_view_entity,
2314            viewport: UVec4::new(
2315                0,
2316                0,
2317                directional_light_shadow_map_size,
2318                directional_light_shadow_map_size,
2319            ),
2320            world_from_view: spot_world_from_view,
2321            clip_from_view: spot_projection,
2322            clip_from_world: None,
2323            target_format: CORE_3D_SHADOW_MAP_FORMAT,
2324            color_grading: Default::default(),
2325            invert_culling: false,
2326        },
2327        *spot_light_frustum.unwrap(),
2328        LightEntity::Spot {
2329            light_entity: *light_entity,
2330        },
2331        (*light_render_layers).clone(),
2332        RootNonCameraView(Core3d.intern()),
2333    ));
2334
2335    if !matches!(
2336        gpu_preprocessing_support_max_supported_mode,
2337        GpuPreprocessingMode::Culling
2338    ) {
2339        commands.entity(view_light_entity).insert(NoIndirectDrawing);
2340    }
2341}
2342
2343fn despawn_entities(commands: &mut Commands, entities: Vec<Entity>) {
2344    if entities.is_empty() {
2345        return;
2346    }
2347    commands.queue(move |world: &mut World| {
2348        for entity in entities {
2349            world.despawn(entity);
2350        }
2351    });
2352}
2353
2354#[derive(Resource, Deref, DerefMut, Default, Debug, Clone)]
2355pub struct LightKeyCache(HashMap<RetainedViewEntity, MeshPipelineKey>);
2356
2357#[derive(Resource, Deref, DerefMut, Default)]
2358pub struct SpecializedShadowMaterialPipelineCache {
2359    // view light entity -> view pipeline cache
2360    #[deref]
2361    map: HashMap<RetainedViewEntity, SpecializedShadowMaterialViewPipelineCache>,
2362}
2363
2364#[derive(Deref, DerefMut, Default)]
2365pub struct SpecializedShadowMaterialViewPipelineCache {
2366    #[deref]
2367    map: MainEntityHashMap<(CachedRenderPipelineId, DrawFunctionId, bool)>,
2368}
2369
2370pub fn check_views_lights_need_specialization(
2371    view_light_entities: Query<(&LightEntity, &ExtractedView)>,
2372    shadow_render_phases: Res<ViewBinnedRenderPhases<Shadow>>,
2373    mut light_key_cache: ResMut<LightKeyCache>,
2374    mut dirty_specializations: ResMut<DirtySpecializations>,
2375) {
2376    for (light_entity, extracted_view_light) in &view_light_entities {
2377        if !shadow_render_phases.contains_key(&extracted_view_light.retained_view_entity) {
2378            continue;
2379        }
2380
2381        let is_directional_light = matches!(light_entity, LightEntity::Directional { .. });
2382        let mut light_key = MeshPipelineKey::DEPTH_PREPASS;
2383        light_key.set(
2384            MeshPipelineKey::VIEW_PROJECTION_ORTHOGRAPHIC | MeshPipelineKey::UNCLIPPED_DEPTH_ORTHO,
2385            is_directional_light,
2386        );
2387        light_key.set(
2388            MeshPipelineKey::VIEW_PROJECTION_PERSPECTIVE,
2389            !is_directional_light,
2390        );
2391        if let Some(current_key) =
2392            light_key_cache.get_mut(&extracted_view_light.retained_view_entity)
2393        {
2394            if *current_key != light_key {
2395                light_key_cache.insert(extracted_view_light.retained_view_entity, light_key);
2396                dirty_specializations
2397                    .views
2398                    .insert(extracted_view_light.retained_view_entity);
2399            }
2400        } else {
2401            light_key_cache.insert(extracted_view_light.retained_view_entity, light_key);
2402            dirty_specializations
2403                .views
2404                .insert(extracted_view_light.retained_view_entity);
2405        }
2406    }
2407}
2408
2409pub(crate) struct ShadowSpecializationWorkItem {
2410    visible_entity: MainEntity,
2411    retained_view_entity: RetainedViewEntity,
2412    mesh_key: MeshPipelineKey,
2413    layout: MeshVertexBufferLayoutRef,
2414    properties: Arc<MaterialProperties>,
2415    material_type_id: TypeId,
2416}
2417
2418/// Holds all entities with mesh materials for which the shadow pass couldn't be
2419/// specialized and/or queued because their materials hadn't loaded yet.
2420///
2421/// See the [`PendingQueues`] documentation for more information.
2422#[derive(Default, Deref, DerefMut, Resource)]
2423pub struct PendingShadowQueues(pub PendingQueues);
2424
2425#[derive(SystemParam)]
2426pub(crate) struct SpecializeShadowsSystemParam<'w, 's> {
2427    render_meshes: Res<'w, RenderAssets<RenderMesh>>,
2428    render_mesh_instances: Res<'w, RenderMeshInstances>,
2429    render_materials: Res<'w, ErasedRenderAssets<PreparedMaterial>>,
2430    render_material_instances: Res<'w, RenderMaterialInstances>,
2431    shadow_render_phases: Res<'w, ViewBinnedRenderPhases<Shadow>>,
2432    render_lightmaps: Res<'w, RenderLightmaps>,
2433    view_light_entities: Query<'w, 's, (&'static LightEntity, &'static ExtractedView)>,
2434    shadow_map_visible_entities_query: Query<'w, 's, &'static RenderShadowMapVisibleEntities>,
2435    light_key_cache: Res<'w, LightKeyCache>,
2436    specialized_shadow_material_pipeline_cache: ResMut<'w, SpecializedShadowMaterialPipelineCache>,
2437    pending_shadow_queues: ResMut<'w, PendingShadowQueues>,
2438    dirty_specializations: Res<'w, DirtySpecializations>,
2439}
2440
2441pub(crate) fn specialize_shadows(
2442    world: &mut World,
2443    state: &mut SystemState<SpecializeShadowsSystemParam>,
2444    mut work_items: Local<Vec<ShadowSpecializationWorkItem>>,
2445    mut all_shadow_views: Local<HashSet<RetainedViewEntity, FixedHasher>>,
2446) {
2447    work_items.clear();
2448    all_shadow_views.clear();
2449
2450    {
2451        let SpecializeShadowsSystemParam {
2452            render_meshes,
2453            render_mesh_instances,
2454            render_materials,
2455            render_material_instances,
2456            shadow_render_phases,
2457            render_lightmaps,
2458            view_light_entities,
2459            shadow_map_visible_entities_query,
2460            light_key_cache,
2461            mut specialized_shadow_material_pipeline_cache,
2462            mut pending_shadow_queues,
2463            dirty_specializations,
2464        } = state.get_mut(world).unwrap();
2465
2466        for (light_entity, extracted_view_light) in &view_light_entities {
2467            all_shadow_views.insert(extracted_view_light.retained_view_entity);
2468
2469            if !shadow_render_phases.contains_key(&extracted_view_light.retained_view_entity) {
2470                continue;
2471            }
2472            let Some(light_key) = light_key_cache.get(&extracted_view_light.retained_view_entity)
2473            else {
2474                continue;
2475            };
2476
2477            let visible_entities = get_shadow_map_visible_entities(
2478                &shadow_map_visible_entities_query,
2479                light_entity,
2480                extracted_view_light,
2481            );
2482
2483            let mut maybe_specialized_shadow_material_pipeline_cache =
2484                specialized_shadow_material_pipeline_cache
2485                    .get_mut(&extracted_view_light.retained_view_entity);
2486
2487            // Remove cached pipeline IDs corresponding to entities that
2488            // either have been removed or need to be respecialized.
2489            if let Some(ref mut specialized_shadow_material_pipeline_cache) =
2490                maybe_specialized_shadow_material_pipeline_cache
2491            {
2492                if dirty_specializations
2493                    .must_wipe_specializations_for_view(extracted_view_light.retained_view_entity)
2494                {
2495                    specialized_shadow_material_pipeline_cache.clear();
2496                } else {
2497                    for &renderable_entity in dirty_specializations.iter_to_despecialize() {
2498                        specialized_shadow_material_pipeline_cache.remove(&renderable_entity);
2499                    }
2500                }
2501            }
2502
2503            // Initialize the pending queues.
2504            let view_pending_shadow_queues = pending_shadow_queues
2505                .prepare_for_new_frame(extracted_view_light.retained_view_entity);
2506
2507            // NOTE: Lights with shadow mapping disabled will have no visible entities
2508            // so no meshes will be queued
2509
2510            let Some(visible_entities) = visible_entities.get::<Mesh3d>() else {
2511                continue;
2512            };
2513
2514            // Now process all shadow meshes that need to be re-specialized.
2515            for (render_entity, visible_entity) in dirty_specializations.iter_to_specialize(
2516                extracted_view_light.retained_view_entity,
2517                visible_entities,
2518                &view_pending_shadow_queues.prev_frame,
2519            ) {
2520                if maybe_specialized_shadow_material_pipeline_cache
2521                    .as_ref()
2522                    .is_some_and(|specialized_shadow_material_pipeline_cache| {
2523                        specialized_shadow_material_pipeline_cache.contains_key(visible_entity)
2524                    })
2525                {
2526                    continue;
2527                }
2528
2529                // Check for material instance, mesh, and material. If any of
2530                // these fail, it's probably because the relevant asset hasn't
2531                // loaded yet. In that case, add the entity to the list of
2532                // pending mesh materials and bail.
2533                let Some(material_instance) =
2534                    render_material_instances.instances.get(visible_entity)
2535                else {
2536                    view_pending_shadow_queues
2537                        .current_frame
2538                        .insert((*render_entity, *visible_entity));
2539                    continue;
2540                };
2541                let Some(mesh_instance) =
2542                    render_mesh_instances.render_mesh_queue_data(*visible_entity)
2543                else {
2544                    view_pending_shadow_queues
2545                        .current_frame
2546                        .insert((*render_entity, *visible_entity));
2547                    continue;
2548                };
2549                let Some(material) = render_materials.get(material_instance.asset_id) else {
2550                    view_pending_shadow_queues
2551                        .current_frame
2552                        .insert((*render_entity, *visible_entity));
2553                    continue;
2554                };
2555
2556                if !material.properties.shadows_enabled {
2557                    // If the material is not a shadow caster, we don't need to specialize it.
2558                    continue;
2559                }
2560                if !mesh_instance
2561                    .flags()
2562                    .contains(RenderMeshInstanceFlags::SHADOW_CASTER)
2563                {
2564                    continue;
2565                }
2566                let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id()) else {
2567                    continue;
2568                };
2569
2570                let mut mesh_key =
2571                    *light_key | MeshPipelineKey::from_bits_retain(mesh.key_bits.bits());
2572
2573                // Even though we don't use the lightmap in the shadow map, the
2574                // `SetMeshBindGroup` render command will bind the data for it. So
2575                // we need to include the appropriate flag in the mesh pipeline key
2576                // to ensure that the necessary bind group layout entries are
2577                // present.
2578                if render_lightmaps
2579                    .render_lightmaps
2580                    .contains_key(visible_entity)
2581                {
2582                    mesh_key |= MeshPipelineKey::LIGHTMAPPED;
2583                }
2584
2585                mesh_key |= match material.properties.alpha_mode {
2586                    AlphaMode::Mask(_)
2587                    | AlphaMode::Blend
2588                    | AlphaMode::Premultiplied
2589                    | AlphaMode::Add
2590                    | AlphaMode::AlphaToCoverage => MeshPipelineKey::MAY_DISCARD,
2591                    _ => MeshPipelineKey::NONE,
2592                };
2593
2594                if material.properties.prepass_reads_material() {
2595                    mesh_key |= MeshPipelineKey::PREPASS_READS_MATERIAL;
2596                }
2597
2598                work_items.push(ShadowSpecializationWorkItem {
2599                    visible_entity: *visible_entity,
2600                    retained_view_entity: extracted_view_light.retained_view_entity,
2601                    mesh_key,
2602                    layout: mesh.layout.clone(),
2603                    properties: material.properties.clone(),
2604                    material_type_id: material_instance.asset_id.type_id(),
2605                });
2606            }
2607        }
2608
2609        pending_shadow_queues.expire_stale_views(&all_shadow_views);
2610    }
2611
2612    let depth_clip_control_supported = world
2613        .resource::<PrepassPipeline>()
2614        .depth_clip_control_supported;
2615
2616    for item in work_items.drain(..) {
2617        let Some(prepass_specialize) = item.properties.prepass_specialize else {
2618            continue;
2619        };
2620
2621        let key = ErasedMaterialPipelineKey {
2622            type_id: item.material_type_id,
2623            mesh_key: ErasedMeshPipelineKey::new(item.mesh_key),
2624            material_key: item.properties.material_key.clone(),
2625        };
2626
2627        let emulate_unclipped_depth = item
2628            .mesh_key
2629            .contains(MeshPipelineKey::UNCLIPPED_DEPTH_ORTHO)
2630            && !depth_clip_control_supported;
2631        let is_depth_only_opaque = !item
2632            .mesh_key
2633            .intersects(MeshPipelineKey::MAY_DISCARD | MeshPipelineKey::PREPASS_READS_MATERIAL)
2634            && !emulate_unclipped_depth;
2635        let draw_function = if is_depth_only_opaque {
2636            item.properties
2637                .get_draw_function(ShadowsDepthOnlyDrawFunction)
2638        } else {
2639            item.properties.get_draw_function(ShadowsDrawFunction)
2640        };
2641
2642        let Some(draw_function) = draw_function else {
2643            continue;
2644        };
2645
2646        match prepass_specialize(world, &key, &item.layout, &item.properties) {
2647            Ok(pipeline_id) => {
2648                world
2649                    .resource_mut::<SpecializedShadowMaterialPipelineCache>()
2650                    .entry(item.retained_view_entity)
2651                    .or_default()
2652                    .insert(
2653                        item.visible_entity,
2654                        (pipeline_id, draw_function, is_depth_only_opaque),
2655                    );
2656            }
2657            Err(err) => error!("{}", err),
2658        }
2659    }
2660
2661    // Delete specialized pipelines belonging to views that have expired.
2662    world
2663        .resource_mut::<SpecializedShadowMaterialPipelineCache>()
2664        .retain(|view, _| all_shadow_views.contains(view));
2665}
2666
2667/// For each shadow cascade, iterates over all the meshes "visible" from it and
2668/// adds them to [`BinnedRenderPhase`]s or [`SortedRenderPhase`]s as
2669/// appropriate.
2670pub fn queue_shadows(
2671    render_mesh_instances: Res<RenderMeshInstances>,
2672    render_materials: Res<ErasedRenderAssets<PreparedMaterial>>,
2673    render_material_instances: Res<RenderMaterialInstances>,
2674    mut shadow_render_phases: ResMut<ViewBinnedRenderPhases<Shadow>>,
2675    gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
2676    mesh_allocator: Res<MeshAllocator>,
2677    view_light_entities: Query<(&LightEntity, &ExtractedView, &RenderLayers)>,
2678    shadow_map_visible_entities_query: Query<&RenderShadowMapVisibleEntities>,
2679    specialized_material_pipeline_cache: Res<SpecializedShadowMaterialPipelineCache>,
2680    mut pending_shadow_queues: ResMut<PendingShadowQueues>,
2681    dirty_specializations: Res<DirtySpecializations>,
2682    mut mesh_instances_queued_this_iteration_scratch_space: Local<MainEntityHashSet>,
2683) {
2684    for (light_entity, extracted_view_light, view_light_render_layers) in &view_light_entities {
2685        let Some(shadow_phase) =
2686            shadow_render_phases.get_mut(&extracted_view_light.retained_view_entity)
2687        else {
2688            continue;
2689        };
2690
2691        let Some(view_specialized_material_pipeline_cache) =
2692            specialized_material_pipeline_cache.get(&extracted_view_light.retained_view_entity)
2693        else {
2694            continue;
2695        };
2696
2697        // Fetch the pending mesh material queues for this view.
2698        let view_pending_shadow_queues = pending_shadow_queues
2699            .get_mut(&extracted_view_light.retained_view_entity)
2700            .expect("View pending shadow queues should have been created in `specialize_shadows`");
2701
2702        let visible_entities = get_shadow_map_visible_entities(
2703            &shadow_map_visible_entities_query,
2704            light_entity,
2705            extracted_view_light,
2706        );
2707
2708        let Some(visible_entities) = visible_entities.get::<Mesh3d>() else {
2709            continue;
2710        };
2711
2712        // First, remove meshes that need to be respecialized, and those that were removed, from the bins.
2713        for &main_entity in dirty_specializations
2714            .iter_to_dequeue(extracted_view_light.retained_view_entity, visible_entities)
2715        {
2716            shadow_phase.remove(main_entity);
2717        }
2718
2719        // Now iterate through all newly-visible entities and those needing respecialization.
2720        for (render_entity, main_entity) in dirty_specializations.iter_to_queue(
2721            extracted_view_light.retained_view_entity,
2722            visible_entities,
2723            &view_pending_shadow_queues.prev_frame,
2724            &mut mesh_instances_queued_this_iteration_scratch_space,
2725        ) {
2726            let Some(&(pipeline_id, draw_function, is_depth_only_opaque)) =
2727                view_specialized_material_pipeline_cache.get(main_entity)
2728            else {
2729                continue;
2730            };
2731
2732            let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*main_entity)
2733            else {
2734                // We couldn't fetch the mesh, probably because it hasn't
2735                // loaded yet. Add the entity to the list of pending shadows
2736                // and bail.
2737                view_pending_shadow_queues
2738                    .current_frame
2739                    .insert((*render_entity, *main_entity));
2740                continue;
2741            };
2742            if !mesh_instance
2743                .flags()
2744                .contains(RenderMeshInstanceFlags::SHADOW_CASTER)
2745            {
2746                continue;
2747            }
2748
2749            let mesh_layers = mesh_instance.render_layers.as_ref().unwrap_or_default();
2750            if !view_light_render_layers.intersects(mesh_layers) {
2751                continue;
2752            }
2753
2754            let material_bind_group_index = if is_depth_only_opaque {
2755                None
2756            } else {
2757                let Some(material_instance) = render_material_instances.instances.get(main_entity)
2758                else {
2759                    continue;
2760                };
2761                let Some(material) = render_materials.get(material_instance.asset_id) else {
2762                    // We couldn't fetch the material, probably because the
2763                    // material hasn't been loaded yet. Add the entity to the
2764                    // list of pending shadows and bail.
2765                    view_pending_shadow_queues
2766                        .current_frame
2767                        .insert((*render_entity, *main_entity));
2768                    continue;
2769                };
2770                Some(material.binding.group.0)
2771            };
2772
2773            let Some(mesh_slabs) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id()) else {
2774                continue;
2775            };
2776
2777            let batch_set_key = ShadowBatchSetKey {
2778                pipeline: pipeline_id,
2779                draw_function,
2780                material_bind_group_index,
2781                slabs: mesh_slabs,
2782            };
2783
2784            shadow_phase.add(
2785                batch_set_key,
2786                ShadowBinKey {
2787                    asset_id: mesh_instance.mesh_asset_id().into(),
2788                },
2789                (*render_entity, *main_entity),
2790                mesh_instance.current_uniform_index,
2791                BinnedRenderPhaseType::mesh(
2792                    mesh_instance.should_batch(),
2793                    &gpu_preprocessing_support,
2794                ),
2795            );
2796        }
2797    }
2798}
2799
2800pub struct Shadow {
2801    /// Determines which objects can be placed into a *batch set*.
2802    ///
2803    /// Objects in a single batch set can potentially be multi-drawn together,
2804    /// if it's enabled and the current platform supports it.
2805    pub batch_set_key: ShadowBatchSetKey,
2806    /// Information that separates items into bins.
2807    pub bin_key: ShadowBinKey,
2808    pub representative_entity: (Entity, MainEntity),
2809    pub batch_range: Range<u32>,
2810    pub extra_index: PhaseItemExtraIndex,
2811}
2812
2813/// Information that must be identical in order to place opaque meshes in the
2814/// same *batch set*.
2815///
2816/// A batch set is a set of batches that can be multi-drawn together, if
2817/// multi-draw is in use.
2818#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2819pub struct ShadowBatchSetKey {
2820    /// The identifier of the render pipeline.
2821    pub pipeline: CachedRenderPipelineId,
2822
2823    /// The function used to draw.
2824    pub draw_function: DrawFunctionId,
2825
2826    /// The ID of a bind group specific to the material.
2827    ///
2828    /// In the case of PBR, this is the `MaterialBindGroupIndex`.
2829    pub material_bind_group_index: Option<u32>,
2830
2831    /// The IDs of the slabs of GPU memory in the mesh allocator that contain
2832    /// the mesh data.
2833    ///
2834    /// For non-mesh items, you can fill the [`MeshSlabs::vertex_slab_id`] with
2835    /// 0 if your items can be multi-drawn, or with a unique value if they
2836    /// can't.
2837    pub slabs: MeshSlabs,
2838}
2839
2840impl PhaseItemBatchSetKey for ShadowBatchSetKey {
2841    fn indexed(&self) -> bool {
2842        self.slabs.index_slab_id.is_some()
2843    }
2844}
2845
2846/// Data used to bin each object in the shadow map phase.
2847#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2848pub struct ShadowBinKey {
2849    /// The object.
2850    pub asset_id: UntypedAssetId,
2851}
2852
2853impl PhaseItem for Shadow {
2854    #[inline]
2855    fn entity(&self) -> Entity {
2856        self.representative_entity.0
2857    }
2858
2859    fn main_entity(&self) -> MainEntity {
2860        self.representative_entity.1
2861    }
2862
2863    #[inline]
2864    fn draw_function(&self) -> DrawFunctionId {
2865        self.batch_set_key.draw_function
2866    }
2867
2868    #[inline]
2869    fn batch_range(&self) -> &Range<u32> {
2870        &self.batch_range
2871    }
2872
2873    #[inline]
2874    fn batch_range_mut(&mut self) -> &mut Range<u32> {
2875        &mut self.batch_range
2876    }
2877
2878    #[inline]
2879    fn extra_index(&self) -> PhaseItemExtraIndex {
2880        self.extra_index.clone()
2881    }
2882
2883    #[inline]
2884    fn batch_range_and_extra_index_mut(&mut self) -> (&mut Range<u32>, &mut PhaseItemExtraIndex) {
2885        (&mut self.batch_range, &mut self.extra_index)
2886    }
2887}
2888
2889impl BinnedPhaseItem for Shadow {
2890    type BatchSetKey = ShadowBatchSetKey;
2891    type BinKey = ShadowBinKey;
2892
2893    #[inline]
2894    fn new(
2895        batch_set_key: Self::BatchSetKey,
2896        bin_key: Self::BinKey,
2897        representative_entity: (Entity, MainEntity),
2898        batch_range: Range<u32>,
2899        extra_index: PhaseItemExtraIndex,
2900    ) -> Self {
2901        Shadow {
2902            batch_set_key,
2903            bin_key,
2904            representative_entity,
2905            batch_range,
2906            extra_index,
2907        }
2908    }
2909}
2910
2911impl CachedRenderPipelinePhaseItem for Shadow {
2912    #[inline]
2913    fn cached_pipeline(&self) -> CachedRenderPipelineId {
2914        self.batch_set_key.pipeline
2915    }
2916}
2917
2918pub const EARLY_SHADOW_PASS: bool = false;
2919pub const LATE_SHADOW_PASS: bool = true;
2920
2921/// Renders the shadow maps that aren't associated with a specific view.
2922///
2923/// At present, these consist of the point and spot light shadow maps.
2924pub fn shared_shadow_pass<const IS_LATE: bool>(
2925    world: &World,
2926    view_light_query: ViewQuery<(Entity, &ShadowView, &ExtractedView, Has<OcclusionCulling>)>,
2927    shadow_render_phases: Res<ViewBinnedRenderPhases<Shadow>>,
2928    mut ctx: RenderContext,
2929) {
2930    let (view_light_entity, view_light, extracted_light_view, occlusion_culling) =
2931        view_light_query.into_inner();
2932    view_shadow_pass::<IS_LATE>(
2933        view_light_entity,
2934        view_light,
2935        extracted_light_view,
2936        occlusion_culling,
2937        world,
2938        &shadow_render_phases,
2939        &mut ctx,
2940    );
2941}
2942
2943/// Renders the shadow maps that are associated with a specific view.
2944///
2945/// At present, these consist of the directional light shadows.
2946pub fn per_view_shadow_pass<const IS_LATE: bool>(
2947    world: &World,
2948    view: ViewQuery<&ViewLightEntities>,
2949    view_light_query: Query<(
2950        &ShadowView,
2951        &ExtractedView,
2952        Has<OcclusionCulling>,
2953        Has<PreprocessBindGroups>,
2954        Has<SkipGpuPreprocess>,
2955        Has<NoIndirectDrawing>,
2956    )>,
2957    shadow_render_phases: Res<ViewBinnedRenderPhases<Shadow>>,
2958    preprocess_pipelines: Option<Res<PreprocessPipelines>>,
2959    build_indirect_params_bind_groups: Option<Res<BuildIndirectParametersBindGroups>>,
2960    pipeline_cache: Res<PipelineCache>,
2961    indirect_parameters_buffers: Option<Res<IndirectParametersBuffers>>,
2962    build_indirect_parameters_metadata: Option<Res<BuildIndirectParametersMetadata>>,
2963    mut ctx: RenderContext,
2964) {
2965    let view_lights = view.into_inner();
2966
2967    let diagnostics = ctx.diagnostic_recorder();
2968    let diagnostics = diagnostics.as_deref();
2969    let time_span = diagnostics.time_span(ctx.command_encoder(), "per_view_shadow_pass");
2970
2971    for view_light_entity in view_lights.lights.iter().copied() {
2972        let Ok((
2973            view_light,
2974            extracted_light_view,
2975            occlusion_culling,
2976            has_preprocess_bind_groups,
2977            has_skip_gpu_preprocess,
2978            has_no_indirect_drawing,
2979        )) = view_light_query.get(view_light_entity)
2980        else {
2981            continue;
2982        };
2983
2984        // Make sure to build indirect parameters here, as the normal
2985        // `*_prepass_build_indirect_parameters` functions only run for root
2986        // views, and this shadow view isn't a root view.
2987        if has_preprocess_bind_groups
2988            && !has_skip_gpu_preprocess
2989            && !has_no_indirect_drawing
2990            && let (Some(preprocess_pipelines), Some(build_indirect_parameters_metadata)) =
2991                (&preprocess_pipelines, &build_indirect_parameters_metadata)
2992        {
2993            run_build_indirect_parameters(
2994                &mut ctx,
2995                extracted_light_view.retained_view_entity,
2996                build_indirect_params_bind_groups.as_deref(),
2997                &pipeline_cache,
2998                indirect_parameters_buffers.as_deref(),
2999                build_indirect_parameters_metadata,
3000                if IS_LATE {
3001                    &preprocess_pipelines.late_phase
3002                } else {
3003                    &preprocess_pipelines.early_phase
3004                },
3005                if IS_LATE {
3006                    "late_view_shadow_indirect_parameters_building"
3007                } else {
3008                    "early_view_shadow_indirect_parameters_building"
3009                },
3010            );
3011        }
3012
3013        // Draw the shadow map.
3014        view_shadow_pass::<IS_LATE>(
3015            view_light_entity,
3016            view_light,
3017            extracted_light_view,
3018            occlusion_culling,
3019            world,
3020            &shadow_render_phases,
3021            &mut ctx,
3022        );
3023    }
3024
3025    time_span.end(ctx.command_encoder());
3026}
3027
3028/// A common helper function to render a shadow map.
3029fn view_shadow_pass<const IS_LATE: bool>(
3030    view_light_entity: Entity,
3031    view_light: &ShadowView,
3032    extracted_light_view: &ExtractedView,
3033    occlusion_culling: bool,
3034    world: &World,
3035    shadow_render_phases: &ViewBinnedRenderPhases<Shadow>,
3036    ctx: &mut RenderContext,
3037) {
3038    if IS_LATE && !occlusion_culling {
3039        return;
3040    }
3041
3042    let Some(shadow_phase) = shadow_render_phases.get(&extracted_light_view.retained_view_entity)
3043    else {
3044        return;
3045    };
3046
3047    #[cfg(feature = "trace")]
3048    let _shadow_pass_span = info_span!("", "{}", view_light.pass_name).entered();
3049
3050    let depth_stencil_attachment = Some(view_light.depth_attachment.get_attachment(StoreOp::Store));
3051
3052    let mut render_pass = ctx.begin_tracked_render_pass(RenderPassDescriptor {
3053        label: Some(&view_light.pass_name),
3054        color_attachments: &[],
3055        depth_stencil_attachment,
3056        timestamp_writes: None,
3057        occlusion_query_set: None,
3058        multiview_mask: None,
3059    });
3060
3061    if let Err(err) = shadow_phase.render(&mut render_pass, world, view_light_entity) {
3062        error!("Error encountered while rendering the shadow phase {err:?}");
3063    }
3064}
3065
3066/// Creates the [`ClusterableObjectType`] data for a point or spot light.
3067fn point_or_spot_light_to_clusterable(point_light: &ExtractedPointLight) -> ClusterableObjectType {
3068    match point_light.spot_light_angles {
3069        Some((_, outer_angle)) => ClusterableObjectType::SpotLight {
3070            outer_angle,
3071            shadow_maps_enabled: point_light.shadow_maps_enabled,
3072            volumetric: point_light.volumetric,
3073        },
3074        None => ClusterableObjectType::PointLight {
3075            shadow_maps_enabled: point_light.shadow_maps_enabled,
3076            volumetric: point_light.volumetric,
3077        },
3078    }
3079}
3080
3081/// Returns the [`RenderShadowMapVisibleEntities`] table corresponding to the
3082/// given [`LightEntity`].
3083fn get_shadow_map_visible_entities<'w, 's: 'w>(
3084    shadow_map_visible_entities_query: &'w Query<'w, 's, &'_ RenderShadowMapVisibleEntities>,
3085    light_entity: &'_ LightEntity,
3086    extracted_view_light: &'_ ExtractedView,
3087) -> &'w RenderVisibleEntities {
3088    match light_entity {
3089        LightEntity::Directional { light_entity, .. } => {
3090            let retained_view_entity = extracted_view_light.retained_view_entity;
3091            &shadow_map_visible_entities_query
3092                .get(*light_entity)
3093                .expect("Failed to get directional light visible entities")
3094                .subviews
3095                .get(&retained_view_entity)
3096                .expect("Failed to get directional light visible entities for cascade")
3097                .1
3098        }
3099        LightEntity::Point {
3100            light_entity,
3101            face_index,
3102        } => {
3103            // We replace the auxiliary entity with `None`
3104            // because all cubemap views for a single point light
3105            // currently share the same set of visible entities.
3106            let retained_view_entity = RetainedViewEntity {
3107                main_entity: extracted_view_light.retained_view_entity.main_entity,
3108                auxiliary_entity: None,
3109                subview_index: *face_index as u32,
3110            };
3111            &shadow_map_visible_entities_query
3112                .get(*light_entity)
3113                .expect("Failed to get point light visible entities")
3114                .subviews
3115                .get(&retained_view_entity)
3116                .expect("Failed to get point light visible entity for face")
3117                .1
3118        }
3119        LightEntity::Spot { light_entity } => {
3120            // We replace the auxiliary entity with `None`
3121            // because all shadow maps for a single spot light
3122            // currently share the same set of visible entities.
3123            let retained_view_entity = RetainedViewEntity {
3124                main_entity: extracted_view_light.retained_view_entity.main_entity,
3125                auxiliary_entity: None,
3126                subview_index: 0,
3127            };
3128            &shadow_map_visible_entities_query
3129                .get(*light_entity)
3130                .expect("Failed to get spot light visible entities")
3131                .subviews
3132                .get(&retained_view_entity)
3133                .expect("Failed to get spot light visible entity for view")
3134                .1
3135        }
3136    }
3137}
3138
3139/// An extraction system that determines the origin for LOD computation for
3140/// point and spot light shadow maps and updates the [`RenderShadowLodOrigin`]
3141/// with the result.
3142///
3143/// See [`ShadowLodOrigin`] for more details on the algorithm that this system
3144/// uses.
3145pub fn extract_shadow_lod_origin(
3146    global_transform_query: Extract<Query<&GlobalTransform>>,
3147    mut camera_query: Extract<Query<(Entity, &RenderTarget), With<Camera>>>,
3148    mut shadow_lod_origin_query: Extract<Query<Entity, With<ShadowLodOrigin>>>,
3149    mut lights_query: Extract<Query<Entity, Or<(With<PointLight>, With<SpotLight>)>>>,
3150    mut render_shadow_lod_origin: ResMut<RenderShadowLodOrigin>,
3151) {
3152    match bevy_light::get_shadow_lod_origin(
3153        camera_query.transmute_lens_filtered(),
3154        shadow_lod_origin_query.transmute_lens_filtered(),
3155        lights_query.transmute_lens_filtered(),
3156    )
3157    .and_then(|shadow_lod_origin_entity| global_transform_query.get(shadow_lod_origin_entity).ok())
3158    {
3159        Some(global_transform) => {
3160            render_shadow_lod_origin.0 = global_transform.translation();
3161        }
3162        None => render_shadow_lod_origin.0 = Default::default(),
3163    }
3164}