Skip to content

Commit

Permalink
New batching mechnaism and sprite optimizations
Browse files Browse the repository at this point in the history
  • Loading branch information
Davier committed Jan 1, 2022
1 parent b88d1c3 commit cd83ed0
Show file tree
Hide file tree
Showing 12 changed files with 419 additions and 363 deletions.
34 changes: 31 additions & 3 deletions crates/bevy_core_pipeline/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ pub use main_pass_2d::*;
pub use main_pass_3d::*;
pub use main_pass_driver::*;

use std::ops::Range;

use bevy_app::{App, Plugin};
use bevy_core::FloatOrd;
use bevy_ecs::prelude::*;
Expand All @@ -23,8 +25,8 @@ use bevy_render::{
color::Color,
render_graph::{EmptyNode, RenderGraph, SlotInfo, SlotType},
render_phase::{
sort_phase_system, CachedPipelinePhaseItem, DrawFunctionId, DrawFunctions, EntityPhaseItem,
PhaseItem, RenderPhase,
batch_phase_system, sort_phase_system, BatchedPhaseItem, CachedPipelinePhaseItem,
DrawFunctionId, DrawFunctions, EntityPhaseItem, PhaseItem, RenderPhase,
},
render_resource::*,
renderer::RenderDevice,
Expand Down Expand Up @@ -84,6 +86,11 @@ pub mod clear_graph {
#[derive(Default)]
pub struct CorePipelinePlugin;

#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)]
pub enum CorePipelineRenderSystems {
SortTransparent2d,
}

impl Plugin for CorePipelinePlugin {
fn build(&self, app: &mut App) {
app.init_resource::<ClearColor>();
Expand All @@ -97,7 +104,16 @@ impl Plugin for CorePipelinePlugin {
.add_system_to_stage(RenderStage::Extract, extract_clear_color)
.add_system_to_stage(RenderStage::Extract, extract_core_pipeline_camera_phases)
.add_system_to_stage(RenderStage::Prepare, prepare_core_views_system)
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<Transparent2d>)
.add_system_to_stage(
RenderStage::PhaseSort,
sort_phase_system::<Transparent2d>
.label(CorePipelineRenderSystems::SortTransparent2d),
)
.add_system_to_stage(
RenderStage::PhaseSort,
batch_phase_system::<Transparent2d>
.after(CorePipelineRenderSystems::SortTransparent2d),
)
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<Opaque3d>)
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<AlphaMask3d>)
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<Transparent3d>);
Expand Down Expand Up @@ -160,6 +176,8 @@ pub struct Transparent2d {
pub entity: Entity,
pub pipeline: CachedPipelineId,
pub draw_function: DrawFunctionId,
/// Range in the index buffer of this item
pub batch_range: Option<Range<u32>>,
}

impl PhaseItem for Transparent2d {
Expand Down Expand Up @@ -190,6 +208,16 @@ impl CachedPipelinePhaseItem for Transparent2d {
}
}

impl BatchedPhaseItem for Transparent2d {
fn batch_range(&self) -> &Option<Range<u32>> {
&self.batch_range
}

fn batch_range_mut(&mut self) -> &mut Option<Range<u32>> {
&mut self.batch_range
}
}

pub struct Opaque3d {
pub distance: f32,
pub pipeline: CachedPipelineId,
Expand Down
6 changes: 5 additions & 1 deletion crates/bevy_pbr/src/render/mesh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use bevy_ecs::{
prelude::*,
system::{lifetimeless::*, SystemParamItem},
};
use bevy_math::Mat4;
use bevy_math::{Mat4, Size};
use bevy_reflect::TypeUuid;
use bevy_render::{
mesh::{GpuBufferInfo, Mesh},
Expand Down Expand Up @@ -328,6 +328,10 @@ impl FromWorld for MeshPipeline {
texture,
texture_view,
sampler,
size: Size::new(
image.texture_descriptor.size.width as f32,
image.texture_descriptor.size.height as f32,
),
}
};
MeshPipeline {
Expand Down
36 changes: 35 additions & 1 deletion crates/bevy_render/src/render_phase/draw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use bevy_ecs::{
};
use bevy_utils::HashMap;
use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use std::{any::TypeId, fmt::Debug, hash::Hash};
use std::{any::TypeId, fmt::Debug, hash::Hash, ops::Range};

/// A draw function which is used to draw a specific [`PhaseItem`].
///
Expand Down Expand Up @@ -166,6 +166,40 @@ pub trait CachedPipelinePhaseItem: PhaseItem {
fn cached_pipeline(&self) -> CachedPipelineId;
}

pub trait BatchedPhaseItem: EntityPhaseItem {
/// Range in the index buffer of this item
fn batch_range(&self) -> &Option<Range<u32>>;

/// Range in the index buffer of this item
fn batch_range_mut(&mut self) -> &mut Option<Range<u32>>;

/// Batches another item within this item if they are compatible.
/// Items can be batched together if they have the same entity, and consecutive ranges.
/// If batching is successful, the `other` item should be discarded from the render pass.
#[inline]
fn add_to_batch(&mut self, other: &Self) -> BatchResult {
let self_entity = self.entity();
if let (Some(self_batch_range), Some(other_batch_range)) = (
self.batch_range_mut().as_mut(),
other.batch_range().as_ref(),
) {
if self_entity == other.entity() && self_batch_range.end == other_batch_range.start {
// If the items are compatible, join their range into `self`
self_batch_range.end = other_batch_range.end;
return BatchResult::Success;
}
}
BatchResult::IncompatibleItems
}
}

pub enum BatchResult {
/// The `other` item was batched into `self`
Success,
/// `self` and `other` cannot be batched together
IncompatibleItems,
}

impl<P: EntityPhaseItem, E: EntityRenderCommand> RenderCommand<P> for E {
type Param = E::Param;

Expand Down
35 changes: 35 additions & 0 deletions crates/bevy_render/src/render_phase/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,44 @@ impl<I: PhaseItem> RenderPhase<I> {
}
}

impl<I: BatchedPhaseItem> RenderPhase<I> {
/// Batches the compatible [`BatchedPhaseItem`]s of this render phase
pub fn batch(&mut self) {
// TODO: this could be done in-place
let mut items = std::mem::take(&mut self.items);
let mut items = items.drain(..);

self.items.reserve(items.len());

// Start the first batch from the first item
if let Some(mut current_batch) = items.next() {
// Batch following items until we find an incompatible item
for next_item in items {
if matches!(
current_batch.add_to_batch(&next_item),
BatchResult::IncompatibleItems
) {
// Store the completed batch, and start a new one from the incompatible item
self.items.push(current_batch);
current_batch = next_item;
}
}
// Store the last batch
self.items.push(current_batch);
}
}
}

/// This system sorts all [`RenderPhases`](RenderPhase) for the [`PhaseItem`] type.
pub fn sort_phase_system<I: PhaseItem>(mut render_phases: Query<&mut RenderPhase<I>>) {
for mut phase in render_phases.iter_mut() {
phase.sort();
}
}

/// This batches the [`PhaseItem`]s of a [`RenderPhases`](RenderPhase).
pub fn batch_phase_system<I: BatchedPhaseItem>(mut render_phases: Query<&mut RenderPhase<I>>) {
for mut phase in render_phases.iter_mut() {
phase.batch();
}
}
9 changes: 8 additions & 1 deletion crates/bevy_render/src/texture/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::{
};
use bevy_asset::HandleUntyped;
use bevy_ecs::system::{lifetimeless::SRes, SystemParamItem};
use bevy_math::Size;
use bevy_reflect::TypeUuid;
use thiserror::Error;
use wgpu::{
Expand Down Expand Up @@ -373,12 +374,13 @@ impl TextureFormatPixelInfo for TextureFormat {
}

/// The GPU-representation of an [`Image`].
/// Consists of the [`Texture`], its [`TextureView`] and the corresponding [`Sampler`].
/// Consists of the [`Texture`], its [`TextureView`] and the corresponding [`Sampler`], and the texture's [`Size`].
#[derive(Debug, Clone)]
pub struct GpuImage {
pub texture: Texture,
pub texture_view: TextureView,
pub sampler: Sampler,
pub size: Size,
}

impl RenderAsset for Image {
Expand Down Expand Up @@ -426,10 +428,15 @@ impl RenderAsset for Image {
);

let texture_view = texture.create_view(&TextureViewDescriptor::default());
let size = Size::new(
image.texture_descriptor.size.width as f32,
image.texture_descriptor.size.height as f32,
);
Ok(GpuImage {
texture,
texture_view,
sampler,
size,
})
}
}
2 changes: 1 addition & 1 deletion crates/bevy_render/src/view/visibility/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ impl Default for Visibility {
}

/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
#[derive(Component, Clone, Reflect)]
#[derive(Component, Clone, Reflect, Debug)]
#[reflect(Component)]
pub struct ComputedVisibility {
pub is_visible: bool,
Expand Down
7 changes: 1 addition & 6 deletions crates/bevy_sprite/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ pub const SPRITE_SHADER_HANDLE: HandleUntyped =
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)]
pub enum SpriteSystem {
ExtractSprites,
PrepareSprites,
}

impl Plugin for SpritePlugin {
Expand All @@ -66,18 +65,14 @@ impl Plugin for SpritePlugin {
.init_resource::<SpritePipeline>()
.init_resource::<SpecializedPipelines<SpritePipeline>>()
.init_resource::<SpriteMeta>()
.init_resource::<Extracted2dItems>()
.init_resource::<ExtractedSprites>()
.init_resource::<SpriteAssetEvents>()
.add_render_command::<Transparent2d, DrawSprite>()
.add_system_to_stage(
RenderStage::Extract,
render::extract_sprites.label(SpriteSystem::ExtractSprites),
)
.add_system_to_stage(RenderStage::Extract, render::extract_sprite_events)
.add_system_to_stage(
RenderStage::Prepare,
render::prepare_sprites.label(SpriteSystem::PrepareSprites),
)
.add_system_to_stage(RenderStage::Queue, queue_sprites);
}
}
21 changes: 9 additions & 12 deletions crates/bevy_sprite/src/mesh2d/material.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use bevy_render::{
SpecializedPipeline, SpecializedPipelines,
},
renderer::RenderDevice,
view::{ComputedVisibility, ExtractedView, Msaa, Visibility, VisibleEntities},
view::{ComputedVisibility, Msaa, Visibility, VisibleEntities},
RenderApp, RenderStage,
};
use bevy_transform::components::{GlobalTransform, Transform};
Expand Down Expand Up @@ -265,20 +265,17 @@ pub fn queue_material2d_meshes<M: SpecializedMaterial2d>(
render_meshes: Res<RenderAssets<Mesh>>,
render_materials: Res<RenderAssets<M>>,
material2d_meshes: Query<(&Handle<M>, &Mesh2dHandle, &Mesh2dUniform)>,
mut views: Query<(
&ExtractedView,
&VisibleEntities,
&mut RenderPhase<Transparent2d>,
)>,
mut views: Query<(&VisibleEntities, &mut RenderPhase<Transparent2d>)>,
) {
for (view, visible_entities, mut transparent_phase) in views.iter_mut() {
if material2d_meshes.is_empty() {
return;
}
for (visible_entities, mut transparent_phase) in views.iter_mut() {
let draw_transparent_pbr = transparent_draw_functions
.read()
.get_id::<DrawMaterial2d<M>>()
.unwrap();

let inverse_view_matrix = view.transform.compute_matrix().inverse();
let inverse_view_row_2 = inverse_view_matrix.row(2);
let mesh_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples);

for visible_entity in &visible_entities.entities {
Expand All @@ -302,9 +299,7 @@ pub fn queue_material2d_meshes<M: SpecializedMaterial2d>(
(mesh2d_key, specialized_key),
);

// NOTE: row 2 of the inverse view matrix dotted with column 3 of the model matrix
// gives the z component of translation of the mesh in view space
let mesh_z = inverse_view_row_2.dot(mesh2d_uniform.transform.col(3));
let mesh_z = mesh2d_uniform.transform.w_axis.z;
transparent_phase.add(Transparent2d {
entity: *visible_entity,
draw_function: draw_transparent_pbr,
Expand All @@ -314,6 +309,8 @@ pub fn queue_material2d_meshes<M: SpecializedMaterial2d>(
// -z in front of the camera, the largest distance is -far with values increasing toward the
// camera. As such we can just use mesh_z as the distance
sort_key: FloatOrd(mesh_z),
// This material is not batched
batch_range: None,
});
}
}
Expand Down
21 changes: 7 additions & 14 deletions crates/bevy_sprite/src/mesh2d/mesh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use bevy_ecs::{
prelude::*,
system::{lifetimeless::*, SystemParamItem},
};
use bevy_math::Mat4;
use bevy_math::{Mat4, Size};
use bevy_reflect::TypeUuid;
use bevy_render::{
mesh::{GpuBufferInfo, Mesh},
Expand All @@ -15,12 +15,10 @@ use bevy_render::{
renderer::{RenderDevice, RenderQueue},
texture::{BevyDefault, GpuImage, Image, TextureFormatPixelInfo},
view::{ComputedVisibility, ExtractedView, ViewUniform, ViewUniformOffset, ViewUniforms},
RenderApp, RenderStage, RenderWorld,
RenderApp, RenderStage,
};
use bevy_transform::components::GlobalTransform;

use crate::{Extracted2dItem, Extracted2dItems, SpriteSystem};

#[derive(Default, Clone, Component)]
pub struct Mesh2dHandle(pub Handle<Mesh>);

Expand Down Expand Up @@ -63,10 +61,7 @@ impl Plugin for Mesh2dRenderPlugin {
app.sub_app_mut(RenderApp)
.init_resource::<Mesh2dPipeline>()
.init_resource::<SpecializedPipelines<Mesh2dPipeline>>()
.add_system_to_stage(
RenderStage::Extract,
extract_mesh2d.after(SpriteSystem::ExtractSprites),
)
.add_system_to_stage(RenderStage::Extract, extract_mesh2d)
.add_system_to_stage(RenderStage::Queue, queue_mesh2d_bind_group)
.add_system_to_stage(RenderStage::Queue, queue_mesh2d_view_bind_groups);
}
Expand All @@ -90,19 +85,16 @@ bitflags::bitflags! {
}

pub fn extract_mesh2d(
mut render_world: ResMut<RenderWorld>,
mut commands: Commands,
mut previous_len: Local<usize>,
query: Query<(Entity, &ComputedVisibility, &GlobalTransform, &Mesh2dHandle)>,
) {
let mut extracted_sprites = render_world.get_resource_mut::<Extracted2dItems>().unwrap();
let mut values = Vec::with_capacity(*previous_len);
for (entity, computed_visibility, transform, handle) in query.iter() {
if !computed_visibility.is_visible {
continue;
}
let transform = transform.compute_matrix();
let z = transform.col(3).z;
values.push((
entity,
(
Expand All @@ -114,9 +106,6 @@ pub fn extract_mesh2d(
},
),
));
// Break sprite batches
// FIXME: this doesn't account for camera position
extracted_sprites.items.push(Extracted2dItem::Other { z });
}
*previous_len = values.len();
commands.insert_or_spawn_batch(values);
Expand Down Expand Up @@ -202,6 +191,10 @@ impl FromWorld for Mesh2dPipeline {
texture,
texture_view,
sampler,
size: Size::new(
image.texture_descriptor.size.width as f32,
image.texture_descriptor.size.height as f32,
),
}
};
Mesh2dPipeline {
Expand Down
Loading

0 comments on commit cd83ed0

Please sign in to comment.