diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 0caa1a75bc29c..1d4024f1fcdd8 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -47,8 +47,16 @@ use bevy_utils::tracing::debug; use std::ops::{Deref, DerefMut}; /// Contains the default Bevy rendering backend based on wgpu. -#[derive(Default)] -pub struct RenderPlugin; +pub struct RenderPlugin { + /// If set, a [`RenderBatchSize`] will be added as a `Resource`, + /// allowing batched parallel execution of rendering system. + batch_size: Option, +} + +/// Optional `Resource` enabling batched parallel execution of rendering system. +/// Improves rendering performance on scenes with a large number of entities. +#[derive(Copy, Clone)] +pub struct RenderBatchSize(pub usize); /// The labels of the default App rendering stages. #[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)] @@ -82,6 +90,22 @@ pub enum RenderStage { #[derive(Default)] pub struct RenderWorld(World); +impl Default for RenderPlugin { + fn default() -> Self { + Self { + batch_size: Some(1024), + } + } +} + +impl Deref for RenderBatchSize { + type Target = usize; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + impl Deref for RenderWorld { type Target = World; @@ -125,6 +149,10 @@ impl Plugin for RenderPlugin { .init_asset_loader::() .init_debug_asset_loader::() .register_type::(); + // Enables batched parallel execution of rendering + if let Some(batch_size) = self.batch_size { + app.insert_resource(RenderBatchSize(batch_size)); + } if let Some(backends) = options.backends { let instance = wgpu::Instance::new(backends); diff --git a/crates/bevy_render/src/view/visibility/mod.rs b/crates/bevy_render/src/view/visibility/mod.rs index 4f8a456486a19..da8e751ebbbb0 100644 --- a/crates/bevy_render/src/view/visibility/mod.rs +++ b/crates/bevy_render/src/view/visibility/mod.rs @@ -1,6 +1,7 @@ mod render_layers; use bevy_math::Vec3A; +use crossbeam_channel::Sender; pub use render_layers::*; use bevy_app::{CoreStage, Plugin}; @@ -15,6 +16,7 @@ use crate::{ camera::{Camera, CameraProjection, OrthographicProjection, PerspectiveProjection, Projection}, mesh::Mesh, primitives::{Aabb, Frustum, Sphere}, + RenderBatchSize, }; /// User indication of whether an entity is visible @@ -147,7 +149,63 @@ pub fn update_frusta( } } +fn check_single_visibility( + view_mask: RenderLayers, + frustum: &Frustum, + sender: &Sender, + ( + entity, + visibility, + mut computed_visibility, + maybe_entity_mask, + maybe_aabb, + maybe_no_frustum_culling, + maybe_transform, + ): ( + Entity, + &Visibility, + Mut, + Option<&RenderLayers>, + Option<&Aabb>, + Option<&NoFrustumCulling>, + Option<&GlobalTransform>, + ), +) { + // Reset visibility + computed_visibility.is_visible = false; + + if !visibility.is_visible { + return; + } + let entity_mask = maybe_entity_mask.copied().unwrap_or_default(); + if !view_mask.intersects(&entity_mask) { + return; + } + + // 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; + } + } + computed_visibility.is_visible = true; + sender.send(entity).ok(); +} + pub fn check_visibility( + batch_size: Option>, mut view_query: Query<(&mut VisibleEntities, &Frustum, Option<&RenderLayers>), With>, mut visible_entity_query: Query<( Entity, @@ -163,51 +221,15 @@ pub fn check_visibility( let view_mask = maybe_view_mask.copied().unwrap_or_default(); 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; - } - let entity_mask = maybe_entity_mask.copied().unwrap_or_default(); - if !view_mask.intersects(&entity_mask) { - return; - } - - // 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; - } - } - - computed_visibility.is_visible = true; - visible_entity_sender.send(entity).ok(); - }, - ); + if let Some(batch) = &batch_size { + visible_entity_query.par_for_each_mut(batch.0, |elements| { + check_single_visibility(view_mask, frustum, &visible_entity_sender, elements); + }); + } else { + for elements in visible_entity_query.iter_mut() { + check_single_visibility(view_mask, frustum, &visible_entity_sender, elements); + } + } visible_entities.entities = visible_entity_receiver.try_iter().collect(); } }