Skip to content

Commit a8f15bd

Browse files
authored
Introduce two-level bins for multidrawable meshes. (#16898)
Currently, our batchable binned items are stored in a hash table that maps bin key, which includes the batch set key, to a list of entities. Multidraw is handled by sorting the bin keys and accumulating adjacent bins that can be multidrawn together (i.e. have the same batch set key) into multidraw commands during `batch_and_prepare_binned_render_phase`. This is reasonably efficient right now, but it will complicate future work to retain indirect draw parameters from frame to frame. Consider what must happen when we have retained indirect draw parameters and the application adds a bin (i.e. a new mesh) that shares a batch set key with some pre-existing meshes. (That is, the new mesh can be multidrawn with the pre-existing meshes.) To be maximally efficient, our goal in that scenario will be to update *only* the indirect draw parameters for the batch set (i.e. multidraw command) containing the mesh that was added, while leaving the others alone. That means that we have to quickly locate all the bins that belong to the batch set being modified. In the existing code, we would have to sort the list of bin keys so that bins that can be multidrawn together become adjacent to one another in the list. Then we would have to do a binary search through the sorted list to find the location of the bin that was just added. Next, we would have to widen our search to adjacent indexes that contain the same batch set, doing expensive comparisons against the batch set key every time. Finally, we would reallocate the indirect draw parameters and update the stored pointers to the indirect draw parameters that the bins store. By contrast, it'd be dramatically simpler if we simply changed the way bins are stored to first map from batch set key (i.e. multidraw command) to the bins (i.e. meshes) within that batch set key, and then from each individual bin to the mesh instances. That way, the scenario above in which we add a new mesh will be simpler to handle. First, we will look up the batch set key corresponding to that mesh in the outer map to find an inner map corresponding to the single multidraw command that will draw that batch set. We will know how many meshes the multidraw command is going to draw by the size of that inner map. Then we simply need to reallocate the indirect draw parameters and update the pointers to those parameters within the bins as necessary. There will be no need to do any binary search or expensive batch set key comparison: only a single hash lookup and an iteration over the inner map to update the pointers. This patch implements the above technique. Because we don't have retained bins yet, this PR provides no performance benefits. However, it opens the door to maximally efficient updates when only a small number of meshes change from frame to frame. The main churn that this patch causes is that the *batch set key* (which uniquely specifies a multidraw command) and *bin key* (which uniquely specifies a mesh *within* that multidraw command) are now separate, instead of the batch set key being embedded *within* the bin key. In order to isolate potential regressions, I think that at least #16890, #16836, and #16825 should land before this PR does. ## Migration Guide * The *batch set key* is now separate from the *bin key* in `BinnedPhaseItem`. The batch set key is used to collect multidrawable meshes together. If you aren't using the multidraw feature, you can safely set the batch set key to `()`.
1 parent 21c1b6a commit a8f15bd

File tree

18 files changed

+564
-311
lines changed

18 files changed

+564
-311
lines changed

assets/shaders/storage_buffer.wgsl

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
}
55

66
@group(2) @binding(0) var<storage, read> colors: array<vec4<f32>, 5>;
7+
@group(2) @binding(1) var<uniform> color_id: u32;
78

89
struct Vertex {
910
@builtin(instance_index) instance_index: u32,
@@ -23,10 +24,7 @@ fn vertex(vertex: Vertex) -> VertexOutput {
2324
out.world_position = mesh_functions::mesh_position_local_to_world(world_from_local, vec4(vertex.position, 1.0));
2425
out.clip_position = position_world_to_clip(out.world_position.xyz);
2526

26-
// We have 5 colors in the storage buffer, but potentially many instances of the mesh, so
27-
// we use the instance index to select a color from the storage buffer.
28-
out.color = colors[vertex.instance_index % 5];
29-
27+
out.color = colors[color_id];
3028
return out;
3129
}
3230

crates/bevy_core_pipeline/src/core_2d/mod.rs

Lines changed: 33 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,7 @@ pub mod graph {
3333
use core::ops::Range;
3434

3535
use bevy_asset::UntypedAssetId;
36-
use bevy_render::{
37-
batching::gpu_preprocessing::GpuPreprocessingMode, render_phase::PhaseItemBinKey,
38-
};
36+
use bevy_render::batching::gpu_preprocessing::GpuPreprocessingMode;
3937
use bevy_utils::HashMap;
4038
pub use camera_2d::*;
4139
pub use main_opaque_pass_2d_node::*;
@@ -127,8 +125,13 @@ impl Plugin for Core2dPlugin {
127125

128126
/// Opaque 2D [`BinnedPhaseItem`]s.
129127
pub struct Opaque2d {
128+
/// Determines which objects can be placed into a *batch set*.
129+
///
130+
/// Objects in a single batch set can potentially be multi-drawn together,
131+
/// if it's enabled and the current platform supports it.
132+
pub batch_set_key: (),
130133
/// The key, which determines which can be batched.
131-
pub key: Opaque2dBinKey,
134+
pub bin_key: Opaque2dBinKey,
132135
/// An entity from which data will be fetched, including the mesh if
133136
/// applicable.
134137
pub representative_entity: (Entity, MainEntity),
@@ -155,14 +158,6 @@ pub struct Opaque2dBinKey {
155158
pub material_bind_group_id: Option<BindGroupId>,
156159
}
157160

158-
impl PhaseItemBinKey for Opaque2dBinKey {
159-
type BatchSetKey = ();
160-
161-
fn get_batch_set_key(&self) -> Option<Self::BatchSetKey> {
162-
None
163-
}
164-
}
165-
166161
impl PhaseItem for Opaque2d {
167162
#[inline]
168163
fn entity(&self) -> Entity {
@@ -175,7 +170,7 @@ impl PhaseItem for Opaque2d {
175170

176171
#[inline]
177172
fn draw_function(&self) -> DrawFunctionId {
178-
self.key.draw_function
173+
self.bin_key.draw_function
179174
}
180175

181176
#[inline]
@@ -198,16 +193,22 @@ impl PhaseItem for Opaque2d {
198193
}
199194

200195
impl BinnedPhaseItem for Opaque2d {
196+
// Since 2D meshes presently can't be multidrawn, the batch set key is
197+
// irrelevant.
198+
type BatchSetKey = ();
199+
201200
type BinKey = Opaque2dBinKey;
202201

203202
fn new(
204-
key: Self::BinKey,
203+
batch_set_key: Self::BatchSetKey,
204+
bin_key: Self::BinKey,
205205
representative_entity: (Entity, MainEntity),
206206
batch_range: Range<u32>,
207207
extra_index: PhaseItemExtraIndex,
208208
) -> Self {
209209
Opaque2d {
210-
key,
210+
batch_set_key,
211+
bin_key,
211212
representative_entity,
212213
batch_range,
213214
extra_index,
@@ -218,14 +219,19 @@ impl BinnedPhaseItem for Opaque2d {
218219
impl CachedRenderPipelinePhaseItem for Opaque2d {
219220
#[inline]
220221
fn cached_pipeline(&self) -> CachedRenderPipelineId {
221-
self.key.pipeline
222+
self.bin_key.pipeline
222223
}
223224
}
224225

225226
/// Alpha mask 2D [`BinnedPhaseItem`]s.
226227
pub struct AlphaMask2d {
228+
/// Determines which objects can be placed into a *batch set*.
229+
///
230+
/// Objects in a single batch set can potentially be multi-drawn together,
231+
/// if it's enabled and the current platform supports it.
232+
pub batch_set_key: (),
227233
/// The key, which determines which can be batched.
228-
pub key: AlphaMask2dBinKey,
234+
pub bin_key: AlphaMask2dBinKey,
229235
/// An entity from which data will be fetched, including the mesh if
230236
/// applicable.
231237
pub representative_entity: (Entity, MainEntity),
@@ -265,7 +271,7 @@ impl PhaseItem for AlphaMask2d {
265271

266272
#[inline]
267273
fn draw_function(&self) -> DrawFunctionId {
268-
self.key.draw_function
274+
self.bin_key.draw_function
269275
}
270276

271277
#[inline]
@@ -288,35 +294,33 @@ impl PhaseItem for AlphaMask2d {
288294
}
289295

290296
impl BinnedPhaseItem for AlphaMask2d {
297+
// Since 2D meshes presently can't be multidrawn, the batch set key is
298+
// irrelevant.
299+
type BatchSetKey = ();
300+
291301
type BinKey = AlphaMask2dBinKey;
292302

293303
fn new(
294-
key: Self::BinKey,
304+
batch_set_key: Self::BatchSetKey,
305+
bin_key: Self::BinKey,
295306
representative_entity: (Entity, MainEntity),
296307
batch_range: Range<u32>,
297308
extra_index: PhaseItemExtraIndex,
298309
) -> Self {
299310
AlphaMask2d {
300-
key,
311+
batch_set_key,
312+
bin_key,
301313
representative_entity,
302314
batch_range,
303315
extra_index,
304316
}
305317
}
306318
}
307319

308-
impl PhaseItemBinKey for AlphaMask2dBinKey {
309-
type BatchSetKey = ();
310-
311-
fn get_batch_set_key(&self) -> Option<Self::BatchSetKey> {
312-
None
313-
}
314-
}
315-
316320
impl CachedRenderPipelinePhaseItem for AlphaMask2d {
317321
#[inline]
318322
fn cached_pipeline(&self) -> CachedRenderPipelineId {
319-
self.key.pipeline
323+
self.bin_key.pipeline
320324
}
321325
}
322326

crates/bevy_core_pipeline/src/core_3d/mod.rs

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ use core::ops::Range;
6868
use bevy_render::{
6969
batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport},
7070
mesh::allocator::SlabId,
71-
render_phase::PhaseItemBinKey,
7271
view::NoIndirectDrawing,
7372
};
7473
pub use camera_3d::*;
@@ -115,8 +114,8 @@ use crate::{
115114
dof::DepthOfFieldNode,
116115
prepass::{
117116
node::PrepassNode, AlphaMask3dPrepass, DeferredPrepass, DepthPrepass, MotionVectorPrepass,
118-
NormalPrepass, Opaque3dPrepass, OpaqueNoLightmap3dBinKey, ViewPrepassTextures,
119-
MOTION_VECTOR_PREPASS_FORMAT, NORMAL_PREPASS_FORMAT,
117+
NormalPrepass, Opaque3dPrepass, OpaqueNoLightmap3dBatchSetKey, OpaqueNoLightmap3dBinKey,
118+
ViewPrepassTextures, MOTION_VECTOR_PREPASS_FORMAT, NORMAL_PREPASS_FORMAT,
120119
},
121120
skybox::SkyboxPlugin,
122121
tonemapping::TonemappingNode,
@@ -219,8 +218,13 @@ impl Plugin for Core3dPlugin {
219218

220219
/// Opaque 3D [`BinnedPhaseItem`]s.
221220
pub struct Opaque3d {
221+
/// Determines which objects can be placed into a *batch set*.
222+
///
223+
/// Objects in a single batch set can potentially be multi-drawn together,
224+
/// if it's enabled and the current platform supports it.
225+
pub batch_set_key: Opaque3dBatchSetKey,
222226
/// The key, which determines which can be batched.
223-
pub key: Opaque3dBinKey,
227+
pub bin_key: Opaque3dBinKey,
224228
/// An entity from which data will be fetched, including the mesh if
225229
/// applicable.
226230
pub representative_entity: (Entity, MainEntity),
@@ -270,27 +274,13 @@ pub struct Opaque3dBatchSetKey {
270274
/// Note that a *batch set* (if multi-draw is in use) contains multiple batches.
271275
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
272276
pub struct Opaque3dBinKey {
273-
/// The key of the *batch set*.
274-
///
275-
/// As batches belong to a batch set, meshes in a batch must obviously be
276-
/// able to be placed in a single batch set.
277-
pub batch_set_key: Opaque3dBatchSetKey,
278-
279277
/// The asset that this phase item is associated with.
280278
///
281279
/// Normally, this is the ID of the mesh, but for non-mesh items it might be
282280
/// the ID of another type of asset.
283281
pub asset_id: UntypedAssetId,
284282
}
285283

286-
impl PhaseItemBinKey for Opaque3dBinKey {
287-
type BatchSetKey = Opaque3dBatchSetKey;
288-
289-
fn get_batch_set_key(&self) -> Option<Self::BatchSetKey> {
290-
Some(self.batch_set_key.clone())
291-
}
292-
}
293-
294284
impl PhaseItem for Opaque3d {
295285
#[inline]
296286
fn entity(&self) -> Entity {
@@ -304,7 +294,7 @@ impl PhaseItem for Opaque3d {
304294

305295
#[inline]
306296
fn draw_function(&self) -> DrawFunctionId {
307-
self.key.batch_set_key.draw_function
297+
self.batch_set_key.draw_function
308298
}
309299

310300
#[inline]
@@ -327,17 +317,20 @@ impl PhaseItem for Opaque3d {
327317
}
328318

329319
impl BinnedPhaseItem for Opaque3d {
320+
type BatchSetKey = Opaque3dBatchSetKey;
330321
type BinKey = Opaque3dBinKey;
331322

332323
#[inline]
333324
fn new(
334-
key: Self::BinKey,
325+
batch_set_key: Self::BatchSetKey,
326+
bin_key: Self::BinKey,
335327
representative_entity: (Entity, MainEntity),
336328
batch_range: Range<u32>,
337329
extra_index: PhaseItemExtraIndex,
338330
) -> Self {
339331
Opaque3d {
340-
key,
332+
batch_set_key,
333+
bin_key,
341334
representative_entity,
342335
batch_range,
343336
extra_index,
@@ -348,12 +341,18 @@ impl BinnedPhaseItem for Opaque3d {
348341
impl CachedRenderPipelinePhaseItem for Opaque3d {
349342
#[inline]
350343
fn cached_pipeline(&self) -> CachedRenderPipelineId {
351-
self.key.batch_set_key.pipeline
344+
self.batch_set_key.pipeline
352345
}
353346
}
354347

355348
pub struct AlphaMask3d {
356-
pub key: OpaqueNoLightmap3dBinKey,
349+
/// Determines which objects can be placed into a *batch set*.
350+
///
351+
/// Objects in a single batch set can potentially be multi-drawn together,
352+
/// if it's enabled and the current platform supports it.
353+
pub batch_set_key: OpaqueNoLightmap3dBatchSetKey,
354+
/// The key, which determines which can be batched.
355+
pub bin_key: OpaqueNoLightmap3dBinKey,
357356
pub representative_entity: (Entity, MainEntity),
358357
pub batch_range: Range<u32>,
359358
pub extra_index: PhaseItemExtraIndex,
@@ -371,7 +370,7 @@ impl PhaseItem for AlphaMask3d {
371370

372371
#[inline]
373372
fn draw_function(&self) -> DrawFunctionId {
374-
self.key.batch_set_key.draw_function
373+
self.batch_set_key.draw_function
375374
}
376375

377376
#[inline]
@@ -397,16 +396,19 @@ impl PhaseItem for AlphaMask3d {
397396

398397
impl BinnedPhaseItem for AlphaMask3d {
399398
type BinKey = OpaqueNoLightmap3dBinKey;
399+
type BatchSetKey = OpaqueNoLightmap3dBatchSetKey;
400400

401401
#[inline]
402402
fn new(
403-
key: Self::BinKey,
403+
batch_set_key: Self::BatchSetKey,
404+
bin_key: Self::BinKey,
404405
representative_entity: (Entity, MainEntity),
405406
batch_range: Range<u32>,
406407
extra_index: PhaseItemExtraIndex,
407408
) -> Self {
408409
Self {
409-
key,
410+
batch_set_key,
411+
bin_key,
410412
representative_entity,
411413
batch_range,
412414
extra_index,
@@ -417,7 +419,7 @@ impl BinnedPhaseItem for AlphaMask3d {
417419
impl CachedRenderPipelinePhaseItem for AlphaMask3d {
418420
#[inline]
419421
fn cached_pipeline(&self) -> CachedRenderPipelineId {
420-
self.key.batch_set_key.pipeline
422+
self.batch_set_key.pipeline
421423
}
422424
}
423425

0 commit comments

Comments
 (0)