Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optional batch size for bevy_render #5025

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 30 additions & 2 deletions crates/bevy_render/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<usize>,
}

/// 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)]
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -125,6 +149,10 @@ impl Plugin for RenderPlugin {
.init_asset_loader::<ShaderLoader>()
.init_debug_asset_loader::<ShaderLoader>()
.register_type::<Color>();
// 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);
Expand Down
112 changes: 67 additions & 45 deletions crates/bevy_render/src/view/visibility/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod render_layers;

use bevy_math::Vec3A;
use crossbeam_channel::Sender;
pub use render_layers::*;

use bevy_app::{CoreStage, Plugin};
Expand All @@ -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
Expand Down Expand Up @@ -147,7 +149,63 @@ pub fn update_frusta<T: Component + CameraProjection + Send + Sync + 'static>(
}
}

fn check_single_visibility(
view_mask: RenderLayers,
frustum: &Frustum,
sender: &Sender<Entity>,
(
entity,
visibility,
mut computed_visibility,
maybe_entity_mask,
maybe_aabb,
maybe_no_frustum_culling,
maybe_transform,
): (
Entity,
&Visibility,
Mut<ComputedVisibility>,
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<Res<RenderBatchSize>>,
mut view_query: Query<(&mut VisibleEntities, &Frustum, Option<&RenderLayers>), With<Camera>>,
mut visible_entity_query: Query<(
Entity,
Expand All @@ -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();
}
}