Despawn unused light-view entity (#15902)
# Objective - Fixes #15897 ## Solution - Despawn light view entities when they go unused or when the corresponding view is not alive. ## Testing - `scene_viewer` example no longer prints "The preprocessing index buffer wasn't present" warning - modified an example to try toggling shadows for all kinds of light: https://gist.github.com/akimakinai/ddb0357191f5052b654370699d2314cf
This commit is contained in:
		
							parent
							
								
									acbed6040e
								
							
						
					
					
						commit
						4ac528a579
					
				| @ -4,7 +4,7 @@ use bevy_color::ColorToComponents; | ||||
| use bevy_core_pipeline::core_3d::{Camera3d, CORE_3D_DEPTH_FORMAT}; | ||||
| use bevy_derive::{Deref, DerefMut}; | ||||
| use bevy_ecs::{ | ||||
|     entity::{EntityHashMap, EntityHashSet}, | ||||
|     entity::{EntityHash, EntityHashMap, EntityHashSet}, | ||||
|     prelude::*, | ||||
|     system::lifetimeless::Read, | ||||
| }; | ||||
| @ -459,7 +459,9 @@ fn create_render_visible_mesh_entities( | ||||
| } | ||||
| 
 | ||||
| #[derive(Component, Default, Deref, DerefMut)] | ||||
| pub struct LightViewEntities(Vec<Entity>); | ||||
| /// Component automatically attached to a light entity to track light-view entities
 | ||||
| /// for each view.
 | ||||
| pub struct LightViewEntities(EntityHashMap<Vec<Entity>>); | ||||
| 
 | ||||
| // TODO: using required component
 | ||||
| pub(crate) fn add_light_view_entities( | ||||
| @ -477,13 +479,15 @@ pub(crate) fn remove_light_view_entities( | ||||
|     mut commands: Commands, | ||||
| ) { | ||||
|     if let Ok(entities) = query.get(trigger.entity()) { | ||||
|         for e in entities.0.iter().copied() { | ||||
|         for v in entities.0.values() { | ||||
|             for e in v.iter().copied() { | ||||
|                 if let Some(mut v) = commands.get_entity(e) { | ||||
|                     v.despawn(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub(crate) struct CubeMapFace { | ||||
|     pub(crate) target: Vec3, | ||||
| @ -731,7 +735,8 @@ pub fn prepare_lights( | ||||
|     let point_light_count = point_lights | ||||
|         .iter() | ||||
|         .filter(|light| light.1.spot_light_angles.is_none()) | ||||
|         .count(); | ||||
|         .count() | ||||
|         .min(max_texture_cubes); | ||||
| 
 | ||||
|     let point_light_volumetric_enabled_count = point_lights | ||||
|         .iter() | ||||
| @ -759,6 +764,12 @@ pub fn prepare_lights( | ||||
|         .count() | ||||
|         .min(max_texture_array_layers / MAX_CASCADES_PER_LIGHT); | ||||
| 
 | ||||
|     let spot_light_count = point_lights | ||||
|         .iter() | ||||
|         .filter(|(_, light, _)| light.spot_light_angles.is_some()) | ||||
|         .count() | ||||
|         .min(max_texture_array_layers - directional_shadow_enabled_count * MAX_CASCADES_PER_LIGHT); | ||||
| 
 | ||||
|     let spot_light_volumetric_enabled_count = point_lights | ||||
|         .iter() | ||||
|         .filter(|(_, light, _)| light.volumetric && light.spot_light_angles.is_some()) | ||||
| @ -956,9 +967,12 @@ pub fn prepare_lights( | ||||
| 
 | ||||
|     live_shadow_mapping_lights.clear(); | ||||
| 
 | ||||
|     let mut dir_light_view_offset = 0; | ||||
|     let mut live_views = EntityHashSet::with_capacity_and_hasher(views_count, EntityHash); | ||||
| 
 | ||||
|     // set up light data for each view
 | ||||
|     for (offset, (entity, extracted_view, clusters, maybe_layers)) in views.iter().enumerate() { | ||||
|     for (entity, extracted_view, clusters, maybe_layers) in views.iter() { | ||||
|         live_views.insert(entity); | ||||
| 
 | ||||
|         let point_light_depth_texture = texture_cache.get( | ||||
|             &render_device, | ||||
|             TextureDescriptor { | ||||
| @ -1032,9 +1046,19 @@ pub fn prepare_lights( | ||||
|         for &(light_entity, light, (point_light_frusta, _)) in point_lights | ||||
|             .iter() | ||||
|             // Lights are sorted, shadow enabled lights are first
 | ||||
|             .take(point_light_shadow_maps_count) | ||||
|             .filter(|(_, light, _)| light.shadows_enabled) | ||||
|             .take(point_light_count) | ||||
|         { | ||||
|             let Ok(mut light_view_entities) = light_view_entities.get_mut(light_entity) else { | ||||
|                 continue; | ||||
|             }; | ||||
| 
 | ||||
|             if !light.shadows_enabled { | ||||
|                 if let Some(entities) = light_view_entities.remove(&entity) { | ||||
|                     despawn_entities(&mut commands, entities); | ||||
|                 } | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             let light_index = *global_light_meta | ||||
|                 .entity_to_index | ||||
|                 .get(&light_entity) | ||||
| @ -1044,14 +1068,10 @@ pub fn prepare_lights( | ||||
|             // and ignore rotation because we want the shadow map projections to align with the axes
 | ||||
|             let view_translation = GlobalTransform::from_translation(light.transform.translation()); | ||||
| 
 | ||||
|             let Ok(mut light_entities) = light_view_entities.get_mut(light_entity) else { | ||||
|                 continue; | ||||
|             }; | ||||
| 
 | ||||
|             // for each face of a cube and each view we spawn a light entity
 | ||||
|             while light_entities.len() < 6 * (offset + 1) { | ||||
|                 light_entities.push(commands.spawn_empty().id()); | ||||
|             } | ||||
|             let light_view_entities = light_view_entities | ||||
|                 .entry(entity) | ||||
|                 .or_insert_with(|| (0..6).map(|_| commands.spawn_empty().id()).collect()); | ||||
| 
 | ||||
|             let cube_face_projection = Mat4::perspective_infinite_reverse_rh( | ||||
|                 core::f32::consts::FRAC_PI_2, | ||||
| @ -1062,7 +1082,7 @@ pub fn prepare_lights( | ||||
|             for (face_index, ((view_rotation, frustum), view_light_entity)) in cube_face_rotations | ||||
|                 .iter() | ||||
|                 .zip(&point_light_frusta.unwrap().frusta) | ||||
|                 .zip(light_entities.iter().skip(6 * offset).copied()) | ||||
|                 .zip(light_view_entities.iter().copied()) | ||||
|                 .enumerate() | ||||
|             { | ||||
|                 let depth_texture_view = | ||||
| @ -1119,16 +1139,23 @@ pub fn prepare_lights( | ||||
|         for (light_index, &(light_entity, light, (_, spot_light_frustum))) in point_lights | ||||
|             .iter() | ||||
|             .skip(point_light_count) | ||||
|             .take(spot_light_shadow_maps_count) | ||||
|             .take(spot_light_count) | ||||
|             .enumerate() | ||||
|         { | ||||
|             let spot_world_from_view = spot_light_world_from_view(&light.transform); | ||||
|             let spot_world_from_view = spot_world_from_view.into(); | ||||
| 
 | ||||
|             let Ok(mut light_view_entities) = light_view_entities.get_mut(light_entity) else { | ||||
|                 continue; | ||||
|             }; | ||||
| 
 | ||||
|             if !light.shadows_enabled { | ||||
|                 if let Some(entities) = light_view_entities.remove(&entity) { | ||||
|                     despawn_entities(&mut commands, entities); | ||||
|                 } | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             let spot_world_from_view = spot_light_world_from_view(&light.transform); | ||||
|             let spot_world_from_view = spot_world_from_view.into(); | ||||
| 
 | ||||
|             let angle = light.spot_light_angles.expect("lights should be sorted so that \ | ||||
|                 [point_light_count..point_light_count + spot_light_shadow_maps_count] are spot lights").1;
 | ||||
|             let spot_projection = spot_light_clip_from_view(angle, light.shadow_map_near_z); | ||||
| @ -1147,11 +1174,11 @@ pub fn prepare_lights( | ||||
|                         array_layer_count: Some(1u32), | ||||
|                     }); | ||||
| 
 | ||||
|             while light_view_entities.len() < offset + 1 { | ||||
|                 light_view_entities.push(commands.spawn_empty().id()); | ||||
|             } | ||||
|             let light_view_entities = light_view_entities | ||||
|                 .entry(entity) | ||||
|                 .or_insert_with(|| vec![commands.spawn_empty().id()]); | ||||
| 
 | ||||
|             let view_light_entity = light_view_entities[offset]; | ||||
|             let view_light_entity = light_view_entities[0]; | ||||
| 
 | ||||
|             commands.entity(view_light_entity).insert(( | ||||
|                 ShadowView { | ||||
| @ -1194,14 +1221,21 @@ pub fn prepare_lights( | ||||
|             let Ok(mut light_view_entities) = light_view_entities.get_mut(light_entity) else { | ||||
|                 continue; | ||||
|             }; | ||||
| 
 | ||||
|             // Check if the light intersects with the view.
 | ||||
|             if !view_layers.intersects(&light.render_layers) { | ||||
|                 gpu_light.skip = 1u32; | ||||
|                 if let Some(entities) = light_view_entities.remove(&entity) { | ||||
|                     despawn_entities(&mut commands, entities); | ||||
|                 } | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             // Only deal with cascades when shadows are enabled.
 | ||||
|             if (gpu_light.flags & DirectionalLightFlags::SHADOWS_ENABLED.bits()) == 0u32 { | ||||
|                 if let Some(entities) = light_view_entities.remove(&entity) { | ||||
|                     despawn_entities(&mut commands, entities); | ||||
|                 } | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
| @ -1222,18 +1256,19 @@ pub fn prepare_lights( | ||||
|                 .zip(frusta) | ||||
|                 .zip(&light.cascade_shadow_config.bounds); | ||||
| 
 | ||||
|             while light_view_entities.len() < dir_light_view_offset + iter.len() { | ||||
|                 light_view_entities.push(commands.spawn_empty().id()); | ||||
|             let light_view_entities = light_view_entities.entry(entity).or_insert_with(|| { | ||||
|                 (0..iter.len()) | ||||
|                     .map(|_| commands.spawn_empty().id()) | ||||
|                     .collect() | ||||
|             }); | ||||
|             if light_view_entities.len() != iter.len() { | ||||
|                 let entities = core::mem::take(light_view_entities); | ||||
|                 despawn_entities(&mut commands, entities); | ||||
|                 light_view_entities.extend((0..iter.len()).map(|_| commands.spawn_empty().id())); | ||||
|             } | ||||
| 
 | ||||
|             for (cascade_index, (((cascade, frustum), bound), view_light_entity)) in iter | ||||
|                 .zip( | ||||
|                     light_view_entities | ||||
|                         .iter() | ||||
|                         .skip(dir_light_view_offset) | ||||
|                         .copied(), | ||||
|                 ) | ||||
|                 .enumerate() | ||||
|             for (cascade_index, (((cascade, frustum), bound), view_light_entity)) in | ||||
|                 iter.zip(light_view_entities.iter().copied()).enumerate() | ||||
|             { | ||||
|                 gpu_lights.directional_lights[light_index].cascades[cascade_index] = | ||||
|                     GpuDirectionalCascade { | ||||
| @ -1292,7 +1327,6 @@ pub fn prepare_lights( | ||||
| 
 | ||||
|                 shadow_render_phases.insert_or_clear(view_light_entity); | ||||
|                 live_shadow_mapping_lights.insert(view_light_entity); | ||||
|                 dir_light_view_offset += 1; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| @ -1360,9 +1394,29 @@ pub fn prepare_lights( | ||||
|         )); | ||||
|     } | ||||
| 
 | ||||
|     // Despawn light-view entities for views that no longer exist
 | ||||
|     for mut entities in &mut light_view_entities { | ||||
|         for (_, light_view_entities) in | ||||
|             entities.extract_if(|entity, _| !live_views.contains(entity)) | ||||
|         { | ||||
|             despawn_entities(&mut commands, light_view_entities); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     shadow_render_phases.retain(|entity, _| live_shadow_mapping_lights.contains(entity)); | ||||
| } | ||||
| 
 | ||||
| fn despawn_entities(commands: &mut Commands, entities: Vec<Entity>) { | ||||
|     if entities.is_empty() { | ||||
|         return; | ||||
|     } | ||||
|     commands.queue(move |world: &mut World| { | ||||
|         for entity in entities { | ||||
|             world.despawn(entity); | ||||
|         } | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| /// For each shadow cascade, iterates over all the meshes "visible" from it and
 | ||||
| /// adds them to [`BinnedRenderPhase`]s or [`SortedRenderPhase`]s as
 | ||||
| /// appropriate.
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 akimakinai
						akimakinai