Skip to content

Commit f5de3f0

Browse files
authored
Use multidraw for opaque meshes when GPU culling is in use. (bevyengine#16427)
This commit adds support for *multidraw*, which is a feature that allows multiple meshes to be drawn in a single drawcall. `wgpu` currently implements multidraw on Vulkan, so this feature is only enabled there. Multiple meshes can be drawn at once if they're in the same vertex and index buffers and are otherwise placed in the same bin. (Thus, for example, at present the materials and textures must be identical, but see bevyengine#16368.) Multidraw is a significant performance improvement during the draw phase because it reduces the number of rebindings, as well as the number of drawcalls. This feature is currently only enabled when GPU culling is used: i.e. when `GpuCulling` is present on a camera. Therefore, if you run for example `scene_viewer`, you will not see any performance improvements, because `scene_viewer` doesn't add the `GpuCulling` component to its camera. Additionally, the multidraw feature is only implemented for opaque 3D meshes and not for shadows or 2D meshes. I plan to make GPU culling the default and to extend the feature to shadows in the future. Also, in the future I suspect that polyfilling multidraw on APIs that don't support it will be fruitful, as even without driver-level support use of multidraw allows us to avoid expensive `wgpu` rebindings.
1 parent 4d6b02a commit f5de3f0

File tree

28 files changed

+669
-310
lines changed

28 files changed

+669
-310
lines changed

crates/bevy_core_pipeline/src/core_2d/mod.rs

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ pub mod graph {
3333
use core::ops::Range;
3434

3535
use bevy_asset::UntypedAssetId;
36+
use bevy_render::batching::gpu_preprocessing::GpuPreprocessingMode;
37+
use bevy_render::render_phase::PhaseItemBinKey;
3638
use bevy_utils::HashMap;
3739
pub use camera_2d::*;
3840
pub use main_opaque_pass_2d_node::*;
@@ -153,6 +155,14 @@ pub struct Opaque2dBinKey {
153155
pub material_bind_group_id: Option<BindGroupId>,
154156
}
155157

158+
impl PhaseItemBinKey for Opaque2dBinKey {
159+
type BatchSetKey = ();
160+
161+
fn get_batch_set_key(&self) -> Option<Self::BatchSetKey> {
162+
None
163+
}
164+
}
165+
156166
impl PhaseItem for Opaque2d {
157167
#[inline]
158168
fn entity(&self) -> Entity {
@@ -179,7 +189,7 @@ impl PhaseItem for Opaque2d {
179189
}
180190

181191
fn extra_index(&self) -> PhaseItemExtraIndex {
182-
self.extra_index
192+
self.extra_index.clone()
183193
}
184194

185195
fn batch_range_and_extra_index_mut(&mut self) -> (&mut Range<u32>, &mut PhaseItemExtraIndex) {
@@ -269,7 +279,7 @@ impl PhaseItem for AlphaMask2d {
269279
}
270280

271281
fn extra_index(&self) -> PhaseItemExtraIndex {
272-
self.extra_index
282+
self.extra_index.clone()
273283
}
274284

275285
fn batch_range_and_extra_index_mut(&mut self) -> (&mut Range<u32>, &mut PhaseItemExtraIndex) {
@@ -295,6 +305,14 @@ impl BinnedPhaseItem for AlphaMask2d {
295305
}
296306
}
297307

308+
impl PhaseItemBinKey for AlphaMask2dBinKey {
309+
type BatchSetKey = ();
310+
311+
fn get_batch_set_key(&self) -> Option<Self::BatchSetKey> {
312+
None
313+
}
314+
}
315+
298316
impl CachedRenderPipelinePhaseItem for AlphaMask2d {
299317
#[inline]
300318
fn cached_pipeline(&self) -> CachedRenderPipelineId {
@@ -340,7 +358,7 @@ impl PhaseItem for Transparent2d {
340358

341359
#[inline]
342360
fn extra_index(&self) -> PhaseItemExtraIndex {
343-
self.extra_index
361+
self.extra_index.clone()
344362
}
345363

346364
#[inline]
@@ -385,8 +403,8 @@ pub fn extract_core_2d_camera_phases(
385403
continue;
386404
}
387405
transparent_2d_phases.insert_or_clear(entity);
388-
opaque_2d_phases.insert_or_clear(entity);
389-
alpha_mask_2d_phases.insert_or_clear(entity);
406+
opaque_2d_phases.insert_or_clear(entity, GpuPreprocessingMode::None);
407+
alpha_mask_2d_phases.insert_or_clear(entity, GpuPreprocessingMode::None);
390408

391409
live_entities.insert(entity);
392410
}

crates/bevy_core_pipeline/src/core_3d/mod.rs

Lines changed: 87 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ pub const DEPTH_TEXTURE_SAMPLING_SUPPORTED: bool = true;
6565

6666
use core::ops::Range;
6767

68+
use bevy_render::batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport};
69+
use bevy_render::mesh::allocator::SlabId;
70+
use bevy_render::render_phase::PhaseItemBinKey;
71+
use bevy_render::view::GpuCulling;
6872
pub use camera_3d::*;
6973
pub use main_opaque_pass_3d_node::*;
7074
pub use main_transparent_pass_3d_node::*;
@@ -224,9 +228,13 @@ pub struct Opaque3d {
224228
pub extra_index: PhaseItemExtraIndex,
225229
}
226230

227-
/// Data that must be identical in order to batch phase items together.
231+
/// Information that must be identical in order to place opaque meshes in the
232+
/// same *batch set*.
233+
///
234+
/// A batch set is a set of batches that can be multi-drawn together, if
235+
/// multi-draw is in use.
228236
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
229-
pub struct Opaque3dBinKey {
237+
pub struct Opaque3dBatchSetKey {
230238
/// The identifier of the render pipeline.
231239
pub pipeline: CachedRenderPipelineId,
232240

@@ -238,14 +246,45 @@ pub struct Opaque3dBinKey {
238246
/// In the case of PBR, this is the `MaterialBindGroupIndex`.
239247
pub material_bind_group_index: Option<u32>,
240248

249+
/// The ID of the slab of GPU memory that contains vertex data.
250+
///
251+
/// For non-mesh items, you can fill this with 0 if your items can be
252+
/// multi-drawn, or with a unique value if they can't.
253+
pub vertex_slab: SlabId,
254+
255+
/// The ID of the slab of GPU memory that contains index data, if present.
256+
///
257+
/// For non-mesh items, you can safely fill this with `None`.
258+
pub index_slab: Option<SlabId>,
259+
260+
/// The lightmap, if present.
261+
pub lightmap_image: Option<AssetId<Image>>,
262+
}
263+
264+
/// Data that must be identical in order to *batch* phase items together.
265+
///
266+
/// Note that a *batch set* (if multi-draw is in use) contains multiple batches.
267+
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
268+
pub struct Opaque3dBinKey {
269+
/// The key of the *batch set*.
270+
///
271+
/// As batches belong to a batch set, meshes in a batch must obviously be
272+
/// able to be placed in a single batch set.
273+
pub batch_set_key: Opaque3dBatchSetKey,
274+
241275
/// The asset that this phase item is associated with.
242276
///
243277
/// Normally, this is the ID of the mesh, but for non-mesh items it might be
244278
/// the ID of another type of asset.
245279
pub asset_id: UntypedAssetId,
280+
}
246281

247-
/// The lightmap, if present.
248-
pub lightmap_image: Option<AssetId<Image>>,
282+
impl PhaseItemBinKey for Opaque3dBinKey {
283+
type BatchSetKey = Opaque3dBatchSetKey;
284+
285+
fn get_batch_set_key(&self) -> Option<Self::BatchSetKey> {
286+
Some(self.batch_set_key.clone())
287+
}
249288
}
250289

251290
impl PhaseItem for Opaque3d {
@@ -261,7 +300,7 @@ impl PhaseItem for Opaque3d {
261300

262301
#[inline]
263302
fn draw_function(&self) -> DrawFunctionId {
264-
self.key.draw_function
303+
self.key.batch_set_key.draw_function
265304
}
266305

267306
#[inline]
@@ -275,7 +314,7 @@ impl PhaseItem for Opaque3d {
275314
}
276315

277316
fn extra_index(&self) -> PhaseItemExtraIndex {
278-
self.extra_index
317+
self.extra_index.clone()
279318
}
280319

281320
fn batch_range_and_extra_index_mut(&mut self) -> (&mut Range<u32>, &mut PhaseItemExtraIndex) {
@@ -305,7 +344,7 @@ impl BinnedPhaseItem for Opaque3d {
305344
impl CachedRenderPipelinePhaseItem for Opaque3d {
306345
#[inline]
307346
fn cached_pipeline(&self) -> CachedRenderPipelineId {
308-
self.key.pipeline
347+
self.key.batch_set_key.pipeline
309348
}
310349
}
311350

@@ -343,7 +382,7 @@ impl PhaseItem for AlphaMask3d {
343382

344383
#[inline]
345384
fn extra_index(&self) -> PhaseItemExtraIndex {
346-
self.extra_index
385+
self.extra_index.clone()
347386
}
348387

349388
#[inline]
@@ -426,7 +465,7 @@ impl PhaseItem for Transmissive3d {
426465

427466
#[inline]
428467
fn extra_index(&self) -> PhaseItemExtraIndex {
429-
self.extra_index
468+
self.extra_index.clone()
430469
}
431470

432471
#[inline]
@@ -493,7 +532,7 @@ impl PhaseItem for Transparent3d {
493532

494533
#[inline]
495534
fn extra_index(&self) -> PhaseItemExtraIndex {
496-
self.extra_index
535+
self.extra_index.clone()
497536
}
498537

499538
#[inline]
@@ -529,18 +568,27 @@ pub fn extract_core_3d_camera_phases(
529568
mut alpha_mask_3d_phases: ResMut<ViewBinnedRenderPhases<AlphaMask3d>>,
530569
mut transmissive_3d_phases: ResMut<ViewSortedRenderPhases<Transmissive3d>>,
531570
mut transparent_3d_phases: ResMut<ViewSortedRenderPhases<Transparent3d>>,
532-
cameras_3d: Extract<Query<(RenderEntity, &Camera), With<Camera3d>>>,
571+
cameras_3d: Extract<Query<(RenderEntity, &Camera, Has<GpuCulling>), With<Camera3d>>>,
533572
mut live_entities: Local<EntityHashSet>,
573+
gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
534574
) {
535575
live_entities.clear();
536576

537-
for (entity, camera) in &cameras_3d {
577+
for (entity, camera, has_gpu_culling) in &cameras_3d {
538578
if !camera.is_active {
539579
continue;
540580
}
541581

542-
opaque_3d_phases.insert_or_clear(entity);
543-
alpha_mask_3d_phases.insert_or_clear(entity);
582+
// If GPU culling is in use, use it (and indirect mode); otherwise, just
583+
// preprocess the meshes.
584+
let gpu_preprocessing_mode = gpu_preprocessing_support.min(if has_gpu_culling {
585+
GpuPreprocessingMode::Culling
586+
} else {
587+
GpuPreprocessingMode::PreprocessingOnly
588+
});
589+
590+
opaque_3d_phases.insert_or_clear(entity, gpu_preprocessing_mode);
591+
alpha_mask_3d_phases.insert_or_clear(entity, gpu_preprocessing_mode);
544592
transmissive_3d_phases.insert_or_clear(entity);
545593
transparent_3d_phases.insert_or_clear(entity);
546594

@@ -554,6 +602,8 @@ pub fn extract_core_3d_camera_phases(
554602
}
555603

556604
// Extract the render phases for the prepass
605+
606+
#[allow(clippy::too_many_arguments)]
557607
pub fn extract_camera_prepass_phase(
558608
mut commands: Commands,
559609
mut opaque_3d_prepass_phases: ResMut<ViewBinnedRenderPhases<Opaque3dPrepass>>,
@@ -565,6 +615,7 @@ pub fn extract_camera_prepass_phase(
565615
(
566616
RenderEntity,
567617
&Camera,
618+
Has<GpuCulling>,
568619
Has<DepthPrepass>,
569620
Has<NormalPrepass>,
570621
Has<MotionVectorPrepass>,
@@ -574,27 +625,43 @@ pub fn extract_camera_prepass_phase(
574625
>,
575626
>,
576627
mut live_entities: Local<EntityHashSet>,
628+
gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
577629
) {
578630
live_entities.clear();
579631

580-
for (entity, camera, depth_prepass, normal_prepass, motion_vector_prepass, deferred_prepass) in
581-
cameras_3d.iter()
632+
for (
633+
entity,
634+
camera,
635+
gpu_culling,
636+
depth_prepass,
637+
normal_prepass,
638+
motion_vector_prepass,
639+
deferred_prepass,
640+
) in cameras_3d.iter()
582641
{
583642
if !camera.is_active {
584643
continue;
585644
}
586645

646+
// If GPU culling is in use, use it (and indirect mode); otherwise, just
647+
// preprocess the meshes.
648+
let gpu_preprocessing_mode = gpu_preprocessing_support.min(if gpu_culling {
649+
GpuPreprocessingMode::Culling
650+
} else {
651+
GpuPreprocessingMode::PreprocessingOnly
652+
});
653+
587654
if depth_prepass || normal_prepass || motion_vector_prepass {
588-
opaque_3d_prepass_phases.insert_or_clear(entity);
589-
alpha_mask_3d_prepass_phases.insert_or_clear(entity);
655+
opaque_3d_prepass_phases.insert_or_clear(entity, gpu_preprocessing_mode);
656+
alpha_mask_3d_prepass_phases.insert_or_clear(entity, gpu_preprocessing_mode);
590657
} else {
591658
opaque_3d_prepass_phases.remove(&entity);
592659
alpha_mask_3d_prepass_phases.remove(&entity);
593660
}
594661

595662
if deferred_prepass {
596-
opaque_3d_deferred_phases.insert_or_clear(entity);
597-
alpha_mask_3d_deferred_phases.insert_or_clear(entity);
663+
opaque_3d_deferred_phases.insert_or_clear(entity, gpu_preprocessing_mode);
664+
alpha_mask_3d_deferred_phases.insert_or_clear(entity, gpu_preprocessing_mode);
598665
} else {
599666
opaque_3d_deferred_phases.remove(&entity);
600667
alpha_mask_3d_deferred_phases.remove(&entity);

crates/bevy_core_pipeline/src/deferred/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ impl PhaseItem for Opaque3dDeferred {
5858

5959
#[inline]
6060
fn extra_index(&self) -> PhaseItemExtraIndex {
61-
self.extra_index
61+
self.extra_index.clone()
6262
}
6363

6464
#[inline]
@@ -133,7 +133,7 @@ impl PhaseItem for AlphaMask3dDeferred {
133133

134134
#[inline]
135135
fn extra_index(&self) -> PhaseItemExtraIndex {
136-
self.extra_index
136+
self.extra_index.clone()
137137
}
138138

139139
#[inline]

crates/bevy_core_pipeline/src/prepass/mod.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ use bevy_asset::UntypedAssetId;
3434
use bevy_ecs::prelude::*;
3535
use bevy_math::Mat4;
3636
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
37+
use bevy_render::render_phase::PhaseItemBinKey;
3738
use bevy_render::sync_world::MainEntity;
3839
use bevy_render::{
3940
render_phase::{
@@ -167,6 +168,14 @@ pub struct OpaqueNoLightmap3dBinKey {
167168
pub asset_id: UntypedAssetId,
168169
}
169170

171+
impl PhaseItemBinKey for OpaqueNoLightmap3dBinKey {
172+
type BatchSetKey = ();
173+
174+
fn get_batch_set_key(&self) -> Option<Self::BatchSetKey> {
175+
None
176+
}
177+
}
178+
170179
impl PhaseItem for Opaque3dPrepass {
171180
#[inline]
172181
fn entity(&self) -> Entity {
@@ -194,7 +203,7 @@ impl PhaseItem for Opaque3dPrepass {
194203

195204
#[inline]
196205
fn extra_index(&self) -> PhaseItemExtraIndex {
197-
self.extra_index
206+
self.extra_index.clone()
198207
}
199208

200209
#[inline]
@@ -268,7 +277,7 @@ impl PhaseItem for AlphaMask3dPrepass {
268277

269278
#[inline]
270279
fn extra_index(&self) -> PhaseItemExtraIndex {
271-
self.extra_index
280+
self.extra_index.clone()
272281
}
273282

274283
#[inline]

crates/bevy_gizmos/src/pipeline_2d.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ fn queue_line_gizmos_2d(
338338
pipeline,
339339
sort_key: FloatOrd(f32::INFINITY),
340340
batch_range: 0..1,
341-
extra_index: PhaseItemExtraIndex::NONE,
341+
extra_index: PhaseItemExtraIndex::None,
342342
});
343343
}
344344

@@ -358,7 +358,7 @@ fn queue_line_gizmos_2d(
358358
pipeline,
359359
sort_key: FloatOrd(f32::INFINITY),
360360
batch_range: 0..1,
361-
extra_index: PhaseItemExtraIndex::NONE,
361+
extra_index: PhaseItemExtraIndex::None,
362362
});
363363
}
364364
}
@@ -417,7 +417,7 @@ fn queue_line_joint_gizmos_2d(
417417
pipeline,
418418
sort_key: FloatOrd(f32::INFINITY),
419419
batch_range: 0..1,
420-
extra_index: PhaseItemExtraIndex::NONE,
420+
extra_index: PhaseItemExtraIndex::None,
421421
});
422422
}
423423
}

0 commit comments

Comments
 (0)