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