Skip to content

Commit 4ac528a

Browse files
authored
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
1 parent acbed60 commit 4ac528a

File tree

1 file changed

+91
-37
lines changed

1 file changed

+91
-37
lines changed

crates/bevy_pbr/src/render/light.rs

Lines changed: 91 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use bevy_color::ColorToComponents;
44
use bevy_core_pipeline::core_3d::{Camera3d, CORE_3D_DEPTH_FORMAT};
55
use bevy_derive::{Deref, DerefMut};
66
use bevy_ecs::{
7-
entity::{EntityHashMap, EntityHashSet},
7+
entity::{EntityHash, EntityHashMap, EntityHashSet},
88
prelude::*,
99
system::lifetimeless::Read,
1010
};
@@ -459,7 +459,9 @@ fn create_render_visible_mesh_entities(
459459
}
460460

461461
#[derive(Component, Default, Deref, DerefMut)]
462-
pub struct LightViewEntities(Vec<Entity>);
462+
/// Component automatically attached to a light entity to track light-view entities
463+
/// for each view.
464+
pub struct LightViewEntities(EntityHashMap<Vec<Entity>>);
463465

464466
// TODO: using required component
465467
pub(crate) fn add_light_view_entities(
@@ -477,9 +479,11 @@ pub(crate) fn remove_light_view_entities(
477479
mut commands: Commands,
478480
) {
479481
if let Ok(entities) = query.get(trigger.entity()) {
480-
for e in entities.0.iter().copied() {
481-
if let Some(mut v) = commands.get_entity(e) {
482-
v.despawn();
482+
for v in entities.0.values() {
483+
for e in v.iter().copied() {
484+
if let Some(mut v) = commands.get_entity(e) {
485+
v.despawn();
486+
}
483487
}
484488
}
485489
}
@@ -731,7 +735,8 @@ pub fn prepare_lights(
731735
let point_light_count = point_lights
732736
.iter()
733737
.filter(|light| light.1.spot_light_angles.is_none())
734-
.count();
738+
.count()
739+
.min(max_texture_cubes);
735740

736741
let point_light_volumetric_enabled_count = point_lights
737742
.iter()
@@ -759,6 +764,12 @@ pub fn prepare_lights(
759764
.count()
760765
.min(max_texture_array_layers / MAX_CASCADES_PER_LIGHT);
761766

767+
let spot_light_count = point_lights
768+
.iter()
769+
.filter(|(_, light, _)| light.spot_light_angles.is_some())
770+
.count()
771+
.min(max_texture_array_layers - directional_shadow_enabled_count * MAX_CASCADES_PER_LIGHT);
772+
762773
let spot_light_volumetric_enabled_count = point_lights
763774
.iter()
764775
.filter(|(_, light, _)| light.volumetric && light.spot_light_angles.is_some())
@@ -956,9 +967,12 @@ pub fn prepare_lights(
956967

957968
live_shadow_mapping_lights.clear();
958969

959-
let mut dir_light_view_offset = 0;
970+
let mut live_views = EntityHashSet::with_capacity_and_hasher(views_count, EntityHash);
971+
960972
// set up light data for each view
961-
for (offset, (entity, extracted_view, clusters, maybe_layers)) in views.iter().enumerate() {
973+
for (entity, extracted_view, clusters, maybe_layers) in views.iter() {
974+
live_views.insert(entity);
975+
962976
let point_light_depth_texture = texture_cache.get(
963977
&render_device,
964978
TextureDescriptor {
@@ -1032,9 +1046,19 @@ pub fn prepare_lights(
10321046
for &(light_entity, light, (point_light_frusta, _)) in point_lights
10331047
.iter()
10341048
// Lights are sorted, shadow enabled lights are first
1035-
.take(point_light_shadow_maps_count)
1036-
.filter(|(_, light, _)| light.shadows_enabled)
1049+
.take(point_light_count)
10371050
{
1051+
let Ok(mut light_view_entities) = light_view_entities.get_mut(light_entity) else {
1052+
continue;
1053+
};
1054+
1055+
if !light.shadows_enabled {
1056+
if let Some(entities) = light_view_entities.remove(&entity) {
1057+
despawn_entities(&mut commands, entities);
1058+
}
1059+
continue;
1060+
}
1061+
10381062
let light_index = *global_light_meta
10391063
.entity_to_index
10401064
.get(&light_entity)
@@ -1044,14 +1068,10 @@ pub fn prepare_lights(
10441068
// and ignore rotation because we want the shadow map projections to align with the axes
10451069
let view_translation = GlobalTransform::from_translation(light.transform.translation());
10461070

1047-
let Ok(mut light_entities) = light_view_entities.get_mut(light_entity) else {
1048-
continue;
1049-
};
1050-
10511071
// for each face of a cube and each view we spawn a light entity
1052-
while light_entities.len() < 6 * (offset + 1) {
1053-
light_entities.push(commands.spawn_empty().id());
1054-
}
1072+
let light_view_entities = light_view_entities
1073+
.entry(entity)
1074+
.or_insert_with(|| (0..6).map(|_| commands.spawn_empty().id()).collect());
10551075

10561076
let cube_face_projection = Mat4::perspective_infinite_reverse_rh(
10571077
core::f32::consts::FRAC_PI_2,
@@ -1062,7 +1082,7 @@ pub fn prepare_lights(
10621082
for (face_index, ((view_rotation, frustum), view_light_entity)) in cube_face_rotations
10631083
.iter()
10641084
.zip(&point_light_frusta.unwrap().frusta)
1065-
.zip(light_entities.iter().skip(6 * offset).copied())
1085+
.zip(light_view_entities.iter().copied())
10661086
.enumerate()
10671087
{
10681088
let depth_texture_view =
@@ -1119,16 +1139,23 @@ pub fn prepare_lights(
11191139
for (light_index, &(light_entity, light, (_, spot_light_frustum))) in point_lights
11201140
.iter()
11211141
.skip(point_light_count)
1122-
.take(spot_light_shadow_maps_count)
1142+
.take(spot_light_count)
11231143
.enumerate()
11241144
{
1125-
let spot_world_from_view = spot_light_world_from_view(&light.transform);
1126-
let spot_world_from_view = spot_world_from_view.into();
1127-
11281145
let Ok(mut light_view_entities) = light_view_entities.get_mut(light_entity) else {
11291146
continue;
11301147
};
11311148

1149+
if !light.shadows_enabled {
1150+
if let Some(entities) = light_view_entities.remove(&entity) {
1151+
despawn_entities(&mut commands, entities);
1152+
}
1153+
continue;
1154+
}
1155+
1156+
let spot_world_from_view = spot_light_world_from_view(&light.transform);
1157+
let spot_world_from_view = spot_world_from_view.into();
1158+
11321159
let angle = light.spot_light_angles.expect("lights should be sorted so that \
11331160
[point_light_count..point_light_count + spot_light_shadow_maps_count] are spot lights").1;
11341161
let spot_projection = spot_light_clip_from_view(angle, light.shadow_map_near_z);
@@ -1147,11 +1174,11 @@ pub fn prepare_lights(
11471174
array_layer_count: Some(1u32),
11481175
});
11491176

1150-
while light_view_entities.len() < offset + 1 {
1151-
light_view_entities.push(commands.spawn_empty().id());
1152-
}
1177+
let light_view_entities = light_view_entities
1178+
.entry(entity)
1179+
.or_insert_with(|| vec![commands.spawn_empty().id()]);
11531180

1154-
let view_light_entity = light_view_entities[offset];
1181+
let view_light_entity = light_view_entities[0];
11551182

11561183
commands.entity(view_light_entity).insert((
11571184
ShadowView {
@@ -1194,14 +1221,21 @@ pub fn prepare_lights(
11941221
let Ok(mut light_view_entities) = light_view_entities.get_mut(light_entity) else {
11951222
continue;
11961223
};
1224+
11971225
// Check if the light intersects with the view.
11981226
if !view_layers.intersects(&light.render_layers) {
11991227
gpu_light.skip = 1u32;
1228+
if let Some(entities) = light_view_entities.remove(&entity) {
1229+
despawn_entities(&mut commands, entities);
1230+
}
12001231
continue;
12011232
}
12021233

12031234
// Only deal with cascades when shadows are enabled.
12041235
if (gpu_light.flags & DirectionalLightFlags::SHADOWS_ENABLED.bits()) == 0u32 {
1236+
if let Some(entities) = light_view_entities.remove(&entity) {
1237+
despawn_entities(&mut commands, entities);
1238+
}
12051239
continue;
12061240
}
12071241

@@ -1222,18 +1256,19 @@ pub fn prepare_lights(
12221256
.zip(frusta)
12231257
.zip(&light.cascade_shadow_config.bounds);
12241258

1225-
while light_view_entities.len() < dir_light_view_offset + iter.len() {
1226-
light_view_entities.push(commands.spawn_empty().id());
1259+
let light_view_entities = light_view_entities.entry(entity).or_insert_with(|| {
1260+
(0..iter.len())
1261+
.map(|_| commands.spawn_empty().id())
1262+
.collect()
1263+
});
1264+
if light_view_entities.len() != iter.len() {
1265+
let entities = core::mem::take(light_view_entities);
1266+
despawn_entities(&mut commands, entities);
1267+
light_view_entities.extend((0..iter.len()).map(|_| commands.spawn_empty().id()));
12271268
}
12281269

1229-
for (cascade_index, (((cascade, frustum), bound), view_light_entity)) in iter
1230-
.zip(
1231-
light_view_entities
1232-
.iter()
1233-
.skip(dir_light_view_offset)
1234-
.copied(),
1235-
)
1236-
.enumerate()
1270+
for (cascade_index, (((cascade, frustum), bound), view_light_entity)) in
1271+
iter.zip(light_view_entities.iter().copied()).enumerate()
12371272
{
12381273
gpu_lights.directional_lights[light_index].cascades[cascade_index] =
12391274
GpuDirectionalCascade {
@@ -1292,7 +1327,6 @@ pub fn prepare_lights(
12921327

12931328
shadow_render_phases.insert_or_clear(view_light_entity);
12941329
live_shadow_mapping_lights.insert(view_light_entity);
1295-
dir_light_view_offset += 1;
12961330
}
12971331
}
12981332

@@ -1360,9 +1394,29 @@ pub fn prepare_lights(
13601394
));
13611395
}
13621396

1397+
// Despawn light-view entities for views that no longer exist
1398+
for mut entities in &mut light_view_entities {
1399+
for (_, light_view_entities) in
1400+
entities.extract_if(|entity, _| !live_views.contains(entity))
1401+
{
1402+
despawn_entities(&mut commands, light_view_entities);
1403+
}
1404+
}
1405+
13631406
shadow_render_phases.retain(|entity, _| live_shadow_mapping_lights.contains(entity));
13641407
}
13651408

1409+
fn despawn_entities(commands: &mut Commands, entities: Vec<Entity>) {
1410+
if entities.is_empty() {
1411+
return;
1412+
}
1413+
commands.queue(move |world: &mut World| {
1414+
for entity in entities {
1415+
world.despawn(entity);
1416+
}
1417+
});
1418+
}
1419+
13661420
/// For each shadow cascade, iterates over all the meshes "visible" from it and
13671421
/// adds them to [`BinnedRenderPhase`]s or [`SortedRenderPhase`]s as
13681422
/// appropriate.

0 commit comments

Comments
 (0)