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:
akimakinai 2024-10-15 22:54:09 +09:00 committed by GitHub
parent acbed6040e
commit 4ac528a579
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -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,9 +479,11 @@ 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() {
if let Some(mut v) = commands.get_entity(e) {
v.despawn();
for v in entities.0.values() {
for e in v.iter().copied() {
if let Some(mut v) = commands.get_entity(e) {
v.despawn();
}
}
}
}
@ -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.