Skip to content

Commit cd83ed0

Browse files
committed
New batching mechnaism and sprite optimizations
1 parent b88d1c3 commit cd83ed0

File tree

12 files changed

+419
-363
lines changed

12 files changed

+419
-363
lines changed

crates/bevy_core_pipeline/src/lib.rs

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ pub use main_pass_2d::*;
1515
pub use main_pass_3d::*;
1616
pub use main_pass_driver::*;
1717

18+
use std::ops::Range;
19+
1820
use bevy_app::{App, Plugin};
1921
use bevy_core::FloatOrd;
2022
use bevy_ecs::prelude::*;
@@ -23,8 +25,8 @@ use bevy_render::{
2325
color::Color,
2426
render_graph::{EmptyNode, RenderGraph, SlotInfo, SlotType},
2527
render_phase::{
26-
sort_phase_system, CachedPipelinePhaseItem, DrawFunctionId, DrawFunctions, EntityPhaseItem,
27-
PhaseItem, RenderPhase,
28+
batch_phase_system, sort_phase_system, BatchedPhaseItem, CachedPipelinePhaseItem,
29+
DrawFunctionId, DrawFunctions, EntityPhaseItem, PhaseItem, RenderPhase,
2830
},
2931
render_resource::*,
3032
renderer::RenderDevice,
@@ -84,6 +86,11 @@ pub mod clear_graph {
8486
#[derive(Default)]
8587
pub struct CorePipelinePlugin;
8688

89+
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)]
90+
pub enum CorePipelineRenderSystems {
91+
SortTransparent2d,
92+
}
93+
8794
impl Plugin for CorePipelinePlugin {
8895
fn build(&self, app: &mut App) {
8996
app.init_resource::<ClearColor>();
@@ -97,7 +104,16 @@ impl Plugin for CorePipelinePlugin {
97104
.add_system_to_stage(RenderStage::Extract, extract_clear_color)
98105
.add_system_to_stage(RenderStage::Extract, extract_core_pipeline_camera_phases)
99106
.add_system_to_stage(RenderStage::Prepare, prepare_core_views_system)
100-
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<Transparent2d>)
107+
.add_system_to_stage(
108+
RenderStage::PhaseSort,
109+
sort_phase_system::<Transparent2d>
110+
.label(CorePipelineRenderSystems::SortTransparent2d),
111+
)
112+
.add_system_to_stage(
113+
RenderStage::PhaseSort,
114+
batch_phase_system::<Transparent2d>
115+
.after(CorePipelineRenderSystems::SortTransparent2d),
116+
)
101117
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<Opaque3d>)
102118
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<AlphaMask3d>)
103119
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<Transparent3d>);
@@ -160,6 +176,8 @@ pub struct Transparent2d {
160176
pub entity: Entity,
161177
pub pipeline: CachedPipelineId,
162178
pub draw_function: DrawFunctionId,
179+
/// Range in the index buffer of this item
180+
pub batch_range: Option<Range<u32>>,
163181
}
164182

165183
impl PhaseItem for Transparent2d {
@@ -190,6 +208,16 @@ impl CachedPipelinePhaseItem for Transparent2d {
190208
}
191209
}
192210

211+
impl BatchedPhaseItem for Transparent2d {
212+
fn batch_range(&self) -> &Option<Range<u32>> {
213+
&self.batch_range
214+
}
215+
216+
fn batch_range_mut(&mut self) -> &mut Option<Range<u32>> {
217+
&mut self.batch_range
218+
}
219+
}
220+
193221
pub struct Opaque3d {
194222
pub distance: f32,
195223
pub pipeline: CachedPipelineId,

crates/bevy_pbr/src/render/mesh.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use bevy_ecs::{
88
prelude::*,
99
system::{lifetimeless::*, SystemParamItem},
1010
};
11-
use bevy_math::Mat4;
11+
use bevy_math::{Mat4, Size};
1212
use bevy_reflect::TypeUuid;
1313
use bevy_render::{
1414
mesh::{GpuBufferInfo, Mesh},
@@ -328,6 +328,10 @@ impl FromWorld for MeshPipeline {
328328
texture,
329329
texture_view,
330330
sampler,
331+
size: Size::new(
332+
image.texture_descriptor.size.width as f32,
333+
image.texture_descriptor.size.height as f32,
334+
),
331335
}
332336
};
333337
MeshPipeline {

crates/bevy_render/src/render_phase/draw.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use bevy_ecs::{
1313
};
1414
use bevy_utils::HashMap;
1515
use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
16-
use std::{any::TypeId, fmt::Debug, hash::Hash};
16+
use std::{any::TypeId, fmt::Debug, hash::Hash, ops::Range};
1717

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

169+
pub trait BatchedPhaseItem: EntityPhaseItem {
170+
/// Range in the index buffer of this item
171+
fn batch_range(&self) -> &Option<Range<u32>>;
172+
173+
/// Range in the index buffer of this item
174+
fn batch_range_mut(&mut self) -> &mut Option<Range<u32>>;
175+
176+
/// Batches another item within this item if they are compatible.
177+
/// Items can be batched together if they have the same entity, and consecutive ranges.
178+
/// If batching is successful, the `other` item should be discarded from the render pass.
179+
#[inline]
180+
fn add_to_batch(&mut self, other: &Self) -> BatchResult {
181+
let self_entity = self.entity();
182+
if let (Some(self_batch_range), Some(other_batch_range)) = (
183+
self.batch_range_mut().as_mut(),
184+
other.batch_range().as_ref(),
185+
) {
186+
if self_entity == other.entity() && self_batch_range.end == other_batch_range.start {
187+
// If the items are compatible, join their range into `self`
188+
self_batch_range.end = other_batch_range.end;
189+
return BatchResult::Success;
190+
}
191+
}
192+
BatchResult::IncompatibleItems
193+
}
194+
}
195+
196+
pub enum BatchResult {
197+
/// The `other` item was batched into `self`
198+
Success,
199+
/// `self` and `other` cannot be batched together
200+
IncompatibleItems,
201+
}
202+
169203
impl<P: EntityPhaseItem, E: EntityRenderCommand> RenderCommand<P> for E {
170204
type Param = E::Param;
171205

crates/bevy_render/src/render_phase/mod.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,44 @@ impl<I: PhaseItem> RenderPhase<I> {
3131
}
3232
}
3333

34+
impl<I: BatchedPhaseItem> RenderPhase<I> {
35+
/// Batches the compatible [`BatchedPhaseItem`]s of this render phase
36+
pub fn batch(&mut self) {
37+
// TODO: this could be done in-place
38+
let mut items = std::mem::take(&mut self.items);
39+
let mut items = items.drain(..);
40+
41+
self.items.reserve(items.len());
42+
43+
// Start the first batch from the first item
44+
if let Some(mut current_batch) = items.next() {
45+
// Batch following items until we find an incompatible item
46+
for next_item in items {
47+
if matches!(
48+
current_batch.add_to_batch(&next_item),
49+
BatchResult::IncompatibleItems
50+
) {
51+
// Store the completed batch, and start a new one from the incompatible item
52+
self.items.push(current_batch);
53+
current_batch = next_item;
54+
}
55+
}
56+
// Store the last batch
57+
self.items.push(current_batch);
58+
}
59+
}
60+
}
61+
3462
/// This system sorts all [`RenderPhases`](RenderPhase) for the [`PhaseItem`] type.
3563
pub fn sort_phase_system<I: PhaseItem>(mut render_phases: Query<&mut RenderPhase<I>>) {
3664
for mut phase in render_phases.iter_mut() {
3765
phase.sort();
3866
}
3967
}
68+
69+
/// This batches the [`PhaseItem`]s of a [`RenderPhases`](RenderPhase).
70+
pub fn batch_phase_system<I: BatchedPhaseItem>(mut render_phases: Query<&mut RenderPhase<I>>) {
71+
for mut phase in render_phases.iter_mut() {
72+
phase.batch();
73+
}
74+
}

crates/bevy_render/src/texture/image.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::{
77
};
88
use bevy_asset::HandleUntyped;
99
use bevy_ecs::system::{lifetimeless::SRes, SystemParamItem};
10+
use bevy_math::Size;
1011
use bevy_reflect::TypeUuid;
1112
use thiserror::Error;
1213
use wgpu::{
@@ -373,12 +374,13 @@ impl TextureFormatPixelInfo for TextureFormat {
373374
}
374375

375376
/// The GPU-representation of an [`Image`].
376-
/// Consists of the [`Texture`], its [`TextureView`] and the corresponding [`Sampler`].
377+
/// Consists of the [`Texture`], its [`TextureView`] and the corresponding [`Sampler`], and the texture's [`Size`].
377378
#[derive(Debug, Clone)]
378379
pub struct GpuImage {
379380
pub texture: Texture,
380381
pub texture_view: TextureView,
381382
pub sampler: Sampler,
383+
pub size: Size,
382384
}
383385

384386
impl RenderAsset for Image {
@@ -426,10 +428,15 @@ impl RenderAsset for Image {
426428
);
427429

428430
let texture_view = texture.create_view(&TextureViewDescriptor::default());
431+
let size = Size::new(
432+
image.texture_descriptor.size.width as f32,
433+
image.texture_descriptor.size.height as f32,
434+
);
429435
Ok(GpuImage {
430436
texture,
431437
texture_view,
432438
sampler,
439+
size,
433440
})
434441
}
435442
}

crates/bevy_render/src/view/visibility/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ impl Default for Visibility {
2828
}
2929

3030
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
31-
#[derive(Component, Clone, Reflect)]
31+
#[derive(Component, Clone, Reflect, Debug)]
3232
#[reflect(Component)]
3333
pub struct ComputedVisibility {
3434
pub is_visible: bool,

crates/bevy_sprite/src/lib.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ pub const SPRITE_SHADER_HANDLE: HandleUntyped =
4848
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)]
4949
pub enum SpriteSystem {
5050
ExtractSprites,
51-
PrepareSprites,
5251
}
5352

5453
impl Plugin for SpritePlugin {
@@ -66,18 +65,14 @@ impl Plugin for SpritePlugin {
6665
.init_resource::<SpritePipeline>()
6766
.init_resource::<SpecializedPipelines<SpritePipeline>>()
6867
.init_resource::<SpriteMeta>()
69-
.init_resource::<Extracted2dItems>()
68+
.init_resource::<ExtractedSprites>()
7069
.init_resource::<SpriteAssetEvents>()
7170
.add_render_command::<Transparent2d, DrawSprite>()
7271
.add_system_to_stage(
7372
RenderStage::Extract,
7473
render::extract_sprites.label(SpriteSystem::ExtractSprites),
7574
)
7675
.add_system_to_stage(RenderStage::Extract, render::extract_sprite_events)
77-
.add_system_to_stage(
78-
RenderStage::Prepare,
79-
render::prepare_sprites.label(SpriteSystem::PrepareSprites),
80-
)
8176
.add_system_to_stage(RenderStage::Queue, queue_sprites);
8277
}
8378
}

crates/bevy_sprite/src/mesh2d/material.rs

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use bevy_render::{
2424
SpecializedPipeline, SpecializedPipelines,
2525
},
2626
renderer::RenderDevice,
27-
view::{ComputedVisibility, ExtractedView, Msaa, Visibility, VisibleEntities},
27+
view::{ComputedVisibility, Msaa, Visibility, VisibleEntities},
2828
RenderApp, RenderStage,
2929
};
3030
use bevy_transform::components::{GlobalTransform, Transform};
@@ -265,20 +265,17 @@ pub fn queue_material2d_meshes<M: SpecializedMaterial2d>(
265265
render_meshes: Res<RenderAssets<Mesh>>,
266266
render_materials: Res<RenderAssets<M>>,
267267
material2d_meshes: Query<(&Handle<M>, &Mesh2dHandle, &Mesh2dUniform)>,
268-
mut views: Query<(
269-
&ExtractedView,
270-
&VisibleEntities,
271-
&mut RenderPhase<Transparent2d>,
272-
)>,
268+
mut views: Query<(&VisibleEntities, &mut RenderPhase<Transparent2d>)>,
273269
) {
274-
for (view, visible_entities, mut transparent_phase) in views.iter_mut() {
270+
if material2d_meshes.is_empty() {
271+
return;
272+
}
273+
for (visible_entities, mut transparent_phase) in views.iter_mut() {
275274
let draw_transparent_pbr = transparent_draw_functions
276275
.read()
277276
.get_id::<DrawMaterial2d<M>>()
278277
.unwrap();
279278

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

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

305-
// NOTE: row 2 of the inverse view matrix dotted with column 3 of the model matrix
306-
// gives the z component of translation of the mesh in view space
307-
let mesh_z = inverse_view_row_2.dot(mesh2d_uniform.transform.col(3));
302+
let mesh_z = mesh2d_uniform.transform.w_axis.z;
308303
transparent_phase.add(Transparent2d {
309304
entity: *visible_entity,
310305
draw_function: draw_transparent_pbr,
@@ -314,6 +309,8 @@ pub fn queue_material2d_meshes<M: SpecializedMaterial2d>(
314309
// -z in front of the camera, the largest distance is -far with values increasing toward the
315310
// camera. As such we can just use mesh_z as the distance
316311
sort_key: FloatOrd(mesh_z),
312+
// This material is not batched
313+
batch_range: None,
317314
});
318315
}
319316
}

crates/bevy_sprite/src/mesh2d/mesh.rs

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use bevy_ecs::{
44
prelude::*,
55
system::{lifetimeless::*, SystemParamItem},
66
};
7-
use bevy_math::Mat4;
7+
use bevy_math::{Mat4, Size};
88
use bevy_reflect::TypeUuid;
99
use bevy_render::{
1010
mesh::{GpuBufferInfo, Mesh},
@@ -15,12 +15,10 @@ use bevy_render::{
1515
renderer::{RenderDevice, RenderQueue},
1616
texture::{BevyDefault, GpuImage, Image, TextureFormatPixelInfo},
1717
view::{ComputedVisibility, ExtractedView, ViewUniform, ViewUniformOffset, ViewUniforms},
18-
RenderApp, RenderStage, RenderWorld,
18+
RenderApp, RenderStage,
1919
};
2020
use bevy_transform::components::GlobalTransform;
2121

22-
use crate::{Extracted2dItem, Extracted2dItems, SpriteSystem};
23-
2422
#[derive(Default, Clone, Component)]
2523
pub struct Mesh2dHandle(pub Handle<Mesh>);
2624

@@ -63,10 +61,7 @@ impl Plugin for Mesh2dRenderPlugin {
6361
app.sub_app_mut(RenderApp)
6462
.init_resource::<Mesh2dPipeline>()
6563
.init_resource::<SpecializedPipelines<Mesh2dPipeline>>()
66-
.add_system_to_stage(
67-
RenderStage::Extract,
68-
extract_mesh2d.after(SpriteSystem::ExtractSprites),
69-
)
64+
.add_system_to_stage(RenderStage::Extract, extract_mesh2d)
7065
.add_system_to_stage(RenderStage::Queue, queue_mesh2d_bind_group)
7166
.add_system_to_stage(RenderStage::Queue, queue_mesh2d_view_bind_groups);
7267
}
@@ -90,19 +85,16 @@ bitflags::bitflags! {
9085
}
9186

9287
pub fn extract_mesh2d(
93-
mut render_world: ResMut<RenderWorld>,
9488
mut commands: Commands,
9589
mut previous_len: Local<usize>,
9690
query: Query<(Entity, &ComputedVisibility, &GlobalTransform, &Mesh2dHandle)>,
9791
) {
98-
let mut extracted_sprites = render_world.get_resource_mut::<Extracted2dItems>().unwrap();
9992
let mut values = Vec::with_capacity(*previous_len);
10093
for (entity, computed_visibility, transform, handle) in query.iter() {
10194
if !computed_visibility.is_visible {
10295
continue;
10396
}
10497
let transform = transform.compute_matrix();
105-
let z = transform.col(3).z;
10698
values.push((
10799
entity,
108100
(
@@ -114,9 +106,6 @@ pub fn extract_mesh2d(
114106
},
115107
),
116108
));
117-
// Break sprite batches
118-
// FIXME: this doesn't account for camera position
119-
extracted_sprites.items.push(Extracted2dItem::Other { z });
120109
}
121110
*previous_len = values.len();
122111
commands.insert_or_spawn_batch(values);
@@ -202,6 +191,10 @@ impl FromWorld for Mesh2dPipeline {
202191
texture,
203192
texture_view,
204193
sampler,
194+
size: Size::new(
195+
image.texture_descriptor.size.width as f32,
196+
image.texture_descriptor.size.height as f32,
197+
),
205198
}
206199
};
207200
Mesh2dPipeline {

0 commit comments

Comments
 (0)