Fix the crash in Bistro by avoiding binning mesh instances multiple times.#24984
Conversation
times. At the moment, `DirtySpecializations::iter_to_queue` attempts to avoid yielding the same mesh instance multiple times in a complex way. Unfortunately, this logic is incorrect for mesh instances that couldn't be queued because their materials haven't loaded yet (i.e. the contents of `last_frame_view_pending_queues`). The Bistro example tests this code path, as it has many materials that take a significant amount of time to load. Code exists that tries to handle the case in which a mesh is re-binned, and this is why no crash occurred before PR bevyengine#23662. PR bevyengine#23662 made this incorrect code *more* incorrect because it bumps the instance count regardless of whether the mesh instance was already present in the bin; this causes a crash later with an out-of-bounds array access. However, it's always illegal to try to bin a binned entity; the entity must be removed from its bin first. Therefore, moving the instance count bump to the case in which the entity wasn't already in a bin is the wrong fix. This commit contains the correct fix, which is to change `DirtySpecializations::iter_to_queue` so that it doesn't yield duplicate entities. This PR removes the complex logic that failed to account for all cases of duplicate mesh instances in favor of a simple hash set that tracks the mesh instances yielded so far. Additionally, this PR deletes the code path that tried to handle the case in which a mesh that's already binned is binned again and replaces it with a panic in debug mode. I verified that this change fixes the crash in Bistro via `cargo run --features=bevy/debug -p bistro --bin bistro`. Note that on my NVIDIA GeForce RTX 2060, I still get an OOM error on that test case, but that's a separate issue.
|
This doesn’t need to be in 0.19.1 as PR #23662 isn’t in 0.19. |
kfc35
left a comment
There was a problem hiding this comment.
This fixes the startup errors for mixed_lighting and clustered_decal_maps, but switching light types in pcss (cargo run --example pcss —features="experimental_pbr_pcss") still errors when switching light types, now with the new error. That example removes a Light component from an entity and sticks a different Light on the same entity when the light type is switched. It results in the new debug_assert! failing in render_phase/mod.rs:
thread 'Compute Task Pool (1)' (3041233) panicked at crates/bevy_render/src/render_phase/mod.rs:295:9:
assertion failed: maybe_previous_binned_mesh_instance_buffer_index.is_none()
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
thread 'Compute Task Pool (1)' (3041233) panicked at crates/bevy_ecs/src/error/handler.rs:141:1:
Encountered an error in system `bevy_pbr::render::light::queue_shadows`: System panicked
Encountered a panic in system `bevy_pbr::render::light::queue_shadows`!
thread 'Render thread' (3041323) panicked at crates/bevy_ecs/src/error/handler.rs:141:1:
Encountered an error in system `bevy_render::run_render_schedule`: Exclusive system panicked
Encountered a panic in system `bevy_render::run_render_schedule`!
I’m approving since this does fix several crashes. This probably supercedes #24980 but the issue with pcss still needs to be resolved.
| }) | ||
| .chain(last_frame_view_pending_queues.iter().filter_map( | ||
| |(entity, main_entity)| { | ||
| if render_visible_mesh_entities.entity_pair_is_visible(*entity, *main_entity) { |
There was a problem hiding this comment.
Is removing the entity_pair_is_visible check intentional here? It seems like the check could be kept. iter_to_specialize continues to keep it
There was a problem hiding this comment.
It shouldn’t be needed anymore because mesh_instances_queued_this_frame handles that case. i.e. it existed before to avoid yielding a mesh instance twice, but now we have a more general solution for that, so there’s no need for the check.
There was a problem hiding this comment.
Oh, and also iter_to_specialize doesn’t have mesh_instances_queued_this_frame, so it still needs the check since it has no other way to avoid yielding a mesh instance twice.
| last_frame_view_pending_queues: &'a HashSet<(Entity, MainEntity)>, | ||
| mesh_instances_queued_this_frame: &'a mut MainEntityHashSet, | ||
| ) -> impl Iterator<Item = (&'a Entity, &'a MainEntity)> { | ||
| mesh_instances_queued_this_frame.clear(); |
There was a problem hiding this comment.
nit: mesh_instances_queued_this_frame is maybe misleading name given it is cleared on every iter_to_queue call -- is more of a queued scratch space
There was a problem hiding this comment.
Renamed it to mesh_instances_queued_this_iteration_scratch_space.
|
Waiting on brief replies to @stuartparmenter then I'll merge :) |
ChristopherBiscardi
left a comment
There was a problem hiding this comment.
fixes world_serialization and custom_skinned_mesh as well. thus closes #24990
|
@pcwalton lemme know when you'd like me to merge this and I'll press the button for you :) |
|
Merged on top of #24980. I kept the change from #24980 that avoids panicking or corrupting the bins if we yield the same mesh instance twice, but we now log an error. Additionally, I fixed the crash in PCSS at the source. The problem in that case was that |
An entity whose material is still loading lives in ViewPendingQueues — the sole retry mechanism for specialization and queueing. Both iterators matched those entries against the current visible lists by exact (render entity, main entity) pair (iter_to_specialize), or not at all (iter_to_queue, since bevyengine#24984). The render-entity half of a stored pair can go stale while the main entity stays continuously visible, e.g. when a component insertion changes how the entity is mirrored to the render world. The specialize side then silently evicts the entry, the main entity never re-enters via added_entities (it was never invisible), and once the material loads nothing specializes or queues it: on deferred views the prepass bin item is the entire draw, so the mesh is permanently invisible with no error. Key both pending chains on the main entity and re-resolve the render entity through entity_pair_from_visible_main_entity, restoring the queue/specialize symmetry bevyengine#24984 dropped while keeping its never-bin-twice dedup as the final stage.
…imes. (bevyengine#24984) At the moment, `DirtySpecializations::iter_to_queue` attempts to avoid yielding the same mesh instance multiple times in a complex way. Unfortunately, this logic is incorrect for mesh instances that couldn't be queued because their materials haven't loaded yet (i.e. the contents of `last_frame_view_pending_queues`). The Bistro example tests this code path, as it has many materials that take a significant amount of time to load. Code exists that tries to handle the case in which a mesh is re-binned, and this is why no crash occurred before PR bevyengine#23662. PR bevyengine#23662 made this incorrect code *more* incorrect because it bumps the instance count regardless of whether the mesh instance was already present in the bin; this causes a crash later with an out-of-bounds array access. However, it's always illegal to try to bin a binned entity; the entity must be removed from its bin first. Therefore, moving the instance count bump to the case in which the entity wasn't already in a bin is the wrong fix. This commit contains the correct fix, which is to change `DirtySpecializations::iter_to_queue` so that it doesn't yield duplicate entities. This PR removes the complex logic that failed to account for all cases of duplicate mesh instances in favor of a simple hash set that tracks the mesh instances yielded so far. Additionally, this PR deletes the code path that tried to handle the case in which a mesh that's already binned is binned again and replaces it with a panic in debug mode. I verified that this change fixes the crash in Bistro via `cargo run --features=bevy/debug -p bistro --bin bistro`. Note that on my NVIDIA GeForce RTX 2060, I still get an OOM error on that test case, but that's a separate issue. Fixes bevyengine#24990.
At the moment,
DirtySpecializations::iter_to_queueattempts to avoid yielding the same mesh instance multiple times in a complex way. Unfortunately, this logic is incorrect for mesh instances that couldn't be queued because their materials haven't loaded yet (i.e. the contents oflast_frame_view_pending_queues). The Bistro example tests this code path, as it has many materials that take a significant amount of time to load.Code exists that tries to handle the case in which a mesh is re-binned, and this is why no crash occurred before PR #23662. PR #23662 made this incorrect code more incorrect because it bumps the instance count regardless of whether the mesh instance was already present in the bin; this causes a crash later with an out-of-bounds array access. However, it's always illegal to try to bin a binned entity; the entity must be removed from its bin first. Therefore, moving the instance count bump to the case in which the entity wasn't already in a bin is the wrong fix.
This commit contains the correct fix, which is to change
DirtySpecializations::iter_to_queueso that it doesn't yield duplicate entities. This PR removes the complex logic that failed to account for all cases of duplicate mesh instances in favor of a simple hash set that tracks the mesh instances yielded so far. Additionally, this PR deletes the code path that tried to handle the case in which a mesh that's already binned is binned again and replaces it with a panic in debug mode.I verified that this change fixes the crash in Bistro via
cargo run --features=bevy/debug -p bistro --bin bistro. Note that on my NVIDIA GeForce RTX 2060, I still get an OOM error on that test case, but that's a separate issue.Fixes #24990.