diff --git a/assets/shaders/storage_buffer.wgsl b/assets/shaders/storage_buffer.wgsl index c052411e3f..1859e8dde2 100644 --- a/assets/shaders/storage_buffer.wgsl +++ b/assets/shaders/storage_buffer.wgsl @@ -4,6 +4,7 @@ } @group(2) @binding(0) var colors: array, 5>; +@group(2) @binding(1) var color_id: u32; struct Vertex { @builtin(instance_index) instance_index: u32, @@ -23,10 +24,7 @@ fn vertex(vertex: Vertex) -> VertexOutput { out.world_position = mesh_functions::mesh_position_local_to_world(world_from_local, vec4(vertex.position, 1.0)); out.clip_position = position_world_to_clip(out.world_position.xyz); - // We have 5 colors in the storage buffer, but potentially many instances of the mesh, so - // we use the instance index to select a color from the storage buffer. - out.color = colors[vertex.instance_index % 5]; - + out.color = colors[color_id]; return out; } diff --git a/crates/bevy_core_pipeline/src/core_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs index d57134aa3e..288b08b6ae 100644 --- a/crates/bevy_core_pipeline/src/core_2d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -33,9 +33,7 @@ pub mod graph { use core::ops::Range; use bevy_asset::UntypedAssetId; -use bevy_render::{ - batching::gpu_preprocessing::GpuPreprocessingMode, render_phase::PhaseItemBinKey, -}; +use bevy_render::batching::gpu_preprocessing::GpuPreprocessingMode; use bevy_utils::HashMap; pub use camera_2d::*; pub use main_opaque_pass_2d_node::*; @@ -127,8 +125,13 @@ impl Plugin for Core2dPlugin { /// Opaque 2D [`BinnedPhaseItem`]s. pub struct Opaque2d { + /// Determines which objects can be placed into a *batch set*. + /// + /// Objects in a single batch set can potentially be multi-drawn together, + /// if it's enabled and the current platform supports it. + pub batch_set_key: (), /// The key, which determines which can be batched. - pub key: Opaque2dBinKey, + pub bin_key: Opaque2dBinKey, /// An entity from which data will be fetched, including the mesh if /// applicable. pub representative_entity: (Entity, MainEntity), @@ -155,14 +158,6 @@ pub struct Opaque2dBinKey { pub material_bind_group_id: Option, } -impl PhaseItemBinKey for Opaque2dBinKey { - type BatchSetKey = (); - - fn get_batch_set_key(&self) -> Option { - None - } -} - impl PhaseItem for Opaque2d { #[inline] fn entity(&self) -> Entity { @@ -175,7 +170,7 @@ impl PhaseItem for Opaque2d { #[inline] fn draw_function(&self) -> DrawFunctionId { - self.key.draw_function + self.bin_key.draw_function } #[inline] @@ -198,16 +193,22 @@ impl PhaseItem for Opaque2d { } impl BinnedPhaseItem for Opaque2d { + // Since 2D meshes presently can't be multidrawn, the batch set key is + // irrelevant. + type BatchSetKey = (); + type BinKey = Opaque2dBinKey; fn new( - key: Self::BinKey, + batch_set_key: Self::BatchSetKey, + bin_key: Self::BinKey, representative_entity: (Entity, MainEntity), batch_range: Range, extra_index: PhaseItemExtraIndex, ) -> Self { Opaque2d { - key, + batch_set_key, + bin_key, representative_entity, batch_range, extra_index, @@ -218,14 +219,19 @@ impl BinnedPhaseItem for Opaque2d { impl CachedRenderPipelinePhaseItem for Opaque2d { #[inline] fn cached_pipeline(&self) -> CachedRenderPipelineId { - self.key.pipeline + self.bin_key.pipeline } } /// Alpha mask 2D [`BinnedPhaseItem`]s. pub struct AlphaMask2d { + /// Determines which objects can be placed into a *batch set*. + /// + /// Objects in a single batch set can potentially be multi-drawn together, + /// if it's enabled and the current platform supports it. + pub batch_set_key: (), /// The key, which determines which can be batched. - pub key: AlphaMask2dBinKey, + pub bin_key: AlphaMask2dBinKey, /// An entity from which data will be fetched, including the mesh if /// applicable. pub representative_entity: (Entity, MainEntity), @@ -265,7 +271,7 @@ impl PhaseItem for AlphaMask2d { #[inline] fn draw_function(&self) -> DrawFunctionId { - self.key.draw_function + self.bin_key.draw_function } #[inline] @@ -288,16 +294,22 @@ impl PhaseItem for AlphaMask2d { } impl BinnedPhaseItem for AlphaMask2d { + // Since 2D meshes presently can't be multidrawn, the batch set key is + // irrelevant. + type BatchSetKey = (); + type BinKey = AlphaMask2dBinKey; fn new( - key: Self::BinKey, + batch_set_key: Self::BatchSetKey, + bin_key: Self::BinKey, representative_entity: (Entity, MainEntity), batch_range: Range, extra_index: PhaseItemExtraIndex, ) -> Self { AlphaMask2d { - key, + batch_set_key, + bin_key, representative_entity, batch_range, extra_index, @@ -305,18 +317,10 @@ impl BinnedPhaseItem for AlphaMask2d { } } -impl PhaseItemBinKey for AlphaMask2dBinKey { - type BatchSetKey = (); - - fn get_batch_set_key(&self) -> Option { - None - } -} - impl CachedRenderPipelinePhaseItem for AlphaMask2d { #[inline] fn cached_pipeline(&self) -> CachedRenderPipelineId { - self.key.pipeline + self.bin_key.pipeline } } diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index 7d2c11c0c2..77fdabcab8 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -68,7 +68,6 @@ use core::ops::Range; use bevy_render::{ batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport}, mesh::allocator::SlabId, - render_phase::PhaseItemBinKey, view::NoIndirectDrawing, }; pub use camera_3d::*; @@ -115,8 +114,8 @@ use crate::{ dof::DepthOfFieldNode, prepass::{ node::PrepassNode, AlphaMask3dPrepass, DeferredPrepass, DepthPrepass, MotionVectorPrepass, - NormalPrepass, Opaque3dPrepass, OpaqueNoLightmap3dBinKey, ViewPrepassTextures, - MOTION_VECTOR_PREPASS_FORMAT, NORMAL_PREPASS_FORMAT, + NormalPrepass, Opaque3dPrepass, OpaqueNoLightmap3dBatchSetKey, OpaqueNoLightmap3dBinKey, + ViewPrepassTextures, MOTION_VECTOR_PREPASS_FORMAT, NORMAL_PREPASS_FORMAT, }, skybox::SkyboxPlugin, tonemapping::TonemappingNode, @@ -219,8 +218,13 @@ impl Plugin for Core3dPlugin { /// Opaque 3D [`BinnedPhaseItem`]s. pub struct Opaque3d { + /// Determines which objects can be placed into a *batch set*. + /// + /// Objects in a single batch set can potentially be multi-drawn together, + /// if it's enabled and the current platform supports it. + pub batch_set_key: Opaque3dBatchSetKey, /// The key, which determines which can be batched. - pub key: Opaque3dBinKey, + pub bin_key: Opaque3dBinKey, /// An entity from which data will be fetched, including the mesh if /// applicable. pub representative_entity: (Entity, MainEntity), @@ -270,12 +274,6 @@ pub struct Opaque3dBatchSetKey { /// Note that a *batch set* (if multi-draw is in use) contains multiple batches. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Opaque3dBinKey { - /// The key of the *batch set*. - /// - /// As batches belong to a batch set, meshes in a batch must obviously be - /// able to be placed in a single batch set. - pub batch_set_key: Opaque3dBatchSetKey, - /// The asset that this phase item is associated with. /// /// Normally, this is the ID of the mesh, but for non-mesh items it might be @@ -283,14 +281,6 @@ pub struct Opaque3dBinKey { pub asset_id: UntypedAssetId, } -impl PhaseItemBinKey for Opaque3dBinKey { - type BatchSetKey = Opaque3dBatchSetKey; - - fn get_batch_set_key(&self) -> Option { - Some(self.batch_set_key.clone()) - } -} - impl PhaseItem for Opaque3d { #[inline] fn entity(&self) -> Entity { @@ -304,7 +294,7 @@ impl PhaseItem for Opaque3d { #[inline] fn draw_function(&self) -> DrawFunctionId { - self.key.batch_set_key.draw_function + self.batch_set_key.draw_function } #[inline] @@ -327,17 +317,20 @@ impl PhaseItem for Opaque3d { } impl BinnedPhaseItem for Opaque3d { + type BatchSetKey = Opaque3dBatchSetKey; type BinKey = Opaque3dBinKey; #[inline] fn new( - key: Self::BinKey, + batch_set_key: Self::BatchSetKey, + bin_key: Self::BinKey, representative_entity: (Entity, MainEntity), batch_range: Range, extra_index: PhaseItemExtraIndex, ) -> Self { Opaque3d { - key, + batch_set_key, + bin_key, representative_entity, batch_range, extra_index, @@ -348,12 +341,18 @@ impl BinnedPhaseItem for Opaque3d { impl CachedRenderPipelinePhaseItem for Opaque3d { #[inline] fn cached_pipeline(&self) -> CachedRenderPipelineId { - self.key.batch_set_key.pipeline + self.batch_set_key.pipeline } } pub struct AlphaMask3d { - pub key: OpaqueNoLightmap3dBinKey, + /// Determines which objects can be placed into a *batch set*. + /// + /// Objects in a single batch set can potentially be multi-drawn together, + /// if it's enabled and the current platform supports it. + pub batch_set_key: OpaqueNoLightmap3dBatchSetKey, + /// The key, which determines which can be batched. + pub bin_key: OpaqueNoLightmap3dBinKey, pub representative_entity: (Entity, MainEntity), pub batch_range: Range, pub extra_index: PhaseItemExtraIndex, @@ -371,7 +370,7 @@ impl PhaseItem for AlphaMask3d { #[inline] fn draw_function(&self) -> DrawFunctionId { - self.key.batch_set_key.draw_function + self.batch_set_key.draw_function } #[inline] @@ -397,16 +396,19 @@ impl PhaseItem for AlphaMask3d { impl BinnedPhaseItem for AlphaMask3d { type BinKey = OpaqueNoLightmap3dBinKey; + type BatchSetKey = OpaqueNoLightmap3dBatchSetKey; #[inline] fn new( - key: Self::BinKey, + batch_set_key: Self::BatchSetKey, + bin_key: Self::BinKey, representative_entity: (Entity, MainEntity), batch_range: Range, extra_index: PhaseItemExtraIndex, ) -> Self { Self { - key, + batch_set_key, + bin_key, representative_entity, batch_range, extra_index, @@ -417,7 +419,7 @@ impl BinnedPhaseItem for AlphaMask3d { impl CachedRenderPipelinePhaseItem for AlphaMask3d { #[inline] fn cached_pipeline(&self) -> CachedRenderPipelineId { - self.key.batch_set_key.pipeline + self.batch_set_key.pipeline } } diff --git a/crates/bevy_core_pipeline/src/deferred/mod.rs b/crates/bevy_core_pipeline/src/deferred/mod.rs index f544204ec2..b9f5169b48 100644 --- a/crates/bevy_core_pipeline/src/deferred/mod.rs +++ b/crates/bevy_core_pipeline/src/deferred/mod.rs @@ -3,8 +3,7 @@ pub mod node; use core::ops::Range; -use crate::core_3d::Opaque3dBinKey; -use crate::prepass::OpaqueNoLightmap3dBinKey; +use crate::prepass::{OpaqueNoLightmap3dBatchSetKey, OpaqueNoLightmap3dBinKey}; use bevy_ecs::prelude::*; use bevy_render::sync_world::MainEntity; use bevy_render::{ @@ -26,7 +25,13 @@ pub const DEFERRED_LIGHTING_PASS_ID_DEPTH_FORMAT: TextureFormat = TextureFormat: /// Used to render all 3D meshes with materials that have no transparency. #[derive(PartialEq, Eq, Hash)] pub struct Opaque3dDeferred { - pub key: Opaque3dBinKey, + /// Determines which objects can be placed into a *batch set*. + /// + /// Objects in a single batch set can potentially be multi-drawn together, + /// if it's enabled and the current platform supports it. + pub batch_set_key: OpaqueNoLightmap3dBatchSetKey, + /// Information that separates items into bins. + pub bin_key: OpaqueNoLightmap3dBinKey, pub representative_entity: (Entity, MainEntity), pub batch_range: Range, pub extra_index: PhaseItemExtraIndex, @@ -44,7 +49,7 @@ impl PhaseItem for Opaque3dDeferred { #[inline] fn draw_function(&self) -> DrawFunctionId { - self.key.batch_set_key.draw_function + self.batch_set_key.draw_function } #[inline] @@ -69,17 +74,20 @@ impl PhaseItem for Opaque3dDeferred { } impl BinnedPhaseItem for Opaque3dDeferred { - type BinKey = Opaque3dBinKey; + type BatchSetKey = OpaqueNoLightmap3dBatchSetKey; + type BinKey = OpaqueNoLightmap3dBinKey; #[inline] fn new( - key: Self::BinKey, + batch_set_key: Self::BatchSetKey, + bin_key: Self::BinKey, representative_entity: (Entity, MainEntity), batch_range: Range, extra_index: PhaseItemExtraIndex, ) -> Self { Self { - key, + batch_set_key, + bin_key, representative_entity, batch_range, extra_index, @@ -90,7 +98,7 @@ impl BinnedPhaseItem for Opaque3dDeferred { impl CachedRenderPipelinePhaseItem for Opaque3dDeferred { #[inline] fn cached_pipeline(&self) -> CachedRenderPipelineId { - self.key.batch_set_key.pipeline + self.batch_set_key.pipeline } } @@ -100,7 +108,13 @@ impl CachedRenderPipelinePhaseItem for Opaque3dDeferred { /// /// Used to render all meshes with a material with an alpha mask. pub struct AlphaMask3dDeferred { - pub key: OpaqueNoLightmap3dBinKey, + /// Determines which objects can be placed into a *batch set*. + /// + /// Objects in a single batch set can potentially be multi-drawn together, + /// if it's enabled and the current platform supports it. + pub batch_set_key: OpaqueNoLightmap3dBatchSetKey, + /// Information that separates items into bins. + pub bin_key: OpaqueNoLightmap3dBinKey, pub representative_entity: (Entity, MainEntity), pub batch_range: Range, pub extra_index: PhaseItemExtraIndex, @@ -119,7 +133,7 @@ impl PhaseItem for AlphaMask3dDeferred { #[inline] fn draw_function(&self) -> DrawFunctionId { - self.key.batch_set_key.draw_function + self.batch_set_key.draw_function } #[inline] @@ -144,16 +158,19 @@ impl PhaseItem for AlphaMask3dDeferred { } impl BinnedPhaseItem for AlphaMask3dDeferred { + type BatchSetKey = OpaqueNoLightmap3dBatchSetKey; type BinKey = OpaqueNoLightmap3dBinKey; fn new( - key: Self::BinKey, + batch_set_key: Self::BatchSetKey, + bin_key: Self::BinKey, representative_entity: (Entity, MainEntity), batch_range: Range, extra_index: PhaseItemExtraIndex, ) -> Self { Self { - key, + batch_set_key, + bin_key, representative_entity, batch_range, extra_index, @@ -164,6 +181,6 @@ impl BinnedPhaseItem for AlphaMask3dDeferred { impl CachedRenderPipelinePhaseItem for AlphaMask3dDeferred { #[inline] fn cached_pipeline(&self) -> CachedRenderPipelineId { - self.key.batch_set_key.pipeline + self.batch_set_key.pipeline } } diff --git a/crates/bevy_core_pipeline/src/deferred/node.rs b/crates/bevy_core_pipeline/src/deferred/node.rs index 16704b647f..99287ae4f4 100644 --- a/crates/bevy_core_pipeline/src/deferred/node.rs +++ b/crates/bevy_core_pipeline/src/deferred/node.rs @@ -143,7 +143,8 @@ impl ViewNode for DeferredGBufferPrepassNode { } // Opaque draws - if !opaque_deferred_phase.batchable_mesh_keys.is_empty() + if !opaque_deferred_phase.multidrawable_mesh_keys.is_empty() + || !opaque_deferred_phase.batchable_mesh_keys.is_empty() || !opaque_deferred_phase.unbatchable_mesh_keys.is_empty() { #[cfg(feature = "trace")] diff --git a/crates/bevy_core_pipeline/src/prepass/mod.rs b/crates/bevy_core_pipeline/src/prepass/mod.rs index 78bac66df0..b90dea03a6 100644 --- a/crates/bevy_core_pipeline/src/prepass/mod.rs +++ b/crates/bevy_core_pipeline/src/prepass/mod.rs @@ -34,7 +34,7 @@ use bevy_asset::UntypedAssetId; use bevy_ecs::prelude::*; use bevy_math::Mat4; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; -use bevy_render::render_phase::PhaseItemBinKey; +use bevy_render::mesh::allocator::SlabId; use bevy_render::sync_world::MainEntity; use bevy_render::{ render_phase::{ @@ -139,8 +139,13 @@ impl ViewPrepassTextures { /// /// Used to render all 3D meshes with materials that have no transparency. pub struct Opaque3dPrepass { + /// Determines which objects can be placed into a *batch set*. + /// + /// Objects in a single batch set can potentially be multi-drawn together, + /// if it's enabled and the current platform supports it. + pub batch_set_key: OpaqueNoLightmap3dBatchSetKey, /// Information that separates items into bins. - pub key: OpaqueNoLightmap3dBinKey, + pub bin_key: OpaqueNoLightmap3dBinKey, /// An entity from which Bevy fetches data common to all instances in this /// batch, such as the mesh. @@ -166,30 +171,27 @@ pub struct OpaqueNoLightmap3dBatchSetKey { /// /// In the case of PBR, this is the `MaterialBindGroupIndex`. pub material_bind_group_index: Option, + + /// The ID of the slab of GPU memory that contains vertex data. + /// + /// For non-mesh items, you can fill this with 0 if your items can be + /// multi-drawn, or with a unique value if they can't. + pub vertex_slab: SlabId, + + /// The ID of the slab of GPU memory that contains index data, if present. + /// + /// For non-mesh items, you can safely fill this with `None`. + pub index_slab: Option, } // TODO: Try interning these. /// The data used to bin each opaque 3D object in the prepass and deferred pass. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct OpaqueNoLightmap3dBinKey { - /// The key of the *batch set*. - /// - /// As batches belong to a batch set, meshes in a batch must obviously be - /// able to be placed in a single batch set. - pub batch_set_key: OpaqueNoLightmap3dBatchSetKey, - /// The ID of the asset. pub asset_id: UntypedAssetId, } -impl PhaseItemBinKey for OpaqueNoLightmap3dBinKey { - type BatchSetKey = OpaqueNoLightmap3dBatchSetKey; - - fn get_batch_set_key(&self) -> Option { - Some(self.batch_set_key.clone()) - } -} - impl PhaseItem for Opaque3dPrepass { #[inline] fn entity(&self) -> Entity { @@ -202,7 +204,7 @@ impl PhaseItem for Opaque3dPrepass { #[inline] fn draw_function(&self) -> DrawFunctionId { - self.key.batch_set_key.draw_function + self.batch_set_key.draw_function } #[inline] @@ -227,17 +229,20 @@ impl PhaseItem for Opaque3dPrepass { } impl BinnedPhaseItem for Opaque3dPrepass { + type BatchSetKey = OpaqueNoLightmap3dBatchSetKey; type BinKey = OpaqueNoLightmap3dBinKey; #[inline] fn new( - key: Self::BinKey, + batch_set_key: Self::BatchSetKey, + bin_key: Self::BinKey, representative_entity: (Entity, MainEntity), batch_range: Range, extra_index: PhaseItemExtraIndex, ) -> Self { Opaque3dPrepass { - key, + batch_set_key, + bin_key, representative_entity, batch_range, extra_index, @@ -248,7 +253,7 @@ impl BinnedPhaseItem for Opaque3dPrepass { impl CachedRenderPipelinePhaseItem for Opaque3dPrepass { #[inline] fn cached_pipeline(&self) -> CachedRenderPipelineId { - self.key.batch_set_key.pipeline + self.batch_set_key.pipeline } } @@ -258,7 +263,13 @@ impl CachedRenderPipelinePhaseItem for Opaque3dPrepass { /// /// Used to render all meshes with a material with an alpha mask. pub struct AlphaMask3dPrepass { - pub key: OpaqueNoLightmap3dBinKey, + /// Determines which objects can be placed into a *batch set*. + /// + /// Objects in a single batch set can potentially be multi-drawn together, + /// if it's enabled and the current platform supports it. + pub batch_set_key: OpaqueNoLightmap3dBatchSetKey, + /// Information that separates items into bins. + pub bin_key: OpaqueNoLightmap3dBinKey, pub representative_entity: (Entity, MainEntity), pub batch_range: Range, pub extra_index: PhaseItemExtraIndex, @@ -276,7 +287,7 @@ impl PhaseItem for AlphaMask3dPrepass { #[inline] fn draw_function(&self) -> DrawFunctionId { - self.key.batch_set_key.draw_function + self.batch_set_key.draw_function } #[inline] @@ -301,17 +312,20 @@ impl PhaseItem for AlphaMask3dPrepass { } impl BinnedPhaseItem for AlphaMask3dPrepass { + type BatchSetKey = OpaqueNoLightmap3dBatchSetKey; type BinKey = OpaqueNoLightmap3dBinKey; #[inline] fn new( - key: Self::BinKey, + batch_set_key: Self::BatchSetKey, + bin_key: Self::BinKey, representative_entity: (Entity, MainEntity), batch_range: Range, extra_index: PhaseItemExtraIndex, ) -> Self { Self { - key, + batch_set_key, + bin_key, representative_entity, batch_range, extra_index, @@ -322,7 +336,7 @@ impl BinnedPhaseItem for AlphaMask3dPrepass { impl CachedRenderPipelinePhaseItem for AlphaMask3dPrepass { #[inline] fn cached_pipeline(&self) -> CachedRenderPipelineId { - self.key.batch_set_key.pipeline + self.batch_set_key.pipeline } } diff --git a/crates/bevy_core_pipeline/src/prepass/node.rs b/crates/bevy_core_pipeline/src/prepass/node.rs index c61663c198..4a32ebfc01 100644 --- a/crates/bevy_core_pipeline/src/prepass/node.rs +++ b/crates/bevy_core_pipeline/src/prepass/node.rs @@ -120,7 +120,8 @@ impl ViewNode for PrepassNode { } // Opaque draws - if !opaque_prepass_phase.batchable_mesh_keys.is_empty() + if !opaque_prepass_phase.multidrawable_mesh_keys.is_empty() + || !opaque_prepass_phase.batchable_mesh_keys.is_empty() || !opaque_prepass_phase.unbatchable_mesh_keys.is_empty() { #[cfg(feature = "trace")] diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 33772133cc..521ca52953 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -30,6 +30,7 @@ use bevy_ecs::{ use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::Reflect; use bevy_render::{ + batching::gpu_preprocessing::GpuPreprocessingSupport, camera::TemporalJitter, extract_resource::ExtractResource, mesh::{Mesh3d, MeshVertexBufferLayoutRef, RenderMesh}, @@ -631,9 +632,10 @@ pub fn queue_material_meshes( render_material_instances: Res>, render_lightmaps: Res, render_visibility_ranges: Res, - (mesh_allocator, material_bind_group_allocator): ( + (mesh_allocator, material_bind_group_allocator, gpu_preprocessing_support): ( Res, Res>, + Res, ), mut opaque_render_phases: ResMut>, mut alpha_mask_render_phases: ResMut>, @@ -871,22 +873,26 @@ pub fn queue_material_meshes( } else if material.properties.render_method == OpaqueRendererMethod::Forward { let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id); + let batch_set_key = Opaque3dBatchSetKey { + draw_function: draw_opaque_pbr, + pipeline: pipeline_id, + material_bind_group_index: Some(material.binding.group.0), + vertex_slab: vertex_slab.unwrap_or_default(), + index_slab, + lightmap_slab: lightmap_slab_index + .map(|lightmap_slab_index| *lightmap_slab_index), + }; let bin_key = Opaque3dBinKey { - batch_set_key: Opaque3dBatchSetKey { - draw_function: draw_opaque_pbr, - pipeline: pipeline_id, - material_bind_group_index: Some(material.binding.group.0), - vertex_slab: vertex_slab.unwrap_or_default(), - index_slab, - lightmap_slab: lightmap_slab_index - .map(|lightmap_slab_index| *lightmap_slab_index), - }, asset_id: mesh_instance.mesh_asset_id.into(), }; opaque_phase.add( + batch_set_key, bin_key, (*render_entity, *visible_entity), - BinnedRenderPhaseType::mesh(mesh_instance.should_batch()), + BinnedRenderPhaseType::mesh( + mesh_instance.should_batch(), + &gpu_preprocessing_support, + ), ); } } @@ -904,18 +910,26 @@ pub fn queue_material_meshes( extra_index: PhaseItemExtraIndex::None, }); } else if material.properties.render_method == OpaqueRendererMethod::Forward { + let (vertex_slab, index_slab) = + mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id); + let batch_set_key = OpaqueNoLightmap3dBatchSetKey { + draw_function: draw_alpha_mask_pbr, + pipeline: pipeline_id, + material_bind_group_index: Some(material.binding.group.0), + vertex_slab: vertex_slab.unwrap_or_default(), + index_slab, + }; let bin_key = OpaqueNoLightmap3dBinKey { - batch_set_key: OpaqueNoLightmap3dBatchSetKey { - draw_function: draw_alpha_mask_pbr, - pipeline: pipeline_id, - material_bind_group_index: Some(material.binding.group.0), - }, asset_id: mesh_instance.mesh_asset_id.into(), }; alpha_mask_phase.add( + batch_set_key, bin_key, (*render_entity, *visible_entity), - BinnedRenderPhaseType::mesh(mesh_instance.should_batch()), + BinnedRenderPhaseType::mesh( + mesh_instance.should_batch(), + &gpu_preprocessing_support, + ), ); } } diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 7a430b2132..2e5a082de7 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -2,6 +2,7 @@ mod prepass_bindings; use crate::material_bind_groups::MaterialBindGroupAllocator; use bevy_render::{ + batching::gpu_preprocessing::GpuPreprocessingSupport, mesh::{allocator::MeshAllocator, Mesh3d, MeshVertexBufferLayoutRef, RenderMesh}, render_resource::binding_types::uniform_buffer, renderer::RenderAdapter, @@ -12,10 +13,7 @@ pub use prepass_bindings::*; use bevy_asset::{load_internal_asset, AssetServer}; use bevy_core_pipeline::{ - core_3d::{Opaque3dBatchSetKey, Opaque3dBinKey, CORE_3D_DEPTH_FORMAT}, - deferred::*, - prelude::Camera3d, - prepass::*, + core_3d::CORE_3D_DEPTH_FORMAT, deferred::*, prelude::Camera3d, prepass::*, }; use bevy_ecs::{ prelude::*, @@ -773,8 +771,10 @@ pub fn queue_prepass_material_meshes( prepass_pipeline: Res>, mut pipelines: ResMut>>, pipeline_cache: Res, - render_meshes: Res>, - render_mesh_instances: Res, + (render_meshes, render_mesh_instances): ( + Res>, + Res, + ), render_materials: Res>>, render_material_instances: Res>, render_lightmaps: Res, @@ -783,6 +783,7 @@ pub fn queue_prepass_material_meshes( Res, Res>, ), + gpu_preprocessing_support: Res, mut opaque_prepass_render_phases: ResMut>, mut alpha_mask_prepass_render_phases: ResMut>, mut opaque_deferred_render_phases: ResMut>, @@ -966,65 +967,89 @@ pub fn queue_prepass_material_meshes( let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id); opaque_deferred_phase.as_mut().unwrap().add( - Opaque3dBinKey { - batch_set_key: Opaque3dBatchSetKey { - draw_function: opaque_draw_deferred, - pipeline: pipeline_id, - material_bind_group_index: Some(material.binding.group.0), - vertex_slab: vertex_slab.unwrap_or_default(), - index_slab, - lightmap_slab: lightmap_slab_index - .map(|lightmap_slab_index| *lightmap_slab_index), - }, + OpaqueNoLightmap3dBatchSetKey { + draw_function: opaque_draw_deferred, + pipeline: pipeline_id, + material_bind_group_index: Some(material.binding.group.0), + vertex_slab: vertex_slab.unwrap_or_default(), + index_slab, + }, + OpaqueNoLightmap3dBinKey { asset_id: mesh_instance.mesh_asset_id.into(), }, (*render_entity, *visible_entity), - BinnedRenderPhaseType::mesh(mesh_instance.should_batch()), + BinnedRenderPhaseType::mesh( + mesh_instance.should_batch(), + &gpu_preprocessing_support, + ), ); } else if let Some(opaque_phase) = opaque_phase.as_mut() { + let (vertex_slab, index_slab) = + mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id); opaque_phase.add( + OpaqueNoLightmap3dBatchSetKey { + draw_function: opaque_draw_prepass, + pipeline: pipeline_id, + material_bind_group_index: Some(material.binding.group.0), + vertex_slab: vertex_slab.unwrap_or_default(), + index_slab, + }, OpaqueNoLightmap3dBinKey { - batch_set_key: OpaqueNoLightmap3dBatchSetKey { - draw_function: opaque_draw_prepass, - pipeline: pipeline_id, - material_bind_group_index: Some(material.binding.group.0), - }, asset_id: mesh_instance.mesh_asset_id.into(), }, (*render_entity, *visible_entity), - BinnedRenderPhaseType::mesh(mesh_instance.should_batch()), + BinnedRenderPhaseType::mesh( + mesh_instance.should_batch(), + &gpu_preprocessing_support, + ), ); } } // Alpha mask MeshPipelineKey::MAY_DISCARD => { if deferred { + let (vertex_slab, index_slab) = + mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id); + let batch_set_key = OpaqueNoLightmap3dBatchSetKey { + draw_function: alpha_mask_draw_deferred, + pipeline: pipeline_id, + material_bind_group_index: Some(material.binding.group.0), + vertex_slab: vertex_slab.unwrap_or_default(), + index_slab, + }; let bin_key = OpaqueNoLightmap3dBinKey { - batch_set_key: OpaqueNoLightmap3dBatchSetKey { - draw_function: alpha_mask_draw_deferred, - pipeline: pipeline_id, - material_bind_group_index: Some(material.binding.group.0), - }, asset_id: mesh_instance.mesh_asset_id.into(), }; alpha_mask_deferred_phase.as_mut().unwrap().add( + batch_set_key, bin_key, (*render_entity, *visible_entity), - BinnedRenderPhaseType::mesh(mesh_instance.should_batch()), + BinnedRenderPhaseType::mesh( + mesh_instance.should_batch(), + &gpu_preprocessing_support, + ), ); } else if let Some(alpha_mask_phase) = alpha_mask_phase.as_mut() { + let (vertex_slab, index_slab) = + mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id); + let batch_set_key = OpaqueNoLightmap3dBatchSetKey { + draw_function: alpha_mask_draw_prepass, + pipeline: pipeline_id, + material_bind_group_index: Some(material.binding.group.0), + vertex_slab: vertex_slab.unwrap_or_default(), + index_slab, + }; let bin_key = OpaqueNoLightmap3dBinKey { - batch_set_key: OpaqueNoLightmap3dBatchSetKey { - draw_function: alpha_mask_draw_prepass, - pipeline: pipeline_id, - material_bind_group_index: Some(material.binding.group.0), - }, asset_id: mesh_instance.mesh_asset_id.into(), }; alpha_mask_phase.add( + batch_set_key, bin_key, (*render_entity, *visible_entity), - BinnedRenderPhaseType::mesh(mesh_instance.should_batch()), + BinnedRenderPhaseType::mesh( + mesh_instance.should_batch(), + &gpu_preprocessing_support, + ), ); } } diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 1316b9c6d5..7f7434a9f7 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -1536,11 +1536,9 @@ fn despawn_entities(commands: &mut Commands, entities: Vec) { pub fn queue_shadows( shadow_draw_functions: Res>, prepass_pipeline: Res>, - (render_meshes, render_mesh_instances): ( + (render_meshes, render_mesh_instances, render_materials, render_material_instances): ( Res>, Res, - ), - (render_materials, render_material_instances): ( Res>>, Res>, ), @@ -1549,6 +1547,7 @@ pub fn queue_shadows( mut pipelines: ResMut>>, pipeline_cache: Res, render_lightmaps: Res, + gpu_preprocessing_support: Res, mesh_allocator: Res, view_lights: Query<(Entity, &ViewLightEntities)>, view_light_entities: Query<&LightEntity>, @@ -1670,18 +1669,23 @@ pub fn queue_shadows( let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id); + let batch_set_key = ShadowBatchSetKey { + pipeline: pipeline_id, + draw_function: draw_shadow_mesh, + vertex_slab: vertex_slab.unwrap_or_default(), + index_slab, + }; + shadow_phase.add( + batch_set_key, ShadowBinKey { - batch_set_key: ShadowBatchSetKey { - pipeline: pipeline_id, - draw_function: draw_shadow_mesh, - vertex_slab: vertex_slab.unwrap_or_default(), - index_slab, - }, asset_id: mesh_instance.mesh_asset_id.into(), }, (entity, main_entity), - BinnedRenderPhaseType::mesh(mesh_instance.should_batch()), + BinnedRenderPhaseType::mesh( + mesh_instance.should_batch(), + &gpu_preprocessing_support, + ), ); } } @@ -1689,7 +1693,13 @@ pub fn queue_shadows( } pub struct Shadow { - pub key: ShadowBinKey, + /// Determines which objects can be placed into a *batch set*. + /// + /// Objects in a single batch set can potentially be multi-drawn together, + /// if it's enabled and the current platform supports it. + pub batch_set_key: ShadowBatchSetKey, + /// Information that separates items into bins. + pub bin_key: ShadowBinKey, pub representative_entity: (Entity, MainEntity), pub batch_range: Range, pub extra_index: PhaseItemExtraIndex, @@ -1723,24 +1733,10 @@ pub struct ShadowBatchSetKey { /// Data used to bin each object in the shadow map phase. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct ShadowBinKey { - /// The key of the *batch set*. - /// - /// As batches belong to a batch set, meshes in a batch must obviously be - /// able to be placed in a single batch set. - pub batch_set_key: ShadowBatchSetKey, - /// The object. pub asset_id: UntypedAssetId, } -impl PhaseItemBinKey for ShadowBinKey { - type BatchSetKey = ShadowBatchSetKey; - - fn get_batch_set_key(&self) -> Option { - Some(self.batch_set_key.clone()) - } -} - impl PhaseItem for Shadow { #[inline] fn entity(&self) -> Entity { @@ -1753,7 +1749,7 @@ impl PhaseItem for Shadow { #[inline] fn draw_function(&self) -> DrawFunctionId { - self.key.batch_set_key.draw_function + self.batch_set_key.draw_function } #[inline] @@ -1778,17 +1774,20 @@ impl PhaseItem for Shadow { } impl BinnedPhaseItem for Shadow { + type BatchSetKey = ShadowBatchSetKey; type BinKey = ShadowBinKey; #[inline] fn new( - key: Self::BinKey, + batch_set_key: Self::BatchSetKey, + bin_key: Self::BinKey, representative_entity: (Entity, MainEntity), batch_range: Range, extra_index: PhaseItemExtraIndex, ) -> Self { Shadow { - key, + batch_set_key, + bin_key, representative_entity, batch_range, extra_index, @@ -1799,7 +1798,7 @@ impl BinnedPhaseItem for Shadow { impl CachedRenderPipelinePhaseItem for Shadow { #[inline] fn cached_pipeline(&self) -> CachedRenderPipelineId { - self.key.batch_set_key.pipeline + self.batch_set_key.pipeline } } diff --git a/crates/bevy_render/src/batching/gpu_preprocessing.rs b/crates/bevy_render/src/batching/gpu_preprocessing.rs index 6a7f994e97..659630b4d7 100644 --- a/crates/bevy_render/src/batching/gpu_preprocessing.rs +++ b/crates/bevy_render/src/batching/gpu_preprocessing.rs @@ -17,9 +17,9 @@ use wgpu::{BindingResource, BufferUsages, DownlevelFlags, Features}; use crate::{ render_phase::{ - BinnedPhaseItem, BinnedRenderPhaseBatch, BinnedRenderPhaseBatchSets, - CachedRenderPipelinePhaseItem, PhaseItemBinKey as _, PhaseItemExtraIndex, SortedPhaseItem, - SortedRenderPhase, UnbatchableBinnedEntityIndices, ViewBinnedRenderPhases, + BinnedPhaseItem, BinnedRenderPhaseBatch, BinnedRenderPhaseBatchSet, + BinnedRenderPhaseBatchSets, CachedRenderPipelinePhaseItem, PhaseItemExtraIndex, + SortedPhaseItem, SortedRenderPhase, UnbatchableBinnedEntityIndices, ViewBinnedRenderPhases, ViewSortedRenderPhases, }, render_resource::{Buffer, BufferVec, GpuArrayBufferable, RawBufferVec, UninitBufferVec}, @@ -708,14 +708,88 @@ pub fn batch_and_prepare_binned_render_phase( no_indirect_drawing, }); - // Prepare batchables. + // Prepare multidrawables. - // If multi-draw is in use, as we step through the list of batchables, - // we gather adjacent batches that have the same *batch set* key into - // batch sets. This variable stores the last batch set key that we've - // seen. If our current batch set key is identical to this one, we can - // merge the current batch into the last batch set. - let mut maybe_last_multidraw_key = None; + for batch_set_key in &phase.multidrawable_mesh_keys { + let mut batch_set = None; + for (bin_key, bin) in &phase.multidrawable_mesh_values[batch_set_key] { + let first_output_index = data_buffer.len() as u32; + let mut batch: Option = None; + + for &(entity, main_entity) in &bin.entities { + let Some(input_index) = GFBD::get_binned_index(&system_param_item, main_entity) + else { + continue; + }; + let output_index = data_buffer.add() as u32; + + match batch { + Some(ref mut batch) => { + // Append to the current batch. + batch.instance_range.end = output_index + 1; + work_item_buffer.buffer.push(PreprocessWorkItem { + input_index: input_index.into(), + output_index: first_output_index, + indirect_parameters_index: match batch.extra_index { + PhaseItemExtraIndex::IndirectParametersIndex(ref range) => { + range.start + } + PhaseItemExtraIndex::DynamicOffset(_) + | PhaseItemExtraIndex::None => 0, + }, + }); + } + + None => { + // Start a new batch, in indirect mode. + let indirect_parameters_index = indirect_parameters_buffer.allocate(1); + GFBD::write_batch_indirect_parameters( + &system_param_item, + &mut indirect_parameters_buffer, + indirect_parameters_index, + main_entity, + ); + work_item_buffer.buffer.push(PreprocessWorkItem { + input_index: input_index.into(), + output_index: first_output_index, + indirect_parameters_index, + }); + batch = Some(BinnedRenderPhaseBatch { + representative_entity: (entity, main_entity), + instance_range: output_index..output_index + 1, + extra_index: PhaseItemExtraIndex::maybe_indirect_parameters_index( + NonMaxU32::new(indirect_parameters_index), + ), + }); + } + } + } + + if let Some(batch) = batch { + match batch_set { + None => { + batch_set = Some(BinnedRenderPhaseBatchSet { + batches: vec![batch], + bin_key: bin_key.clone(), + }); + } + Some(ref mut batch_set) => { + batch_set.batches.push(batch); + } + } + } + } + + if let BinnedRenderPhaseBatchSets::MultidrawIndirect(ref mut batch_sets) = + phase.batch_sets + { + if let Some(batch_set) = batch_set { + batch_sets.push(batch_set); + } + } + } + + // Prepare batchables. for key in &phase.batchable_mesh_keys { let first_output_index = data_buffer.len() as u32; @@ -803,21 +877,15 @@ pub fn batch_and_prepare_binned_render_phase( BinnedRenderPhaseBatchSets::Direct(ref mut vec) => { vec.push(batch); } - BinnedRenderPhaseBatchSets::MultidrawIndirect(ref mut batch_sets) => { - // We're in multi-draw mode. Check to see whether our - // batch set key is the same as the last one. If so, - // merge this batch into the preceding batch set. - match (&maybe_last_multidraw_key, key.get_batch_set_key()) { - (Some(ref last_multidraw_key), Some(this_multidraw_key)) - if *last_multidraw_key == this_multidraw_key => - { - batch_sets.last_mut().unwrap().push(batch); - } - (_, maybe_this_multidraw_key) => { - maybe_last_multidraw_key = maybe_this_multidraw_key; - batch_sets.push(vec![batch]); - } - } + BinnedRenderPhaseBatchSets::MultidrawIndirect(ref mut vec) => { + // The Bevy renderer will never mark a mesh as batchable + // but not multidrawable if multidraw is in use. + // However, custom render pipelines might do so, such as + // the `specialized_mesh_pipeline` example. + vec.push(BinnedRenderPhaseBatchSet { + batches: vec![batch], + bin_key: key.1.clone(), + }); } } } diff --git a/crates/bevy_render/src/batching/mod.rs b/crates/bevy_render/src/batching/mod.rs index f43a85510a..590ed94293 100644 --- a/crates/bevy_render/src/batching/mod.rs +++ b/crates/bevy_render/src/batching/mod.rs @@ -161,6 +161,7 @@ where BPI: BinnedPhaseItem, { for phase in phases.values_mut() { + phase.multidrawable_mesh_keys.sort_unstable(); phase.batchable_mesh_keys.sort_unstable(); phase.unbatchable_mesh_keys.sort_unstable(); } diff --git a/crates/bevy_render/src/batching/no_gpu_preprocessing.rs b/crates/bevy_render/src/batching/no_gpu_preprocessing.rs index 1f39c387dc..d1e6c5b248 100644 --- a/crates/bevy_render/src/batching/no_gpu_preprocessing.rs +++ b/crates/bevy_render/src/batching/no_gpu_preprocessing.rs @@ -145,7 +145,7 @@ pub fn batch_and_prepare_binned_render_phase( batch_sets.push(batch_set); } BinnedRenderPhaseBatchSets::Direct(_) - | BinnedRenderPhaseBatchSets::MultidrawIndirect(_) => { + | BinnedRenderPhaseBatchSets::MultidrawIndirect { .. } => { error!( "Dynamic uniform batch sets should be used when GPU preprocessing is off" ); diff --git a/crates/bevy_render/src/render_phase/mod.rs b/crates/bevy_render/src/render_phase/mod.rs index 72c09bef4b..e3814eb75f 100644 --- a/crates/bevy_render/src/render_phase/mod.rs +++ b/crates/bevy_render/src/render_phase/mod.rs @@ -37,7 +37,7 @@ use encase::{internal::WriteInto, ShaderSize}; use nonmax::NonMaxU32; pub use rangefinder::*; -use crate::batching::gpu_preprocessing::GpuPreprocessingMode; +use crate::batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport}; use crate::sync_world::MainEntity; use crate::{ batching::{ @@ -85,28 +85,55 @@ pub struct BinnedRenderPhase where BPI: BinnedPhaseItem, { - /// A list of `BinKey`s for batchable items. + /// A list of `BatchSetKey`s for batchable, multidrawable items. + /// + /// These are accumulated in `queue_material_meshes` and then sorted in + /// `batching::sort_binned_render_phase`. + pub multidrawable_mesh_keys: Vec, + + /// The multidrawable bins themselves. + /// + /// Each batch set key maps to a *batch set*, which in this case is a set of + /// meshes that can be drawn together in one multidraw call. Each batch set + /// is subdivided into *bins*, each of which represents a particular mesh. + /// Each bin contains the entity IDs of instances of that mesh. + /// + /// So, for example, if there are two cubes and a sphere present in the + /// scene, we would generally have one batch set containing two bins, + /// assuming that the cubes and sphere meshes are allocated together and use + /// the same pipeline. The first bin, corresponding to the cubes, will have + /// two entities in it. The second bin, corresponding to the sphere, will + /// have one entity in it. + pub multidrawable_mesh_values: HashMap>, + + /// A list of `BinKey`s for batchable items that aren't multidrawable. /// /// These are accumulated in `queue_material_meshes` and then sorted in /// `batch_and_prepare_binned_render_phase`. - pub batchable_mesh_keys: Vec, - - /// The batchable bins themselves. /// - /// Each bin corresponds to a single batch set. For unbatchable entities, - /// prefer `unbatchable_values` instead. - pub batchable_mesh_values: HashMap, + /// Usually, batchable items aren't multidrawable due to platform or + /// hardware limitations. However, it's also possible to have batchable + /// items alongside multidrawable items with custom mesh pipelines. See + /// `specialized_mesh_pipeline` for an example. + pub batchable_mesh_keys: Vec<(BPI::BatchSetKey, BPI::BinKey)>, + + /// The bins corresponding to batchable items that aren't multidrawable. + /// + /// For multidrawable entities, use `multidrawable_mesh_values`; for + /// unbatchable entities, use `unbatchable_values`. + pub batchable_mesh_values: HashMap<(BPI::BatchSetKey, BPI::BinKey), RenderBin>, /// A list of `BinKey`s for unbatchable items. /// /// These are accumulated in `queue_material_meshes` and then sorted in /// `batch_and_prepare_binned_render_phase`. - pub unbatchable_mesh_keys: Vec, + pub unbatchable_mesh_keys: Vec<(BPI::BatchSetKey, BPI::BinKey)>, /// The unbatchable bins. /// /// Each entity here is rendered in a separate drawcall. - pub unbatchable_mesh_values: HashMap, + pub unbatchable_mesh_values: + HashMap<(BPI::BatchSetKey, BPI::BinKey), UnbatchableBinnedEntities>, /// Items in the bin that aren't meshes at all. /// @@ -115,7 +142,7 @@ where /// entity are simply called in order at rendering time. /// /// See the `custom_phase_item` example for an example of how to use this. - pub non_mesh_items: Vec<(BPI::BinKey, (Entity, MainEntity))>, + pub non_mesh_items: Vec<(BPI::BatchSetKey, BPI::BinKey, (Entity, MainEntity))>, /// Information on each batch set. /// @@ -125,13 +152,14 @@ where /// platforms that support storage buffers, a batch set always consists of /// at most one batch. /// - /// The unbatchable entities immediately follow the batches in the storage - /// buffers. - pub(crate) batch_sets: BinnedRenderPhaseBatchSets, + /// Multidrawable entities come first, then batchable entities, then + /// unbatchable entities. + pub(crate) batch_sets: BinnedRenderPhaseBatchSets, } /// All entities that share a mesh and a material and can be batched as part of /// a [`BinnedRenderPhase`]. +#[derive(Default)] pub struct RenderBin { /// A list of the entities in each bin. pub entities: Vec<(Entity, MainEntity)>, @@ -140,7 +168,7 @@ pub struct RenderBin { /// How we store and render the batch sets. /// /// Each one of these corresponds to a [`GpuPreprocessingMode`]. -pub enum BinnedRenderPhaseBatchSets { +pub enum BinnedRenderPhaseBatchSets { /// Batches are grouped into batch sets based on dynamic uniforms. /// /// This corresponds to [`GpuPreprocessingMode::None`]. @@ -155,10 +183,15 @@ pub enum BinnedRenderPhaseBatchSets { /// be multi-drawn together. /// /// This corresponds to [`GpuPreprocessingMode::Culling`]. - MultidrawIndirect(Vec>), + MultidrawIndirect(Vec>), } -impl BinnedRenderPhaseBatchSets { +pub struct BinnedRenderPhaseBatchSet { + pub(crate) batches: Vec, + pub(crate) bin_key: BK, +} + +impl BinnedRenderPhaseBatchSets { fn clear(&mut self) { match *self { BinnedRenderPhaseBatchSets::DynamicUniforms(ref mut vec) => vec.clear(), @@ -244,8 +277,12 @@ pub(crate) struct UnbatchableBinnedEntityIndices { /// placed in. #[derive(Clone, Copy, PartialEq, Debug)] pub enum BinnedRenderPhaseType { - /// The item is a mesh that's eligible for indirect rendering and can be - /// batched with other meshes of the same type. + /// The item is a mesh that's eligible for multi-draw indirect rendering and + /// can be batched with other meshes of the same type. + MultidrawableMesh, + + /// The item is a mesh that's eligible for single-draw indirect rendering + /// and can be batched with other meshes of the same type. BatchableMesh, /// The item is a mesh that's eligible for indirect rendering, but can't be @@ -310,18 +347,46 @@ where /// type. pub fn add( &mut self, - key: BPI::BinKey, + batch_set_key: BPI::BatchSetKey, + bin_key: BPI::BinKey, (entity, main_entity): (Entity, MainEntity), phase_type: BinnedRenderPhaseType, ) { match phase_type { + BinnedRenderPhaseType::MultidrawableMesh => { + match self.multidrawable_mesh_values.entry(batch_set_key.clone()) { + Entry::Occupied(mut entry) => { + entry + .get_mut() + .entry(bin_key) + .or_default() + .entities + .push((entity, main_entity)); + } + Entry::Vacant(entry) => { + self.multidrawable_mesh_keys.push(batch_set_key); + let mut new_batch_set = HashMap::default(); + new_batch_set.insert( + bin_key, + RenderBin { + entities: vec![(entity, main_entity)], + }, + ); + entry.insert(new_batch_set); + } + } + } + BinnedRenderPhaseType::BatchableMesh => { - match self.batchable_mesh_values.entry(key.clone()) { + match self + .batchable_mesh_values + .entry((batch_set_key.clone(), bin_key.clone()).clone()) + { Entry::Occupied(mut entry) => { entry.get_mut().entities.push((entity, main_entity)); } Entry::Vacant(entry) => { - self.batchable_mesh_keys.push(key); + self.batchable_mesh_keys.push((batch_set_key, bin_key)); entry.insert(RenderBin { entities: vec![(entity, main_entity)], }); @@ -330,12 +395,15 @@ where } BinnedRenderPhaseType::UnbatchableMesh => { - match self.unbatchable_mesh_values.entry(key.clone()) { + match self + .unbatchable_mesh_values + .entry((batch_set_key.clone(), bin_key.clone())) + { Entry::Occupied(mut entry) => { entry.get_mut().entities.push((entity, main_entity)); } Entry::Vacant(entry) => { - self.unbatchable_mesh_keys.push(key); + self.unbatchable_mesh_keys.push((batch_set_key, bin_key)); entry.insert(UnbatchableBinnedEntities { entities: vec![(entity, main_entity)], buffer_indices: default(), @@ -346,7 +414,8 @@ where BinnedRenderPhaseType::NonMesh => { // We don't process these items further. - self.non_mesh_items.push((key, (entity, main_entity))); + self.non_mesh_items + .push((batch_set_key, bin_key, (entity, main_entity))); } } } @@ -387,10 +456,13 @@ where BinnedRenderPhaseBatchSets::DynamicUniforms(ref batch_sets) => { debug_assert_eq!(self.batchable_mesh_keys.len(), batch_sets.len()); - for (key, batch_set) in self.batchable_mesh_keys.iter().zip(batch_sets.iter()) { + for ((batch_set_key, bin_key), batch_set) in + self.batchable_mesh_keys.iter().zip(batch_sets.iter()) + { for batch in batch_set { let binned_phase_item = BPI::new( - key.clone(), + batch_set_key.clone(), + bin_key.clone(), batch.representative_entity, batch.instance_range.clone(), batch.extra_index.clone(), @@ -409,9 +481,12 @@ where } BinnedRenderPhaseBatchSets::Direct(ref batch_set) => { - for (batch, key) in batch_set.iter().zip(self.batchable_mesh_keys.iter()) { + for (batch, (batch_set_key, bin_key)) in + batch_set.iter().zip(self.batchable_mesh_keys.iter()) + { let binned_phase_item = BPI::new( - key.clone(), + batch_set_key.clone(), + bin_key.clone(), batch.representative_entity, batch.instance_range.clone(), batch.extra_index.clone(), @@ -429,17 +504,23 @@ where } BinnedRenderPhaseBatchSets::MultidrawIndirect(ref batch_sets) => { - let mut batchable_mesh_key_index = 0; - for batch_set in batch_sets.iter() { - let Some(batch) = batch_set.first() else { + for (batch_set_key, batch_set) in self + .multidrawable_mesh_keys + .iter() + .chain( + self.batchable_mesh_keys + .iter() + .map(|(batch_set_key, _)| batch_set_key), + ) + .zip(batch_sets.iter()) + { + let Some(batch) = batch_set.batches.first() else { continue; }; - let key = &self.batchable_mesh_keys[batchable_mesh_key_index]; - batchable_mesh_key_index += batch_set.len(); - let binned_phase_item = BPI::new( - key.clone(), + batch_set_key.clone(), + batch_set.bin_key.clone(), batch.representative_entity, batch.instance_range.clone(), match batch.extra_index { @@ -449,7 +530,7 @@ where } PhaseItemExtraIndex::IndirectParametersIndex(ref range) => { PhaseItemExtraIndex::IndirectParametersIndex( - range.start..(range.start + batch_set.len() as u32), + range.start..(range.start + batch_set.batches.len() as u32), ) } }, @@ -480,8 +561,9 @@ where let draw_functions = world.resource::>(); let mut draw_functions = draw_functions.write(); - for key in &self.unbatchable_mesh_keys { - let unbatchable_entities = &self.unbatchable_mesh_values[key]; + for (batch_set_key, bin_key) in &self.unbatchable_mesh_keys { + let unbatchable_entities = + &self.unbatchable_mesh_values[&(batch_set_key.clone(), bin_key.clone())]; for (entity_index, &entity) in unbatchable_entities.entities.iter().enumerate() { let unbatchable_dynamic_offset = match &unbatchable_entities.buffer_indices { UnbatchableBinnedEntityIndexSet::NoEntities => { @@ -512,7 +594,8 @@ where }; let binned_phase_item = BPI::new( - key.clone(), + batch_set_key.clone(), + bin_key.clone(), entity, unbatchable_dynamic_offset.instance_index ..(unbatchable_dynamic_offset.instance_index + 1), @@ -543,10 +626,16 @@ where let draw_functions = world.resource::>(); let mut draw_functions = draw_functions.write(); - for &(ref key, entity) in &self.non_mesh_items { + for &(ref batch_set_key, ref bin_key, entity) in &self.non_mesh_items { // Come up with a fake batch range and extra index. The draw // function is expected to manage any sort of batching logic itself. - let binned_phase_item = BPI::new(key.clone(), entity, 0..1, PhaseItemExtraIndex::None); + let binned_phase_item = BPI::new( + batch_set_key.clone(), + bin_key.clone(), + entity, + 0..1, + PhaseItemExtraIndex::None, + ); let Some(draw_function) = draw_functions.get_mut(binned_phase_item.draw_function()) else { @@ -560,12 +649,15 @@ where } pub fn is_empty(&self) -> bool { - self.batchable_mesh_keys.is_empty() + self.multidrawable_mesh_keys.is_empty() + && self.batchable_mesh_keys.is_empty() && self.unbatchable_mesh_keys.is_empty() && self.non_mesh_items.is_empty() } pub fn clear(&mut self) { + self.multidrawable_mesh_keys.clear(); + self.multidrawable_mesh_values.clear(); self.batchable_mesh_keys.clear(); self.batchable_mesh_values.clear(); self.unbatchable_mesh_keys.clear(); @@ -581,6 +673,8 @@ where { fn new(gpu_preprocessing: GpuPreprocessingMode) -> Self { Self { + multidrawable_mesh_keys: vec![], + multidrawable_mesh_values: HashMap::default(), batchable_mesh_keys: vec![], batchable_mesh_values: HashMap::default(), unbatchable_mesh_keys: vec![], @@ -1072,7 +1166,9 @@ pub trait BinnedPhaseItem: PhaseItem { /// lowest variable bind group id such as the material bind group id, and /// its dynamic offsets if any, next bind group and offsets, etc. This /// reduces the need for rebinding between bins and improves performance. - type BinKey: PhaseItemBinKey; + type BinKey: Clone + Send + Sync + PartialEq + Eq + Ord + Hash; + + type BatchSetKey: Clone + Send + Sync + PartialEq + Eq + Ord + Hash; /// Creates a new binned phase item from the key and per-entity data. /// @@ -1080,33 +1176,14 @@ pub trait BinnedPhaseItem: PhaseItem { /// before rendering. The resulting phase item isn't stored in any data /// structures, resulting in significant memory savings. fn new( - key: Self::BinKey, + batch_set_key: Self::BatchSetKey, + bin_key: Self::BinKey, representative_entity: (Entity, MainEntity), batch_range: Range, extra_index: PhaseItemExtraIndex, ) -> Self; } -/// A trait that allows fetching the *batch set key* from a bin key. -/// -/// A *batch set* is a set of mesh batches that will be rendered with multi-draw -/// if multi-draw is in use. The *batch set key* is the data that has to be -/// identical between meshes in order to place them in the same batch set. A -/// batch set can therefore span multiple bins. -/// -/// The batch set key should be at the beginning of the bin key structure so -/// that batches in the same batch set will be adjacent to one another in the -/// sorted list of bins. -pub trait PhaseItemBinKey: Clone + Send + Sync + PartialEq + Eq + Ord + Hash { - type BatchSetKey: Clone + PartialEq; - - /// Returns the batch set key, if applicable. - /// - /// If this returns `None`, no batches in this phase item can be grouped - /// together into batch sets. - fn get_batch_set_key(&self) -> Option; -} - /// Represents phase items that must be sorted. The `SortKey` specifies the /// order that these items are drawn in. These are placed into a single array, /// and the array as a whole is then sorted. @@ -1189,13 +1266,14 @@ where } impl BinnedRenderPhaseType { - /// Creates the appropriate [`BinnedRenderPhaseType`] for a mesh, given its - /// batchability. - pub fn mesh(batchable: bool) -> BinnedRenderPhaseType { - if batchable { - BinnedRenderPhaseType::BatchableMesh - } else { - BinnedRenderPhaseType::UnbatchableMesh + pub fn mesh( + batchable: bool, + gpu_preprocessing_support: &GpuPreprocessingSupport, + ) -> BinnedRenderPhaseType { + match (batchable, gpu_preprocessing_support.max_supported_mode) { + (true, GpuPreprocessingMode::Culling) => BinnedRenderPhaseType::MultidrawableMesh, + (true, _) => BinnedRenderPhaseType::BatchableMesh, + (false, _) => BinnedRenderPhaseType::UnbatchableMesh, } } } diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index 2e648e513a..977e4a81bf 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -15,7 +15,6 @@ use bevy_ecs::{ }; use bevy_math::FloatOrd; use bevy_reflect::{prelude::ReflectDefault, Reflect}; -use bevy_render::view::RenderVisibleEntities; use bevy_render::{ mesh::{MeshVertexBufferLayoutRef, RenderMesh}, render_asset::{ @@ -32,7 +31,7 @@ use bevy_render::{ SpecializedMeshPipelineError, SpecializedMeshPipelines, }, renderer::RenderDevice, - view::{ExtractedView, Msaa, ViewVisibility}, + view::{ExtractedView, Msaa, RenderVisibleEntities, ViewVisibility}, Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; use bevy_render::{render_resource::BindingResources, sync_world::MainEntityHashMap}; @@ -473,8 +472,10 @@ pub fn queue_material2d_meshes( material2d_pipeline: Res>, mut pipelines: ResMut>>, pipeline_cache: Res, - render_meshes: Res>, - render_materials: Res>>, + (render_meshes, render_materials): ( + Res>, + Res>>, + ), mut render_mesh_instances: ResMut, render_material_instances: Res>, mut transparent_render_phases: ResMut>, @@ -560,6 +561,17 @@ pub fn queue_material2d_meshes( mesh_instance.material_bind_group_id = material_2d.get_bind_group_id(); let mesh_z = mesh_instance.transforms.world_from_local.translation.z; + // We don't support multidraw yet for 2D meshes, so we use this + // custom logic to generate the `BinnedRenderPhaseType` instead of + // `BinnedRenderPhaseType::mesh`, which can return + // `BinnedRenderPhaseType::MultidrawableMesh` if the hardware + // supports multidraw. + let binned_render_phase_type = if mesh_instance.automatic_batching { + BinnedRenderPhaseType::BatchableMesh + } else { + BinnedRenderPhaseType::UnbatchableMesh + }; + match material_2d.properties.alpha_mode { AlphaMode2d::Opaque => { let bin_key = Opaque2dBinKey { @@ -569,9 +581,10 @@ pub fn queue_material2d_meshes( material_bind_group_id: material_2d.get_bind_group_id().0, }; opaque_phase.add( + (), bin_key, (*render_entity, *visible_entity), - BinnedRenderPhaseType::mesh(mesh_instance.automatic_batching), + binned_render_phase_type, ); } AlphaMode2d::Mask(_) => { @@ -582,9 +595,10 @@ pub fn queue_material2d_meshes( material_bind_group_id: material_2d.get_bind_group_id().0, }; alpha_mask_phase.add( + (), bin_key, (*render_entity, *visible_entity), - BinnedRenderPhaseType::mesh(mesh_instance.automatic_batching), + binned_render_phase_type, ); } AlphaMode2d::Blend => { diff --git a/examples/shader/custom_phase_item.rs b/examples/shader/custom_phase_item.rs index 3eb8b6a754..98d858bc3a 100644 --- a/examples/shader/custom_phase_item.rs +++ b/examples/shader/custom_phase_item.rs @@ -259,15 +259,15 @@ fn queue_custom_phase_item( // but you can use anything you like. Note that the asset ID need // not be the ID of a [`Mesh`]. opaque_phase.add( + Opaque3dBatchSetKey { + draw_function: draw_custom_phase_item, + pipeline: pipeline_id, + material_bind_group_index: None, + lightmap_slab: None, + vertex_slab: default(), + index_slab: None, + }, Opaque3dBinKey { - batch_set_key: Opaque3dBatchSetKey { - draw_function: draw_custom_phase_item, - pipeline: pipeline_id, - material_bind_group_index: None, - lightmap_slab: None, - vertex_slab: default(), - index_slab: None, - }, asset_id: AssetId::::invalid().untyped(), }, entity, diff --git a/examples/shader/specialized_mesh_pipeline.rs b/examples/shader/specialized_mesh_pipeline.rs index 8e9913eb2b..322a041fcf 100644 --- a/examples/shader/specialized_mesh_pipeline.rs +++ b/examples/shader/specialized_mesh_pipeline.rs @@ -325,18 +325,18 @@ fn queue_custom_mesh_pipeline( // Add the mesh with our specialized pipeline opaque_phase.add( + Opaque3dBatchSetKey { + draw_function: draw_function_id, + pipeline: pipeline_id, + material_bind_group_index: None, + vertex_slab: default(), + index_slab: None, + lightmap_slab: None, + }, + // The asset ID is arbitrary; we simply use [`AssetId::invalid`], + // but you can use anything you like. Note that the asset ID need + // not be the ID of a [`Mesh`]. Opaque3dBinKey { - batch_set_key: Opaque3dBatchSetKey { - draw_function: draw_function_id, - pipeline: pipeline_id, - material_bind_group_index: None, - vertex_slab: default(), - index_slab: None, - lightmap_slab: None, - }, - // The asset ID is arbitrary; we simply use [`AssetId::invalid`], - // but you can use anything you like. Note that the asset ID need - // not be the ID of a [`Mesh`]. asset_id: AssetId::::invalid().untyped(), }, (render_entity, visible_entity), diff --git a/examples/shader/storage_buffer.rs b/examples/shader/storage_buffer.rs index 4cb3275d7e..87e9e1081b 100644 --- a/examples/shader/storage_buffer.rs +++ b/examples/shader/storage_buffer.rs @@ -1,4 +1,6 @@ //! This example demonstrates how to use a storage buffer with `AsBindGroup` in a custom material. +use std::array; + use bevy::{ prelude::*, reflect::TypePath, @@ -37,19 +39,25 @@ fn setup( let colors = buffers.add(ShaderStorageBuffer::from(color_data)); // Create the custom material with the storage buffer - let custom_material = CustomMaterial { colors }; + let material_handles: [Handle; 5] = array::from_fn(|color_id| { + materials.add(CustomMaterial { + colors: colors.clone(), + color_id: color_id as u32, + }) + }); - let material_handle = materials.add(custom_material); - commands.insert_resource(CustomMaterialHandle(material_handle.clone())); + commands.insert_resource(CustomMaterialHandles(material_handles.clone())); // Spawn cubes with the custom material + let mut current_color_id = 0; for i in -6..=6 { for j in -3..=3 { commands.spawn(( Mesh3d(meshes.add(Cuboid::from_size(Vec3::splat(0.3)))), - MeshMaterial3d(material_handle.clone()), + MeshMaterial3d(material_handles[current_color_id % 5].clone()), Transform::from_xyz(i as f32, j as f32, 0.0), )); + current_color_id += 1; } } @@ -63,11 +71,18 @@ fn setup( // Update the material color by time fn update( time: Res