Commit 306aaed
authored
Prevent panic in
# Objective
Fixes #24804. In some edge cases `check_dir_light_mesh_visibility`
reuses stale data from previous frames, causing a panic.
## Solution
`check_dir_light_mesh_visibility` uses `view_visible_entities_queue` (a
`Local<Parallel<Vec<Vec<Entity>>>>`) as a per-thread buffer, where the
outer `Vec` has one slot per shadow cascade. Being `Local`, the buffers
persist across frames.
Each frame, the buffers should be resized to the current cascade count
in the `par_iter` init closure. But that init only runs for threads that
actually receive work. If the number of meshes goes down, some threads
stay idle and never run their init, leaving their outer `Vec` at
whatever length it had the previous frame. If the cascade count then
increases in the same frame, those buffers get indexed with the new
higher cascade count, which is out-of-bounds.
This fixes the issue by resizing all buffers before the `par_iter` loop.
## Testing
Made a minimal repro for #24804 that panics on main but not with this
PR.
<details>
<summary>Click to view showcase</summary>
```rust
//! Repro for #24804
use bevy::{
light::{CascadeShadowConfig, CascadeShadowConfigBuilder},
prelude::*,
};
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.add_systems(Update, trigger_cascade_change)
.run();
}
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
commands.spawn((
Camera3d::default(),
Transform::from_xyz(0.0, 20.0, 40.0).looking_at(Vec3::ZERO, Vec3::Y),
));
// One cascade so thread-local buffers all get sized to 1
let shadow_config: CascadeShadowConfig = CascadeShadowConfigBuilder {
num_cascades: 1,
..default()
}
.into();
commands.spawn((
DirectionalLight {
illuminance: 10_000.0,
shadow_maps_enabled: true,
..default()
},
shadow_config,
Transform::from_rotation(Quat::from_euler(EulerRot::XYZ, -1.0, 0.5, 0.0)),
));
// Spawn lots of cubes to use multiple threads from the pool
let mesh = meshes.add(Cuboid::new(1.0, 1.0, 1.0));
let mat = materials.add(Color::WHITE);
for x in -10..10_i32 {
for z in -5..5_i32 {
commands.spawn((
Mesh3d(mesh.clone()),
MeshMaterial3d(mat.clone()),
Transform::from_xyz(x as f32 * 2.0, 0.0, z as f32 * 2.0),
));
}
}
}
fn trigger_cascade_change(
mut commands: Commands,
mut frame: Local<u32>,
meshes: Query<Entity, With<Mesh3d>>,
mut light: Query<&mut CascadeShadowConfig, With<DirectionalLight>>,
) {
*frame += 1;
if *frame != 200 {
return;
}
// Only keep one cube so most threadsdon't work this frame
let mut iter = meshes.iter();
iter.next(); // keep one
for entity in iter {
commands.entity(entity).despawn();
}
// Switch to 4 cascades so idle threads cause a panic
for mut config in light.iter_mut() {
*config = CascadeShadowConfigBuilder {
num_cascades: 4,
..default()
}
.into();
}
}
```
</details>check_dir_light_mesh_visibility (#24807)1 parent 5558f7b commit 306aaed
1 file changed
Lines changed: 7 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
400 | 400 | | |
401 | 401 | | |
402 | 402 | | |
| 403 | + | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
403 | 410 | | |
404 | 411 | | |
405 | 412 | | |
| |||
0 commit comments