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 `()`.
This commit is contained in:
Patrick Walton 2025-01-06 10:34:40 -08:00 committed by GitHub
parent 21c1b6a1e8
commit a8f15bd95e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 565 additions and 312 deletions

View File

@ -4,6 +4,7 @@
}
@group(2) @binding(0) var<storage, read> colors: array<vec4<f32>, 5>;
@group(2) @binding(1) var<uniform> 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;
}

View File

@ -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<BindGroupId>,
}
impl PhaseItemBinKey for Opaque2dBinKey {
type BatchSetKey = ();
fn get_batch_set_key(&self) -> Option<Self::BatchSetKey> {
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<u32>,
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<u32>,
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<Self::BatchSetKey> {
None
}
}
impl CachedRenderPipelinePhaseItem for AlphaMask2d {
#[inline]
fn cached_pipeline(&self) -> CachedRenderPipelineId {
self.key.pipeline
self.bin_key.pipeline
}
}

View File

@ -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<Self::BatchSetKey> {
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<u32>,
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<u32>,
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<u32>,
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
}
}

View File

@ -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<u32>,
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<u32>,
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<u32>,
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<u32>,
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
}
}

View File

@ -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")]

View File

@ -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<u32>,
/// 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<SlabId>,
}
// 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<Self::BatchSetKey> {
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<u32>,
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<u32>,
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<u32>,
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
}
}

View File

@ -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")]

View File

@ -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<M: Material>(
render_material_instances: Res<RenderMaterialInstances<M>>,
render_lightmaps: Res<RenderLightmaps>,
render_visibility_ranges: Res<RenderVisibilityRanges>,
(mesh_allocator, material_bind_group_allocator): (
(mesh_allocator, material_bind_group_allocator, gpu_preprocessing_support): (
Res<MeshAllocator>,
Res<MaterialBindGroupAllocator<M>>,
Res<GpuPreprocessingSupport>,
),
mut opaque_render_phases: ResMut<ViewBinnedRenderPhases<Opaque3d>>,
mut alpha_mask_render_phases: ResMut<ViewBinnedRenderPhases<AlphaMask3d>>,
@ -871,8 +873,7 @@ pub fn queue_material_meshes<M: Material>(
} else if material.properties.render_method == OpaqueRendererMethod::Forward {
let (vertex_slab, index_slab) =
mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
let bin_key = Opaque3dBinKey {
batch_set_key: Opaque3dBatchSetKey {
let batch_set_key = Opaque3dBatchSetKey {
draw_function: draw_opaque_pbr,
pipeline: pipeline_id,
material_bind_group_index: Some(material.binding.group.0),
@ -880,13 +881,18 @@ pub fn queue_material_meshes<M: Material>(
index_slab,
lightmap_slab: lightmap_slab_index
.map(|lightmap_slab_index| *lightmap_slab_index),
},
};
let bin_key = Opaque3dBinKey {
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<M: Material>(
extra_index: PhaseItemExtraIndex::None,
});
} else if material.properties.render_method == OpaqueRendererMethod::Forward {
let bin_key = OpaqueNoLightmap3dBinKey {
batch_set_key: OpaqueNoLightmap3dBatchSetKey {
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 {
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,
),
);
}
}

View File

@ -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<M: Material>(
prepass_pipeline: Res<PrepassPipeline<M>>,
mut pipelines: ResMut<SpecializedMeshPipelines<PrepassPipeline<M>>>,
pipeline_cache: Res<PipelineCache>,
render_meshes: Res<RenderAssets<RenderMesh>>,
render_mesh_instances: Res<RenderMeshInstances>,
(render_meshes, render_mesh_instances): (
Res<RenderAssets<RenderMesh>>,
Res<RenderMeshInstances>,
),
render_materials: Res<RenderAssets<PreparedMaterial<M>>>,
render_material_instances: Res<RenderMaterialInstances<M>>,
render_lightmaps: Res<RenderLightmaps>,
@ -783,6 +783,7 @@ pub fn queue_prepass_material_meshes<M: Material>(
Res<MeshAllocator>,
Res<MaterialBindGroupAllocator<M>>,
),
gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
mut opaque_prepass_render_phases: ResMut<ViewBinnedRenderPhases<Opaque3dPrepass>>,
mut alpha_mask_prepass_render_phases: ResMut<ViewBinnedRenderPhases<AlphaMask3dPrepass>>,
mut opaque_deferred_render_phases: ResMut<ViewBinnedRenderPhases<Opaque3dDeferred>>,
@ -966,65 +967,89 @@ pub fn queue_prepass_material_meshes<M: Material>(
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 {
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,
lightmap_slab: lightmap_slab_index
.map(|lightmap_slab_index| *lightmap_slab_index),
},
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(
OpaqueNoLightmap3dBinKey {
batch_set_key: OpaqueNoLightmap3dBatchSetKey {
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 {
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 bin_key = OpaqueNoLightmap3dBinKey {
batch_set_key: OpaqueNoLightmap3dBatchSetKey {
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 {
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 bin_key = OpaqueNoLightmap3dBinKey {
batch_set_key: OpaqueNoLightmap3dBatchSetKey {
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 {
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,
),
);
}
}

View File

@ -1536,11 +1536,9 @@ fn despawn_entities(commands: &mut Commands, entities: Vec<Entity>) {
pub fn queue_shadows<M: Material>(
shadow_draw_functions: Res<DrawFunctions<Shadow>>,
prepass_pipeline: Res<PrepassPipeline<M>>,
(render_meshes, render_mesh_instances): (
(render_meshes, render_mesh_instances, render_materials, render_material_instances): (
Res<RenderAssets<RenderMesh>>,
Res<RenderMeshInstances>,
),
(render_materials, render_material_instances): (
Res<RenderAssets<PreparedMaterial<M>>>,
Res<RenderMaterialInstances<M>>,
),
@ -1549,6 +1547,7 @@ pub fn queue_shadows<M: Material>(
mut pipelines: ResMut<SpecializedMeshPipelines<PrepassPipeline<M>>>,
pipeline_cache: Res<PipelineCache>,
render_lightmaps: Res<RenderLightmaps>,
gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
mesh_allocator: Res<MeshAllocator>,
view_lights: Query<(Entity, &ViewLightEntities)>,
view_light_entities: Query<&LightEntity>,
@ -1670,18 +1669,23 @@ pub fn queue_shadows<M: Material>(
let (vertex_slab, index_slab) =
mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
shadow_phase.add(
ShadowBinKey {
batch_set_key: ShadowBatchSetKey {
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 {
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<M: Material>(
}
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<u32>,
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<Self::BatchSetKey> {
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<u32>,
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
}
}

View File

@ -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<BPI, GFBD>(
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<BinnedRenderPhaseBatch> = 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<BPI, GFBD>(
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(),
});
}
}
}

View File

@ -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();
}

View File

@ -145,7 +145,7 @@ pub fn batch_and_prepare_binned_render_phase<BPI, GFBD>(
batch_sets.push(batch_set);
}
BinnedRenderPhaseBatchSets::Direct(_)
| BinnedRenderPhaseBatchSets::MultidrawIndirect(_) => {
| BinnedRenderPhaseBatchSets::MultidrawIndirect { .. } => {
error!(
"Dynamic uniform batch sets should be used when GPU preprocessing is off"
);

View File

@ -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<BPI>
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<BPI::BatchSetKey>,
/// 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<BPI::BatchSetKey, HashMap<BPI::BinKey, RenderBin>>,
/// 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<BPI::BinKey>,
/// The batchable bins themselves.
///
/// Each bin corresponds to a single batch set. For unbatchable entities,
/// prefer `unbatchable_values` instead.
pub batchable_mesh_values: HashMap<BPI::BinKey, RenderBin>,
/// 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<BPI::BinKey>,
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<BPI::BinKey, UnbatchableBinnedEntities>,
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<BPI::BinKey>,
}
/// 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<BK> {
/// 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<Vec<BinnedRenderPhaseBatch>>),
MultidrawIndirect(Vec<BinnedRenderPhaseBatchSet<BK>>),
}
impl BinnedRenderPhaseBatchSets {
pub struct BinnedRenderPhaseBatchSet<BK> {
pub(crate) batches: Vec<BinnedRenderPhaseBatch>,
pub(crate) bin_key: BK,
}
impl<BK> BinnedRenderPhaseBatchSets<BK> {
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::<DrawFunctions<BPI>>();
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::<DrawFunctions<BPI>>();
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<u32>,
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<Self::BatchSetKey>;
}
/// 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,
}
}
}

View File

@ -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<M: Material2d>(
material2d_pipeline: Res<Material2dPipeline<M>>,
mut pipelines: ResMut<SpecializedMeshPipelines<Material2dPipeline<M>>>,
pipeline_cache: Res<PipelineCache>,
render_meshes: Res<RenderAssets<RenderMesh>>,
render_materials: Res<RenderAssets<PreparedMaterial2d<M>>>,
(render_meshes, render_materials): (
Res<RenderAssets<RenderMesh>>,
Res<RenderAssets<PreparedMaterial2d<M>>>,
),
mut render_mesh_instances: ResMut<RenderMesh2dInstances>,
render_material_instances: Res<RenderMaterial2dInstances<M>>,
mut transparent_render_phases: ResMut<ViewSortedRenderPhases<Transparent2d>>,
@ -560,6 +561,17 @@ pub fn queue_material2d_meshes<M: Material2d>(
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<M: Material2d>(
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<M: Material2d>(
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 => {

View File

@ -259,8 +259,7 @@ 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(
Opaque3dBinKey {
batch_set_key: Opaque3dBatchSetKey {
Opaque3dBatchSetKey {
draw_function: draw_custom_phase_item,
pipeline: pipeline_id,
material_bind_group_index: None,
@ -268,6 +267,7 @@ fn queue_custom_phase_item(
vertex_slab: default(),
index_slab: None,
},
Opaque3dBinKey {
asset_id: AssetId::<Mesh>::invalid().untyped(),
},
entity,

View File

@ -325,8 +325,7 @@ fn queue_custom_mesh_pipeline(
// Add the mesh with our specialized pipeline
opaque_phase.add(
Opaque3dBinKey {
batch_set_key: Opaque3dBatchSetKey {
Opaque3dBatchSetKey {
draw_function: draw_function_id,
pipeline: pipeline_id,
material_bind_group_index: None,
@ -337,6 +336,7 @@ fn queue_custom_mesh_pipeline(
// 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 {
asset_id: AssetId::<Mesh>::invalid().untyped(),
},
(render_entity, visible_entity),

View File

@ -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<CustomMaterial>; 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<Time>,
material_handle: Res<CustomMaterialHandle>,
material_handles: Res<CustomMaterialHandles>,
mut materials: ResMut<Assets<CustomMaterial>>,
mut buffers: ResMut<Assets<ShaderStorageBuffer>>,
) {
let material = materials.get_mut(&material_handle.0).unwrap();
// All materials use the same buffer, so we only need to update one of them.
// But we do need to at least mark the others as changed, so that Bevy will
// reupload their contents to the GPU.
for material in &material_handles.0[1..] {
materials.get_mut(material);
}
let material = materials.get_mut(&material_handles.0[0]).unwrap();
let buffer = buffers.get_mut(&material.colors).unwrap();
buffer.set_data(
(0..5)
@ -85,15 +100,17 @@ fn update(
);
}
// Holds a handle to the custom material
// Holds handles to the custom materials
#[derive(Resource)]
struct CustomMaterialHandle(Handle<CustomMaterial>);
struct CustomMaterialHandles([Handle<CustomMaterial>; 5]);
// This struct defines the data that will be passed to your shader
#[derive(Asset, TypePath, AsBindGroup, Debug, Clone)]
struct CustomMaterial {
#[storage(0, read_only)]
colors: Handle<ShaderStorageBuffer>,
#[uniform(1)]
color_id: u32,
}
impl Material for CustomMaterial {