bevy_pbr/render/
light.rs

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