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 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 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 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 pub occlusion_culling: bool,
90}
91
92bitflags::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
126bitflags::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 cluster_dimensions: UVec4,
144 cluster_factors: Vec4,
148 n_directional_lights: u32,
149 spot_light_shadowmap_offset: i32,
151 ambient_light_affects_lightmapped_meshes: u32,
152}
153
154#[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
183impl 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 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 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 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 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 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 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 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 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 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)]
545pub struct LightViewEntities(EntityHashMap<Vec<Entity>>);
548
549pub(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
559pub(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
590pub(crate) const CUBE_MAP_FACES: [CubeMapFace; 6] = [
598 CubeMapFace {
600 target: Vec3::X,
601 up: Vec3::Y,
602 },
603 CubeMapFace {
605 target: Vec3::NEG_X,
606 up: Vec3::Y,
607 },
608 CubeMapFace {
610 target: Vec3::Y,
611 up: Vec3::Z,
612 },
613 CubeMapFace {
615 target: Vec3::NEG_Y,
616 up: Vec3::NEG_Z,
617 },
618 CubeMapFace {
620 target: Vec3::NEG_Z,
621 up: Vec3::Y,
622 },
623 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#[derive(Component)]
662pub struct ViewLightEntities {
663 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
712pub(crate) fn spot_light_world_from_view(transform: &GlobalTransform) -> Mat4 {
717 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 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 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 point_lights.sort_by_cached_key(|(entity, _, light, _)| {
897 (
898 ClusterableObjectType::from_point_or_spot_light(light).ordering(),
899 *entity,
900 )
901 });
902
903 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 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 light_direction.xz().extend(spot_scale).extend(spot_offset),
968 ops::tan(outer),
969 )
970 }
971 None => {
972 (
973 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 0.0,
982 )
983 }
984 };
985
986 gpu_point_lights.push(GpuClusterableObject {
987 light_custom_data,
988 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 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 #[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 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 if light.volumetric
1202 && light.shadows_enabled
1203 && (index < directional_volumetric_enabled_count)
1204 {
1205 flags |= DirectionalLightFlags::VOLUMETRIC;
1206 }
1207
1208 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 cascades: [GpuDirectionalCascade::default(); MAX_CASCADES_PER_LIGHT],
1232 color: Vec4::from_slice(&light.color.to_f32_array()) * light.illuminance,
1235 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 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 for &(light_entity, light_main_entity, light, (point_light_frusta, _)) in point_lights
1271 .iter()
1272 .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 let view_translation = GlobalTransform::from_translation(light.transform.translation());
1294
1295 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 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 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 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 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 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 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 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 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 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 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 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
1698pub 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 #[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 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 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 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 specialized_material_pipeline_cache.retain(|view, _| all_shadow_views.contains(view));
1977}
1978
1979pub 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 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 pub batch_set_key: ShadowBatchSetKey,
2115 pub bin_key: ShadowBinKey,
2117 pub representative_entity: (Entity, MainEntity),
2118 pub batch_range: Range<u32>,
2119 pub extra_index: PhaseItemExtraIndex,
2120}
2121
2122#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2128pub struct ShadowBatchSetKey {
2129 pub pipeline: CachedRenderPipelineId,
2131
2132 pub draw_function: DrawFunctionId,
2134
2135 pub material_bind_group_index: Option<u32>,
2139
2140 pub vertex_slab: SlabId,
2145
2146 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#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2160pub struct ShadowBinKey {
2161 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#[derive(Deref, DerefMut)]
2236pub struct EarlyShadowPassNode(ShadowPassNode);
2237
2238#[derive(Deref, DerefMut)]
2243pub struct LateShadowPassNode(ShadowPassNode);
2244
2245pub struct ShadowPassNode {
2248 main_view_query: QueryState<Read<ViewLightEntities>>,
2250 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 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 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}