Skip to content

Commit

Permalink
Parallel Frustum Culling (#4489)
Browse files Browse the repository at this point in the history
# Objective

Working with a large number of entities with `Aabbs`, rendered with an instanced shader, I found the bottleneck became the frustum culling system. The goal of this PR is to significantly improve culling performance without any major changes. We should consider constructing a BVH for more substantial improvements.

## Solution

- Convert the inner entity query to a parallel iterator with `par_for_each_mut` using a batch size of 1,024. 
- This outperforms single threaded culling when there are more than 1,000 entities. 
  - Below this they are approximately equal, with <= 10 microseconds of multithreading overhead.
  - Above this, the multithreaded version is significantly faster, scaling linearly with core count.
- In my million-entity-workload, this PR improves my framerate by 200% - 300%.

## log-log of `check_visibility` time vs. entities for single/multithreaded
![image](https://user-images.githubusercontent.com/2632925/163709007-7eab4437-e9f9-4c06-bac0-250073885110.png)

---

## Changelog

Frustum culling is now run with a parallel query. When culling more than a thousand entities, this is faster than the previous method, scaling proportionally with the number of available cores.
  • Loading branch information
aevyrie committed Jun 13, 2022
1 parent b7d784d commit 5504942
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 58 deletions.
1 change: 1 addition & 0 deletions crates/bevy_render/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ once_cell = "1.4.1" # TODO: replace once_cell with std equivalent if/when this l
downcast-rs = "1.2.0"
thiserror = "1.0"
futures-lite = "1.4.0"
crossbeam-channel = "0.5.0"
anyhow = "1.0"
hex = "0.4.2"
hexasphere = "7.0.0"
Expand Down
110 changes: 52 additions & 58 deletions crates/bevy_render/src/view/visibility/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,71 +149,65 @@ pub fn update_frusta<T: Component + CameraProjection + Send + Sync + 'static>(

pub fn check_visibility(
mut view_query: Query<(&mut VisibleEntities, &Frustum, Option<&RenderLayers>), With<Camera>>,
mut visible_entity_query: ParamSet<(
Query<&mut ComputedVisibility>,
Query<(
Entity,
&Visibility,
&mut ComputedVisibility,
Option<&RenderLayers>,
Option<&Aabb>,
Option<&NoFrustumCulling>,
Option<&GlobalTransform>,
)>,
mut visible_entity_query: Query<(
Entity,
&Visibility,
&mut ComputedVisibility,
Option<&RenderLayers>,
Option<&Aabb>,
Option<&NoFrustumCulling>,
Option<&GlobalTransform>,
)>,
) {
// Reset the computed visibility to false
for mut computed_visibility in visible_entity_query.p0().iter_mut() {
computed_visibility.is_visible = false;
}

for (mut visible_entities, frustum, maybe_view_mask) in view_query.iter_mut() {
visible_entities.entities.clear();
let view_mask = maybe_view_mask.copied().unwrap_or_default();

for (
entity,
visibility,
mut computed_visibility,
maybe_entity_mask,
maybe_aabb,
maybe_no_frustum_culling,
maybe_transform,
) in visible_entity_query.p1().iter_mut()
{
if !visibility.is_visible {
continue;
}

let entity_mask = maybe_entity_mask.copied().unwrap_or_default();
if !view_mask.intersects(&entity_mask) {
continue;
}

// If we have an aabb and transform, do frustum culling
if let (Some(model_aabb), None, Some(transform)) =
(maybe_aabb, maybe_no_frustum_culling, maybe_transform)
{
let model = transform.compute_matrix();
let model_sphere = Sphere {
center: model.transform_point3a(model_aabb.center),
radius: (Vec3A::from(transform.scale) * model_aabb.half_extents).length(),
};
// Do quick sphere-based frustum culling
if !frustum.intersects_sphere(&model_sphere, false) {
continue;
let (visible_entity_sender, visible_entity_receiver) = crossbeam_channel::unbounded();

visible_entity_query.par_for_each_mut(
1024,
|(
entity,
visibility,
mut computed_visibility,
maybe_entity_mask,
maybe_aabb,
maybe_no_frustum_culling,
maybe_transform,
)| {
// Reset visibility
computed_visibility.is_visible = false;

if !visibility.is_visible {
return;
}
// If we have an aabb, do aabb-based frustum culling
if !frustum.intersects_obb(model_aabb, &model, false) {
continue;
let entity_mask = maybe_entity_mask.copied().unwrap_or_default();
if !view_mask.intersects(&entity_mask) {
return;
}
}

computed_visibility.is_visible = true;
visible_entities.entities.push(entity);
}
// If we have an aabb and transform, do frustum culling
if let (Some(model_aabb), None, Some(transform)) =
(maybe_aabb, maybe_no_frustum_culling, maybe_transform)
{
let model = transform.compute_matrix();
let model_sphere = Sphere {
center: model.transform_point3a(model_aabb.center),
radius: (Vec3A::from(transform.scale) * model_aabb.half_extents).length(),
};
// Do quick sphere-based frustum culling
if !frustum.intersects_sphere(&model_sphere, false) {
return;
}
// If we have an aabb, do aabb-based frustum culling
if !frustum.intersects_obb(model_aabb, &model, false) {
return;
}
}

// TODO: check for big changes in visible entities len() vs capacity() (ex: 2x) and resize
// to prevent holding unneeded memory
computed_visibility.is_visible = true;
visible_entity_sender.send(entity).ok();
},
);
visible_entities.entities = visible_entity_receiver.try_iter().collect();
}
}

0 comments on commit 5504942

Please sign in to comment.