mirror of
https://github.com/bevyengine/bevy
synced 2025-02-16 14:08:32 +00:00
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
1 changed files with 91 additions and 37 deletions
|
@ -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.
|
||||
|
|
Loading…
Add table
Reference in a new issue