Parallelize bevy 0.16-rc bottlenecks (#18632)

# Objective

- Found stuttering and performance degradation while updating big_space
stress tests.

## Solution

- Identify and fix slow spots using tracy. Patch to verify fixes.

## Testing

- Tracy
- Before: 

![image](https://github.com/user-attachments/assets/ab7f440d-88c1-4ad9-9ad9-dca127c9421f)
- prev_gt parallelization and mutating instead of component insertion: 

![image](https://github.com/user-attachments/assets/9279a663-c0ba-4529-b709-d0f81f2a1d8b)
- parallelize visibility ranges and mesh specialization

![image](https://github.com/user-attachments/assets/25b70e7c-5d30-48ab-9bb2-79211d4d672f)

---------

Co-authored-by: Zachary Harrold <zac@harrold.com.au>
This commit is contained in:
Aevyrie 2025-03-31 11:32:45 -07:00 committed by François Mockers
parent d5c5de20b1
commit cba6698033
4 changed files with 55 additions and 38 deletions

View File

@ -52,6 +52,7 @@ use bevy_render::{
}; };
use bevy_render::{mesh::allocator::MeshAllocator, sync_world::MainEntityHashMap}; use bevy_render::{mesh::allocator::MeshAllocator, sync_world::MainEntityHashMap};
use bevy_render::{texture::FallbackImage, view::RenderVisibleEntities}; use bevy_render::{texture::FallbackImage, view::RenderVisibleEntities};
use bevy_utils::Parallel;
use core::{hash::Hash, marker::PhantomData}; use core::{hash::Hash, marker::PhantomData};
use tracing::error; use tracing::error;
@ -832,14 +833,18 @@ pub fn check_entities_needing_specialization<M>(
With<MeshMaterial3d<M>>, With<MeshMaterial3d<M>>,
), ),
>, >,
mut par_local: Local<Parallel<Vec<Entity>>>,
mut entities_needing_specialization: ResMut<EntitiesNeedingSpecialization<M>>, mut entities_needing_specialization: ResMut<EntitiesNeedingSpecialization<M>>,
) where ) where
M: Material, M: Material,
{ {
entities_needing_specialization.clear(); entities_needing_specialization.clear();
for entity in &needs_specialization {
entities_needing_specialization.push(entity); needs_specialization
} .par_iter()
.for_each(|entity| par_local.borrow_local_mut().push(entity));
par_local.drain_into(&mut entities_needing_specialization);
} }
pub fn specialize_material_meshes<M: Material>( pub fn specialize_material_meshes<M: Material>(

View File

@ -268,20 +268,22 @@ type PreviousMeshFilter = Or<(With<Mesh3d>, With<MeshletMesh3d>)>;
pub fn update_mesh_previous_global_transforms( pub fn update_mesh_previous_global_transforms(
mut commands: Commands, mut commands: Commands,
views: Query<&Camera, Or<(With<Camera3d>, With<ShadowView>)>>, views: Query<&Camera, Or<(With<Camera3d>, With<ShadowView>)>>,
meshes: Query<(Entity, &GlobalTransform, Option<&PreviousGlobalTransform>), PreviousMeshFilter>, new_meshes: Query<
(Entity, &GlobalTransform),
(PreviousMeshFilter, Without<PreviousGlobalTransform>),
>,
mut meshes: Query<(&GlobalTransform, &mut PreviousGlobalTransform), PreviousMeshFilter>,
) { ) {
let should_run = views.iter().any(|camera| camera.is_active); let should_run = views.iter().any(|camera| camera.is_active);
if should_run { if should_run {
for (entity, transform, old_previous_transform) in &meshes { for (entity, transform) in &new_meshes {
let new_previous_transform = PreviousGlobalTransform(transform.affine()); let new_previous_transform = PreviousGlobalTransform(transform.affine());
// Make sure not to trigger change detection on
// `PreviousGlobalTransform` if the previous transform hasn't
// changed.
if old_previous_transform != Some(&new_previous_transform) {
commands.entity(entity).try_insert(new_previous_transform); commands.entity(entity).try_insert(new_previous_transform);
} }
} meshes.par_iter_mut().for_each(|(transform, mut previous)| {
previous.set_if_neq(PreviousGlobalTransform(transform.affine()));
});
} }
} }

View File

@ -15,13 +15,13 @@ use bevy_ecs::{
removal_detection::RemovedComponents, removal_detection::RemovedComponents,
resource::Resource, resource::Resource,
schedule::IntoScheduleConfigs as _, schedule::IntoScheduleConfigs as _,
system::{Query, Res, ResMut}, system::{Local, Query, Res, ResMut},
}; };
use bevy_math::{vec4, FloatOrd, Vec4}; use bevy_math::{vec4, FloatOrd, Vec4};
use bevy_platform_support::collections::HashMap; use bevy_platform_support::collections::HashMap;
use bevy_reflect::Reflect; use bevy_reflect::Reflect;
use bevy_transform::components::GlobalTransform; use bevy_transform::components::GlobalTransform;
use bevy_utils::prelude::default; use bevy_utils::{prelude::default, Parallel};
use nonmax::NonMaxU16; use nonmax::NonMaxU16;
use wgpu::{BufferBindingType, BufferUsages}; use wgpu::{BufferBindingType, BufferUsages};
@ -385,7 +385,8 @@ impl VisibleEntityRanges {
pub fn check_visibility_ranges( pub fn check_visibility_ranges(
mut visible_entity_ranges: ResMut<VisibleEntityRanges>, mut visible_entity_ranges: ResMut<VisibleEntityRanges>,
view_query: Query<(Entity, &GlobalTransform), With<Camera>>, view_query: Query<(Entity, &GlobalTransform), With<Camera>>,
mut entity_query: Query<(Entity, &GlobalTransform, Option<&Aabb>, &VisibilityRange)>, mut par_local: Local<Parallel<Vec<(Entity, u32)>>>,
entity_query: Query<(Entity, &GlobalTransform, Option<&Aabb>, &VisibilityRange)>,
) { ) {
visible_entity_ranges.clear(); visible_entity_ranges.clear();
@ -404,7 +405,8 @@ pub fn check_visibility_ranges(
// Check each entity/view pair. Only consider entities with // Check each entity/view pair. Only consider entities with
// [`VisibilityRange`] components. // [`VisibilityRange`] components.
for (entity, entity_transform, maybe_model_aabb, visibility_range) in entity_query.iter_mut() { entity_query.par_iter().for_each(
|(entity, entity_transform, maybe_model_aabb, visibility_range)| {
let mut visibility = 0; let mut visibility = 0;
for (view_index, &(_, view_position)) in views.iter().enumerate() { for (view_index, &(_, view_position)) in views.iter().enumerate() {
// If instructed to use the AABB and the model has one, use its // If instructed to use the AABB and the model has one, use its
@ -425,9 +427,12 @@ pub fn check_visibility_ranges(
// Invisible entities have no entry at all in the hash map. This speeds // Invisible entities have no entry at all in the hash map. This speeds
// up checks slightly in this common case. // up checks slightly in this common case.
if visibility != 0 { if visibility != 0 {
visible_entity_ranges.entities.insert(entity, visibility); par_local.borrow_local_mut().push((entity, visibility));
}
} }
},
);
visible_entity_ranges.entities.extend(par_local.drain());
} }
/// Extracts all [`VisibilityRange`] components from the main world to the /// Extracts all [`VisibilityRange`] components from the main world to the

View File

@ -45,6 +45,7 @@ use bevy_render::{
view::{ExtractedView, ViewVisibility}, view::{ExtractedView, ViewVisibility},
Extract, ExtractSchedule, Render, RenderApp, RenderSet, Extract, ExtractSchedule, Render, RenderApp, RenderSet,
}; };
use bevy_utils::Parallel;
use core::{hash::Hash, marker::PhantomData}; use core::{hash::Hash, marker::PhantomData};
use derive_more::derive::From; use derive_more::derive::From;
use tracing::error; use tracing::error;
@ -660,14 +661,18 @@ pub fn check_entities_needing_specialization<M>(
With<MeshMaterial2d<M>>, With<MeshMaterial2d<M>>,
), ),
>, >,
mut par_local: Local<Parallel<Vec<Entity>>>,
mut entities_needing_specialization: ResMut<EntitiesNeedingSpecialization<M>>, mut entities_needing_specialization: ResMut<EntitiesNeedingSpecialization<M>>,
) where ) where
M: Material2d, M: Material2d,
{ {
entities_needing_specialization.clear(); entities_needing_specialization.clear();
for entity in &needs_specialization {
entities_needing_specialization.push(entity); needs_specialization
} .par_iter()
.for_each(|entity| par_local.borrow_local_mut().push(entity));
par_local.drain_into(&mut entities_needing_specialization);
} }
pub fn specialize_material2d_meshes<M: Material2d>( pub fn specialize_material2d_meshes<M: Material2d>(