Parallel Frustum Culling (#4489)
# 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  --- ## 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.
This commit is contained in:
		
							parent
							
								
									c6222f1acc
								
							
						
					
					
						commit
						915fa69b66
					
				| @ -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" | downcast-rs = "1.2.0" | ||||||
| thiserror = "1.0" | thiserror = "1.0" | ||||||
| futures-lite = "1.4.0" | futures-lite = "1.4.0" | ||||||
|  | crossbeam-channel = "0.5.0" | ||||||
| anyhow = "1.0" | anyhow = "1.0" | ||||||
| hex = "0.4.2" | hex = "0.4.2" | ||||||
| hexasphere = "7.0.0" | hexasphere = "7.0.0" | ||||||
|  | |||||||
| @ -149,9 +149,7 @@ pub fn update_frusta<T: Component + CameraProjection + Send + Sync + 'static>( | |||||||
| 
 | 
 | ||||||
| pub fn check_visibility( | pub fn check_visibility( | ||||||
|     mut view_query: Query<(&mut VisibleEntities, &Frustum, Option<&RenderLayers>), With<Camera>>, |     mut view_query: Query<(&mut VisibleEntities, &Frustum, Option<&RenderLayers>), With<Camera>>, | ||||||
|     mut visible_entity_query: ParamSet<( |     mut visible_entity_query: Query<( | ||||||
|         Query<&mut ComputedVisibility>, |  | ||||||
|         Query<( |  | ||||||
|         Entity, |         Entity, | ||||||
|         &Visibility, |         &Visibility, | ||||||
|         &mut ComputedVisibility, |         &mut ComputedVisibility, | ||||||
| @ -160,18 +158,14 @@ pub fn check_visibility( | |||||||
|         Option<&NoFrustumCulling>, |         Option<&NoFrustumCulling>, | ||||||
|         Option<&GlobalTransform>, |         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() { |     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(); |         let view_mask = maybe_view_mask.copied().unwrap_or_default(); | ||||||
|  |         let (visible_entity_sender, visible_entity_receiver) = crossbeam_channel::unbounded(); | ||||||
| 
 | 
 | ||||||
|         for ( |         visible_entity_query.par_for_each_mut( | ||||||
|  |             1024, | ||||||
|  |             |( | ||||||
|                 entity, |                 entity, | ||||||
|                 visibility, |                 visibility, | ||||||
|                 mut computed_visibility, |                 mut computed_visibility, | ||||||
| @ -179,15 +173,16 @@ pub fn check_visibility( | |||||||
|                 maybe_aabb, |                 maybe_aabb, | ||||||
|                 maybe_no_frustum_culling, |                 maybe_no_frustum_culling, | ||||||
|                 maybe_transform, |                 maybe_transform, | ||||||
|         ) in visible_entity_query.p1().iter_mut() |             )| { | ||||||
|         { |                 // Reset visibility
 | ||||||
|             if !visibility.is_visible { |                 computed_visibility.is_visible = false; | ||||||
|                 continue; |  | ||||||
|             } |  | ||||||
| 
 | 
 | ||||||
|  |                 if !visibility.is_visible { | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|                 let entity_mask = maybe_entity_mask.copied().unwrap_or_default(); |                 let entity_mask = maybe_entity_mask.copied().unwrap_or_default(); | ||||||
|                 if !view_mask.intersects(&entity_mask) { |                 if !view_mask.intersects(&entity_mask) { | ||||||
|                 continue; |                     return; | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 // If we have an aabb and transform, do frustum culling
 |                 // If we have an aabb and transform, do frustum culling
 | ||||||
| @ -201,19 +196,18 @@ pub fn check_visibility( | |||||||
|                     }; |                     }; | ||||||
|                     // Do quick sphere-based frustum culling
 |                     // Do quick sphere-based frustum culling
 | ||||||
|                     if !frustum.intersects_sphere(&model_sphere, false) { |                     if !frustum.intersects_sphere(&model_sphere, false) { | ||||||
|                     continue; |                         return; | ||||||
|                     } |                     } | ||||||
|                     // If we have an aabb, do aabb-based frustum culling
 |                     // If we have an aabb, do aabb-based frustum culling
 | ||||||
|                     if !frustum.intersects_obb(model_aabb, &model, false) { |                     if !frustum.intersects_obb(model_aabb, &model, false) { | ||||||
|                     continue; |                         return; | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 computed_visibility.is_visible = true; |                 computed_visibility.is_visible = true; | ||||||
|             visible_entities.entities.push(entity); |                 visible_entity_sender.send(entity).ok(); | ||||||
|         } |             }, | ||||||
| 
 |         ); | ||||||
|         // TODO: check for big changes in visible entities len() vs capacity() (ex: 2x) and resize
 |         visible_entities.entities = visible_entity_receiver.try_iter().collect(); | ||||||
|         // to prevent holding unneeded memory
 |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Aevyrie
						Aevyrie