diff --git a/crates/bevy_core_pipeline/src/core_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs index c9883ce441cb6..0e9c064af68a9 100644 --- a/crates/bevy_core_pipeline/src/core_2d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -33,6 +33,8 @@ pub mod graph { use core::ops::Range; use bevy_asset::UntypedAssetId; +use bevy_render::batching::gpu_preprocessing::GpuPreprocessingMode; +use bevy_render::render_phase::PhaseItemBinKey; use bevy_utils::HashMap; pub use camera_2d::*; pub use main_opaque_pass_2d_node::*; @@ -153,6 +155,14 @@ 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 { @@ -179,7 +189,7 @@ impl PhaseItem for Opaque2d { } fn extra_index(&self) -> PhaseItemExtraIndex { - self.extra_index + self.extra_index.clone() } fn batch_range_and_extra_index_mut(&mut self) -> (&mut Range, &mut PhaseItemExtraIndex) { @@ -269,7 +279,7 @@ impl PhaseItem for AlphaMask2d { } fn extra_index(&self) -> PhaseItemExtraIndex { - self.extra_index + self.extra_index.clone() } fn batch_range_and_extra_index_mut(&mut self) -> (&mut Range, &mut PhaseItemExtraIndex) { @@ -295,6 +305,14 @@ 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 { @@ -340,7 +358,7 @@ impl PhaseItem for Transparent2d { #[inline] fn extra_index(&self) -> PhaseItemExtraIndex { - self.extra_index + self.extra_index.clone() } #[inline] @@ -385,8 +403,8 @@ pub fn extract_core_2d_camera_phases( continue; } transparent_2d_phases.insert_or_clear(entity); - opaque_2d_phases.insert_or_clear(entity); - alpha_mask_2d_phases.insert_or_clear(entity); + opaque_2d_phases.insert_or_clear(entity, GpuPreprocessingMode::None); + alpha_mask_2d_phases.insert_or_clear(entity, GpuPreprocessingMode::None); live_entities.insert(entity); } diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index e0e8fe9b05655..909306dd4259c 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -65,6 +65,10 @@ pub const DEPTH_TEXTURE_SAMPLING_SUPPORTED: bool = true; use core::ops::Range; +use bevy_render::batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport}; +use bevy_render::mesh::allocator::SlabId; +use bevy_render::render_phase::PhaseItemBinKey; +use bevy_render::view::GpuCulling; pub use camera_3d::*; pub use main_opaque_pass_3d_node::*; pub use main_transparent_pass_3d_node::*; @@ -224,9 +228,13 @@ pub struct Opaque3d { pub extra_index: PhaseItemExtraIndex, } -/// Data that must be identical in order to batch phase items together. +/// Information that must be identical in order to place opaque meshes in the +/// same *batch set*. +/// +/// A batch set is a set of batches that can be multi-drawn together, if +/// multi-draw is in use. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Opaque3dBinKey { +pub struct Opaque3dBatchSetKey { /// The identifier of the render pipeline. pub pipeline: CachedRenderPipelineId, @@ -238,14 +246,45 @@ pub struct Opaque3dBinKey { /// 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, + + /// The lightmap, if present. + pub lightmap_image: Option>, +} + +/// Data that must be identical in order to *batch* phase items together. +/// +/// 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 /// the ID of another type of asset. pub asset_id: UntypedAssetId, +} - /// The lightmap, if present. - pub lightmap_image: Option>, +impl PhaseItemBinKey for Opaque3dBinKey { + type BatchSetKey = Opaque3dBatchSetKey; + + fn get_batch_set_key(&self) -> Option { + Some(self.batch_set_key.clone()) + } } impl PhaseItem for Opaque3d { @@ -261,7 +300,7 @@ impl PhaseItem for Opaque3d { #[inline] fn draw_function(&self) -> DrawFunctionId { - self.key.draw_function + self.key.batch_set_key.draw_function } #[inline] @@ -275,7 +314,7 @@ impl PhaseItem for Opaque3d { } fn extra_index(&self) -> PhaseItemExtraIndex { - self.extra_index + self.extra_index.clone() } fn batch_range_and_extra_index_mut(&mut self) -> (&mut Range, &mut PhaseItemExtraIndex) { @@ -305,7 +344,7 @@ impl BinnedPhaseItem for Opaque3d { impl CachedRenderPipelinePhaseItem for Opaque3d { #[inline] fn cached_pipeline(&self) -> CachedRenderPipelineId { - self.key.pipeline + self.key.batch_set_key.pipeline } } @@ -343,7 +382,7 @@ impl PhaseItem for AlphaMask3d { #[inline] fn extra_index(&self) -> PhaseItemExtraIndex { - self.extra_index + self.extra_index.clone() } #[inline] @@ -426,7 +465,7 @@ impl PhaseItem for Transmissive3d { #[inline] fn extra_index(&self) -> PhaseItemExtraIndex { - self.extra_index + self.extra_index.clone() } #[inline] @@ -493,7 +532,7 @@ impl PhaseItem for Transparent3d { #[inline] fn extra_index(&self) -> PhaseItemExtraIndex { - self.extra_index + self.extra_index.clone() } #[inline] @@ -529,18 +568,27 @@ pub fn extract_core_3d_camera_phases( mut alpha_mask_3d_phases: ResMut>, mut transmissive_3d_phases: ResMut>, mut transparent_3d_phases: ResMut>, - cameras_3d: Extract>>, + cameras_3d: Extract), With>>, mut live_entities: Local, + gpu_preprocessing_support: Res, ) { live_entities.clear(); - for (entity, camera) in &cameras_3d { + for (entity, camera, has_gpu_culling) in &cameras_3d { if !camera.is_active { continue; } - opaque_3d_phases.insert_or_clear(entity); - alpha_mask_3d_phases.insert_or_clear(entity); + // If GPU culling is in use, use it (and indirect mode); otherwise, just + // preprocess the meshes. + let gpu_preprocessing_mode = gpu_preprocessing_support.min(if has_gpu_culling { + GpuPreprocessingMode::Culling + } else { + GpuPreprocessingMode::PreprocessingOnly + }); + + opaque_3d_phases.insert_or_clear(entity, gpu_preprocessing_mode); + alpha_mask_3d_phases.insert_or_clear(entity, gpu_preprocessing_mode); transmissive_3d_phases.insert_or_clear(entity); transparent_3d_phases.insert_or_clear(entity); @@ -554,6 +602,8 @@ pub fn extract_core_3d_camera_phases( } // Extract the render phases for the prepass + +#[allow(clippy::too_many_arguments)] pub fn extract_camera_prepass_phase( mut commands: Commands, mut opaque_3d_prepass_phases: ResMut>, @@ -565,6 +615,7 @@ pub fn extract_camera_prepass_phase( ( RenderEntity, &Camera, + Has, Has, Has, Has, @@ -574,27 +625,43 @@ pub fn extract_camera_prepass_phase( >, >, mut live_entities: Local, + gpu_preprocessing_support: Res, ) { live_entities.clear(); - for (entity, camera, depth_prepass, normal_prepass, motion_vector_prepass, deferred_prepass) in - cameras_3d.iter() + for ( + entity, + camera, + gpu_culling, + depth_prepass, + normal_prepass, + motion_vector_prepass, + deferred_prepass, + ) in cameras_3d.iter() { if !camera.is_active { continue; } + // If GPU culling is in use, use it (and indirect mode); otherwise, just + // preprocess the meshes. + let gpu_preprocessing_mode = gpu_preprocessing_support.min(if gpu_culling { + GpuPreprocessingMode::Culling + } else { + GpuPreprocessingMode::PreprocessingOnly + }); + if depth_prepass || normal_prepass || motion_vector_prepass { - opaque_3d_prepass_phases.insert_or_clear(entity); - alpha_mask_3d_prepass_phases.insert_or_clear(entity); + opaque_3d_prepass_phases.insert_or_clear(entity, gpu_preprocessing_mode); + alpha_mask_3d_prepass_phases.insert_or_clear(entity, gpu_preprocessing_mode); } else { opaque_3d_prepass_phases.remove(&entity); alpha_mask_3d_prepass_phases.remove(&entity); } if deferred_prepass { - opaque_3d_deferred_phases.insert_or_clear(entity); - alpha_mask_3d_deferred_phases.insert_or_clear(entity); + opaque_3d_deferred_phases.insert_or_clear(entity, gpu_preprocessing_mode); + alpha_mask_3d_deferred_phases.insert_or_clear(entity, gpu_preprocessing_mode); } else { opaque_3d_deferred_phases.remove(&entity); alpha_mask_3d_deferred_phases.remove(&entity); diff --git a/crates/bevy_core_pipeline/src/deferred/mod.rs b/crates/bevy_core_pipeline/src/deferred/mod.rs index 6ae4108d80492..e21291c9af7b8 100644 --- a/crates/bevy_core_pipeline/src/deferred/mod.rs +++ b/crates/bevy_core_pipeline/src/deferred/mod.rs @@ -58,7 +58,7 @@ impl PhaseItem for Opaque3dDeferred { #[inline] fn extra_index(&self) -> PhaseItemExtraIndex { - self.extra_index + self.extra_index.clone() } #[inline] @@ -133,7 +133,7 @@ impl PhaseItem for AlphaMask3dDeferred { #[inline] fn extra_index(&self) -> PhaseItemExtraIndex { - self.extra_index + self.extra_index.clone() } #[inline] diff --git a/crates/bevy_core_pipeline/src/prepass/mod.rs b/crates/bevy_core_pipeline/src/prepass/mod.rs index cc056baab0bfc..93598d10b9230 100644 --- a/crates/bevy_core_pipeline/src/prepass/mod.rs +++ b/crates/bevy_core_pipeline/src/prepass/mod.rs @@ -34,6 +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::sync_world::MainEntity; use bevy_render::{ render_phase::{ @@ -167,6 +168,14 @@ pub struct OpaqueNoLightmap3dBinKey { pub asset_id: UntypedAssetId, } +impl PhaseItemBinKey for OpaqueNoLightmap3dBinKey { + type BatchSetKey = (); + + fn get_batch_set_key(&self) -> Option { + None + } +} + impl PhaseItem for Opaque3dPrepass { #[inline] fn entity(&self) -> Entity { @@ -194,7 +203,7 @@ impl PhaseItem for Opaque3dPrepass { #[inline] fn extra_index(&self) -> PhaseItemExtraIndex { - self.extra_index + self.extra_index.clone() } #[inline] @@ -268,7 +277,7 @@ impl PhaseItem for AlphaMask3dPrepass { #[inline] fn extra_index(&self) -> PhaseItemExtraIndex { - self.extra_index + self.extra_index.clone() } #[inline] diff --git a/crates/bevy_gizmos/src/pipeline_2d.rs b/crates/bevy_gizmos/src/pipeline_2d.rs index 754653ffd5d27..d4420a86889b5 100644 --- a/crates/bevy_gizmos/src/pipeline_2d.rs +++ b/crates/bevy_gizmos/src/pipeline_2d.rs @@ -338,7 +338,7 @@ fn queue_line_gizmos_2d( pipeline, sort_key: FloatOrd(f32::INFINITY), batch_range: 0..1, - extra_index: PhaseItemExtraIndex::NONE, + extra_index: PhaseItemExtraIndex::None, }); } @@ -358,7 +358,7 @@ fn queue_line_gizmos_2d( pipeline, sort_key: FloatOrd(f32::INFINITY), batch_range: 0..1, - extra_index: PhaseItemExtraIndex::NONE, + extra_index: PhaseItemExtraIndex::None, }); } } @@ -417,7 +417,7 @@ fn queue_line_joint_gizmos_2d( pipeline, sort_key: FloatOrd(f32::INFINITY), batch_range: 0..1, - extra_index: PhaseItemExtraIndex::NONE, + extra_index: PhaseItemExtraIndex::None, }); } } diff --git a/crates/bevy_gizmos/src/pipeline_3d.rs b/crates/bevy_gizmos/src/pipeline_3d.rs index 7d7f3d9c3c84e..7b960e173c5f2 100644 --- a/crates/bevy_gizmos/src/pipeline_3d.rs +++ b/crates/bevy_gizmos/src/pipeline_3d.rs @@ -369,7 +369,7 @@ fn queue_line_gizmos_3d( pipeline, distance: 0., batch_range: 0..1, - extra_index: PhaseItemExtraIndex::NONE, + extra_index: PhaseItemExtraIndex::None, }); } @@ -390,7 +390,7 @@ fn queue_line_gizmos_3d( pipeline, distance: 0., batch_range: 0..1, - extra_index: PhaseItemExtraIndex::NONE, + extra_index: PhaseItemExtraIndex::None, }); } } @@ -486,7 +486,7 @@ fn queue_line_joint_gizmos_3d( pipeline, distance: 0., batch_range: 0..1, - extra_index: PhaseItemExtraIndex::NONE, + extra_index: PhaseItemExtraIndex::None, }); } } diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index bb1b11bd115a8..22bb037d7ce4c 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -9,8 +9,8 @@ use crate::*; use bevy_asset::{Asset, AssetId, AssetServer}; use bevy_core_pipeline::{ core_3d::{ - AlphaMask3d, Camera3d, Opaque3d, Opaque3dBinKey, ScreenSpaceTransmissionQuality, - Transmissive3d, Transparent3d, + AlphaMask3d, Camera3d, Opaque3d, Opaque3dBatchSetKey, Opaque3dBinKey, + ScreenSpaceTransmissionQuality, Transmissive3d, Transparent3d, }, oit::OrderIndependentTransparencySettings, prepass::{ @@ -28,7 +28,6 @@ use bevy_ecs::{ }; use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::Reflect; -use bevy_render::view::RenderVisibleEntities; use bevy_render::{ camera::TemporalJitter, extract_resource::ExtractResource, @@ -40,7 +39,8 @@ use bevy_render::{ view::{ExtractedView, Msaa, RenderVisibilityRanges, ViewVisibility}, Extract, }; -use bevy_render::{sync_world::MainEntityHashMap, texture::FallbackImage}; +use bevy_render::{mesh::allocator::MeshAllocator, sync_world::MainEntityHashMap}; +use bevy_render::{texture::FallbackImage, view::RenderVisibleEntities}; use bevy_utils::{hashbrown::hash_map::Entry, tracing::error}; use core::{hash::Hash, marker::PhantomData}; @@ -634,7 +634,10 @@ pub fn queue_material_meshes( render_material_instances: Res>, render_lightmaps: Res, render_visibility_ranges: Res, - material_bind_group_allocator: Res>, + (mesh_allocator, material_bind_group_allocator): ( + Res, + Res>, + ), mut opaque_render_phases: ResMut>, mut alpha_mask_render_phases: ResMut>, mut transmissive_render_phases: ResMut>, @@ -865,15 +868,21 @@ pub fn queue_material_meshes( pipeline: pipeline_id, distance, batch_range: 0..1, - extra_index: PhaseItemExtraIndex::NONE, + 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 bin_key = Opaque3dBinKey { - draw_function: draw_opaque_pbr, - pipeline: pipeline_id, + 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_image, + }, asset_id: mesh_instance.mesh_asset_id.into(), - material_bind_group_index: Some(material.binding.group.0), - lightmap_image, }; opaque_phase.add( bin_key, @@ -893,7 +902,7 @@ pub fn queue_material_meshes( pipeline: pipeline_id, distance, batch_range: 0..1, - extra_index: PhaseItemExtraIndex::NONE, + extra_index: PhaseItemExtraIndex::None, }); } else if material.properties.render_method == OpaqueRendererMethod::Forward { let bin_key = OpaqueNoLightmap3dBinKey { @@ -918,7 +927,7 @@ pub fn queue_material_meshes( pipeline: pipeline_id, distance, batch_range: 0..1, - extra_index: PhaseItemExtraIndex::NONE, + extra_index: PhaseItemExtraIndex::None, }); } } diff --git a/crates/bevy_pbr/src/render/gpu_preprocess.rs b/crates/bevy_pbr/src/render/gpu_preprocess.rs index c4381c010637a..41a32af5df17d 100644 --- a/crates/bevy_pbr/src/render/gpu_preprocess.rs +++ b/crates/bevy_pbr/src/render/gpu_preprocess.rs @@ -137,9 +137,7 @@ impl Plugin for GpuMeshPreprocessPlugin { // This plugin does nothing if GPU instance buffer building isn't in // use. let gpu_preprocessing_support = render_app.world().resource::(); - if !self.use_gpu_instance_buffer_builder - || *gpu_preprocessing_support == GpuPreprocessingSupport::None - { + if !self.use_gpu_instance_buffer_builder || !gpu_preprocessing_support.is_available() { return; } diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 9223441552e8c..c2a61d16e08e2 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -11,8 +11,11 @@ use bevy_ecs::{ system::lifetimeless::Read, }; use bevy_math::{ops, Mat4, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles}; -use bevy_render::camera::SortedCameras; -use bevy_render::sync_world::{MainEntity, RenderEntity, TemporaryRenderEntity}; +use bevy_render::{ + batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport}, + camera::SortedCameras, + mesh::allocator::MeshAllocator, +}; use bevy_render::{ diagnostic::RecordDiagnostics, mesh::RenderMesh, @@ -26,6 +29,10 @@ use bevy_render::{ view::{ExtractedView, RenderLayers, ViewVisibility}, Extract, }; +use bevy_render::{ + mesh::allocator::SlabId, + sync_world::{MainEntity, RenderEntity, TemporaryRenderEntity}, +}; use bevy_transform::{components::GlobalTransform, prelude::Transform}; #[cfg(feature = "trace")] use bevy_utils::tracing::info_span; @@ -673,8 +680,7 @@ pub(crate) fn spot_light_clip_from_view(angle: f32, near_z: f32) -> Mat4 { pub fn prepare_lights( mut commands: Commands, mut texture_cache: ResMut, - render_device: Res, - render_queue: Res, + (render_device, render_queue): (Res, Res), mut global_light_meta: ResMut, mut light_meta: ResMut, views: Query< @@ -703,6 +709,7 @@ pub fn prepare_lights( directional_lights: Query<(Entity, &ExtractedDirectionalLight)>, mut light_view_entities: Query<&mut LightViewEntities>, sorted_cameras: Res, + gpu_preprocessing_support: Res, ) { let views_iter = views.iter(); let views_count = views_iter.len(); @@ -1229,7 +1236,11 @@ pub fn prepare_lights( if first { // Subsequent views with the same light entity will reuse the same shadow map - shadow_render_phases.insert_or_clear(view_light_entity); + // TODO: Implement GPU culling for shadow passes. + shadow_render_phases.insert_or_clear( + view_light_entity, + gpu_preprocessing_support.min(GpuPreprocessingMode::PreprocessingOnly), + ); live_shadow_mapping_lights.insert(view_light_entity); } } @@ -1317,7 +1328,10 @@ pub fn prepare_lights( if first { // Subsequent views with the same light entity will reuse the same shadow map - shadow_render_phases.insert_or_clear(view_light_entity); + shadow_render_phases.insert_or_clear( + view_light_entity, + gpu_preprocessing_support.min(GpuPreprocessingMode::PreprocessingOnly), + ); live_shadow_mapping_lights.insert(view_light_entity); } } @@ -1447,7 +1461,11 @@ pub fn prepare_lights( // Subsequent views with the same light entity will **NOT** reuse the same shadow map // (Because the cascades are unique to each view) - shadow_render_phases.insert_or_clear(view_light_entity); + // TODO: Implement GPU culling for shadow passes. + shadow_render_phases.insert_or_clear( + view_light_entity, + gpu_preprocessing_support.min(GpuPreprocessingMode::PreprocessingOnly), + ); live_shadow_mapping_lights.insert(view_light_entity); } } @@ -1498,15 +1516,20 @@ fn despawn_entities(commands: &mut Commands, entities: Vec) { pub fn queue_shadows( shadow_draw_functions: Res>, prepass_pipeline: Res>, - render_meshes: Res>, - render_mesh_instances: Res, - render_materials: Res>>, - render_material_instances: Res>, + (render_meshes, render_mesh_instances): ( + Res>, + Res, + ), + (render_materials, render_material_instances): ( + Res>>, + Res>, + ), material_bind_group_allocator: Res>, mut shadow_render_phases: ResMut>, mut pipelines: ResMut>>, pipeline_cache: Res, render_lightmaps: Res, + mesh_allocator: Res, view_lights: Query<(Entity, &ViewLightEntities)>, view_light_entities: Query<&LightEntity>, point_light_entities: Query<&RenderCubemapVisibleEntities, With>, @@ -1624,10 +1647,17 @@ pub fn queue_shadows( } }; + let (vertex_slab, index_slab) = + mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id); + shadow_phase.add( ShadowBinKey { - draw_function: draw_shadow_mesh, - pipeline: pipeline_id, + 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), @@ -1645,19 +1675,52 @@ pub struct Shadow { pub extra_index: PhaseItemExtraIndex, } -/// Data used to bin each object in the shadow map phase. +/// Information that must be identical in order to place opaque meshes in the +/// same *batch set*. +/// +/// A batch set is a set of batches that can be multi-drawn together, if +/// multi-draw is in use. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct ShadowBinKey { +pub struct ShadowBatchSetKey { /// The identifier of the render pipeline. pub pipeline: CachedRenderPipelineId, /// The function used to draw. pub draw_function: DrawFunctionId, + /// 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, +} + +/// 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 { @@ -1670,7 +1733,7 @@ impl PhaseItem for Shadow { #[inline] fn draw_function(&self) -> DrawFunctionId { - self.key.draw_function + self.key.batch_set_key.draw_function } #[inline] @@ -1685,7 +1748,7 @@ impl PhaseItem for Shadow { #[inline] fn extra_index(&self) -> PhaseItemExtraIndex { - self.extra_index + self.extra_index.clone() } #[inline] @@ -1716,7 +1779,7 @@ impl BinnedPhaseItem for Shadow { impl CachedRenderPipelinePhaseItem for Shadow { #[inline] fn cached_pipeline(&self) -> CachedRenderPipelineId { - self.key.pipeline + self.key.batch_set_key.pipeline } } diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 29c7ce3ca3107..08eb1fc34178b 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -30,8 +30,8 @@ use bevy_render::{ primitives::Aabb, render_asset::{ExtractAssetsSet, RenderAssets}, render_phase::{ - BinnedRenderPhasePlugin, PhaseItem, RenderCommand, RenderCommandResult, - SortedRenderPhasePlugin, TrackedRenderPass, + BinnedRenderPhasePlugin, PhaseItem, PhaseItemExtraIndex, RenderCommand, + RenderCommandResult, SortedRenderPhasePlugin, TrackedRenderPass, }, render_resource::*, renderer::{RenderDevice, RenderQueue}, @@ -193,8 +193,8 @@ impl Plugin for MeshRenderPlugin { let gpu_preprocessing_support = render_app.world().resource::(); - let use_gpu_instance_buffer_builder = self.use_gpu_instance_buffer_builder - && *gpu_preprocessing_support != GpuPreprocessingSupport::None; + let use_gpu_instance_buffer_builder = + self.use_gpu_instance_buffer_builder && gpu_preprocessing_support.is_available(); let render_mesh_instances = RenderMeshInstances::new(use_gpu_instance_buffer_builder); render_app.insert_resource(render_mesh_instances); @@ -2608,8 +2608,8 @@ impl RenderCommand

for SetMeshBindGroup { let mut dynamic_offsets: [u32; 3] = Default::default(); let mut offset_count = 0; - if let Some(dynamic_offset) = item.extra_index().as_dynamic_offset() { - dynamic_offsets[offset_count] = dynamic_offset.get(); + if let PhaseItemExtraIndex::DynamicOffset(dynamic_offset) = item.extra_index() { + dynamic_offsets[offset_count] = dynamic_offset; offset_count += 1; } if let Some(current_skin_index) = current_skin_index { @@ -2706,25 +2706,32 @@ impl RenderCommand

for DrawMesh { }; // Calculate the indirect offset, and look up the buffer. - let indirect_parameters = match item.extra_index().as_indirect_parameters_index() { - None => None, - Some(index) => match indirect_parameters_buffer.buffer() { - None => { - warn!("Not rendering mesh because indirect parameters buffer wasn't present"); - return RenderCommandResult::Skip; + let indirect_parameters = match item.extra_index() { + PhaseItemExtraIndex::None | PhaseItemExtraIndex::DynamicOffset(_) => None, + PhaseItemExtraIndex::IndirectParametersIndex(indices) => { + match indirect_parameters_buffer.buffer() { + None => { + warn!( + "Not rendering mesh because indirect parameters buffer wasn't present" + ); + return RenderCommandResult::Skip; + } + Some(buffer) => Some(( + indices.start as u64 * size_of::() as u64, + indices.end - indices.start, + buffer, + )), } - Some(buffer) => Some(( - index as u64 * size_of::() as u64, - buffer, - )), - }, + } }; pass.set_vertex_buffer(0, vertex_buffer_slice.buffer.slice(..)); let batch_range = item.batch_range(); - // Draw either directly or indirectly, as appropriate. + // Draw either directly or indirectly, as appropriate. If we're in + // indirect mode, we can additionally multi-draw. (We can't multi-draw + // in direct mode because `wgpu` doesn't expose that functionality.) match &gpu_mesh.buffer_info { RenderMeshBufferInfo::Indexed { index_format, @@ -2746,19 +2753,33 @@ impl RenderCommand

for DrawMesh { batch_range.clone(), ); } - Some((indirect_parameters_offset, indirect_parameters_buffer)) => pass - .draw_indexed_indirect( + Some(( + indirect_parameters_offset, + indirect_parameters_count, + indirect_parameters_buffer, + )) => { + pass.multi_draw_indexed_indirect( indirect_parameters_buffer, indirect_parameters_offset, - ), + indirect_parameters_count, + ); + } } } RenderMeshBufferInfo::NonIndexed => match indirect_parameters { None => { pass.draw(vertex_buffer_slice.range, batch_range.clone()); } - Some((indirect_parameters_offset, indirect_parameters_buffer)) => { - pass.draw_indirect(indirect_parameters_buffer, indirect_parameters_offset); + Some(( + indirect_parameters_offset, + indirect_parameters_count, + indirect_parameters_buffer, + )) => { + pass.multi_draw_indirect( + indirect_parameters_buffer, + indirect_parameters_offset, + indirect_parameters_count, + ); } }, } diff --git a/crates/bevy_render/src/batching/gpu_preprocessing.rs b/crates/bevy_render/src/batching/gpu_preprocessing.rs index 1e8decf73d48e..096d739ba786b 100644 --- a/crates/bevy_render/src/batching/gpu_preprocessing.rs +++ b/crates/bevy_render/src/batching/gpu_preprocessing.rs @@ -10,16 +10,17 @@ use bevy_ecs::{ world::{FromWorld, World}, }; use bevy_encase_derive::ShaderType; +use bevy_utils::tracing::error; use bytemuck::{Pod, Zeroable}; use nonmax::NonMaxU32; -use smallvec::smallvec; use wgpu::{BindingResource, BufferUsages, DownlevelFlags, Features}; use crate::{ render_phase::{ - BinnedPhaseItem, BinnedRenderPhaseBatch, CachedRenderPipelinePhaseItem, - PhaseItemExtraIndex, SortedPhaseItem, SortedRenderPhase, UnbatchableBinnedEntityIndices, - ViewBinnedRenderPhases, ViewSortedRenderPhases, + BinnedPhaseItem, BinnedRenderPhaseBatch, BinnedRenderPhaseBatchSets, + CachedRenderPipelinePhaseItem, PhaseItemBinKey as _, PhaseItemExtraIndex, SortedPhaseItem, + SortedRenderPhase, UnbatchableBinnedEntityIndices, ViewBinnedRenderPhases, + ViewSortedRenderPhases, }, render_resource::{BufferVec, GpuArrayBufferable, RawBufferVec, UninitBufferVec}, renderer::{RenderAdapter, RenderDevice, RenderQueue}, @@ -64,12 +65,49 @@ impl Plugin for BatchingPlugin { /// /// [a `wgpu` limitation]: https://github.com/gfx-rs/wgpu/issues/2471 #[derive(Clone, Copy, PartialEq, Resource)] -pub enum GpuPreprocessingSupport { - /// No GPU preprocessing support is available at all. +pub struct GpuPreprocessingSupport { + /// The maximum amount of GPU preprocessing available on this platform. + pub max_supported_mode: GpuPreprocessingMode, +} + +impl GpuPreprocessingSupport { + /// Returns true if this GPU preprocessing support level isn't `None`. + #[inline] + pub fn is_available(&self) -> bool { + self.max_supported_mode != GpuPreprocessingMode::None + } + + /// Returns the given GPU preprocessing mode, capped to the current + /// preprocessing mode. + pub fn min(&self, mode: GpuPreprocessingMode) -> GpuPreprocessingMode { + match (self.max_supported_mode, mode) { + (GpuPreprocessingMode::None, _) | (_, GpuPreprocessingMode::None) => { + GpuPreprocessingMode::None + } + (mode, GpuPreprocessingMode::Culling) | (GpuPreprocessingMode::Culling, mode) => mode, + (GpuPreprocessingMode::PreprocessingOnly, GpuPreprocessingMode::PreprocessingOnly) => { + GpuPreprocessingMode::PreprocessingOnly + } + } + } +} + +/// The amount of GPU preprocessing (compute and indirect draw) that we do. +#[derive(Clone, Copy, PartialEq)] +pub enum GpuPreprocessingMode { + /// No GPU preprocessing is in use at all. + /// + /// This is used when GPU compute isn't available. None, - /// GPU preprocessing is available, but GPU culling isn't. + + /// GPU preprocessing is in use, but GPU culling isn't. + /// + /// This is used by default. PreprocessingOnly, - /// Both GPU preprocessing and GPU culling are available. + + /// Both GPU preprocessing and GPU culling are in use. + /// + /// This is used when the [`GpuCulling`] component is present on the camera. Culling, } @@ -301,19 +339,21 @@ impl FromWorld for GpuPreprocessingSupport { } } - if device.limits().max_compute_workgroup_size_x == 0 || is_non_supported_android_device(adapter) + let max_supported_mode = if device.limits().max_compute_workgroup_size_x == 0 || is_non_supported_android_device(adapter) { - GpuPreprocessingSupport::None + GpuPreprocessingMode::None } else if !device .features() .contains(Features::INDIRECT_FIRST_INSTANCE) || !adapter.get_downlevel_capabilities().flags.contains( DownlevelFlags::VERTEX_AND_INSTANCE_INDEX_RESPECTS_RESPECTIVE_FIRST_VALUE_IN_INDIRECT_DRAW) { - GpuPreprocessingSupport::PreprocessingOnly + GpuPreprocessingMode::PreprocessingOnly } else { - GpuPreprocessingSupport::Culling - } + GpuPreprocessingMode::Culling + }; + + GpuPreprocessingSupport { max_supported_mode } } } @@ -600,6 +640,13 @@ pub fn batch_and_prepare_binned_render_phase( // Prepare batchables. + // 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 last_multidraw_key = None; + for key in &phase.batchable_mesh_keys { let mut batch: Option = None; for &(entity, main_entity) in &phase.batchable_mesh_values[key] { @@ -615,10 +662,13 @@ pub fn batch_and_prepare_binned_render_phase( batch.instance_range.end = output_index + 1; work_item_buffer.buffer.push(PreprocessWorkItem { input_index: input_index.into(), - output_index: batch - .extra_index - .as_indirect_parameters_index() - .unwrap_or(output_index), + output_index: match batch.extra_index { + PhaseItemExtraIndex::IndirectParametersIndex(ref range) => { + range.start + } + PhaseItemExtraIndex::DynamicOffset(_) + | PhaseItemExtraIndex::None => output_index, + }, }); } @@ -650,14 +700,33 @@ pub fn batch_and_prepare_binned_render_phase( batch = Some(BinnedRenderPhaseBatch { representative_entity: (entity, main_entity), instance_range: output_index..output_index + 1, - extra_index: PhaseItemExtraIndex::NONE, + extra_index: PhaseItemExtraIndex::None, }); } } } if let Some(batch) = batch { - phase.batch_sets.push(smallvec![batch]); + match phase.batch_sets { + BinnedRenderPhaseBatchSets::DynamicUniforms(_) => { + error!("Dynamic uniform batch sets shouldn't be used here"); + } + 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. + let this_multidraw_key = key.get_batch_set_key(); + if last_multidraw_key.as_ref() == Some(&this_multidraw_key) { + batch_sets.last_mut().unwrap().push(batch); + } else { + last_multidraw_key = Some(this_multidraw_key); + batch_sets.push(vec![batch]); + } + } + } } } @@ -688,8 +757,9 @@ pub fn batch_and_prepare_binned_render_phase( .buffer_indices .add(UnbatchableBinnedEntityIndices { instance_index: indirect_parameters_index.into(), - extra_index: PhaseItemExtraIndex::indirect_parameters_index( - indirect_parameters_index.into(), + extra_index: PhaseItemExtraIndex::IndirectParametersIndex( + u32::from(indirect_parameters_index) + ..(u32::from(indirect_parameters_index) + 1), ), }); } else { @@ -701,7 +771,7 @@ pub fn batch_and_prepare_binned_render_phase( .buffer_indices .add(UnbatchableBinnedEntityIndices { instance_index: output_index, - extra_index: PhaseItemExtraIndex::NONE, + extra_index: PhaseItemExtraIndex::None, }); } } diff --git a/crates/bevy_render/src/batching/mod.rs b/crates/bevy_render/src/batching/mod.rs index d4b7e76fcbb15..67adc74559f8c 100644 --- a/crates/bevy_render/src/batching/mod.rs +++ b/crates/bevy_render/src/batching/mod.rs @@ -7,7 +7,7 @@ use bytemuck::Pod; use nonmax::NonMaxU32; use self::gpu_preprocessing::IndirectParametersBuffer; -use crate::sync_world::MainEntity; +use crate::{render_phase::PhaseItemExtraIndex, sync_world::MainEntity}; use crate::{ render_phase::{ BinnedPhaseItem, CachedRenderPipelinePhaseItem, DrawFunctionId, SortedPhaseItem, @@ -54,7 +54,12 @@ impl BatchMeta { BatchMeta { pipeline_id: item.cached_pipeline(), draw_function_id: item.draw_function(), - dynamic_offset: item.extra_index().as_dynamic_offset(), + dynamic_offset: match item.extra_index() { + PhaseItemExtraIndex::DynamicOffset(dynamic_offset) => { + NonMaxU32::new(dynamic_offset) + } + PhaseItemExtraIndex::None | PhaseItemExtraIndex::IndirectParametersIndex(_) => None, + }, user_data, } } diff --git a/crates/bevy_render/src/batching/no_gpu_preprocessing.rs b/crates/bevy_render/src/batching/no_gpu_preprocessing.rs index 63fced58d72d0..41ddea778cfd6 100644 --- a/crates/bevy_render/src/batching/no_gpu_preprocessing.rs +++ b/crates/bevy_render/src/batching/no_gpu_preprocessing.rs @@ -2,13 +2,15 @@ use bevy_derive::{Deref, DerefMut}; use bevy_ecs::system::{Res, ResMut, Resource, StaticSystemParam}; +use bevy_utils::tracing::error; use smallvec::{smallvec, SmallVec}; use wgpu::BindingResource; use crate::{ render_phase::{ - BinnedPhaseItem, BinnedRenderPhaseBatch, CachedRenderPipelinePhaseItem, - PhaseItemExtraIndex, SortedPhaseItem, ViewBinnedRenderPhases, ViewSortedRenderPhases, + BinnedPhaseItem, BinnedRenderPhaseBatch, BinnedRenderPhaseBatchSets, + CachedRenderPipelinePhaseItem, PhaseItemExtraIndex, SortedPhaseItem, + ViewBinnedRenderPhases, ViewSortedRenderPhases, }, render_resource::{GpuArrayBuffer, GpuArrayBufferable}, renderer::{RenderDevice, RenderQueue}, @@ -138,7 +140,17 @@ pub fn batch_and_prepare_binned_render_phase( } } - phase.batch_sets.push(batch_set); + match phase.batch_sets { + BinnedRenderPhaseBatchSets::DynamicUniforms(ref mut batch_sets) => { + batch_sets.push(batch_set); + } + BinnedRenderPhaseBatchSets::Direct(_) + | BinnedRenderPhaseBatchSets::MultidrawIndirect(_) => { + error!( + "Dynamic uniform batch sets should be used when GPU preprocessing is off" + ); + } + } } // Prepare unbatchables. diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index df0b4fd043f53..f0603157fb4fe 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -1,13 +1,12 @@ use super::{ClearColorConfig, Projection}; use crate::{ - batching::gpu_preprocessing::GpuPreprocessingSupport, + batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport}, camera::{CameraProjection, ManualTextureViewHandle, ManualTextureViews}, primitives::Frustum, render_asset::RenderAssets, render_graph::{InternedRenderSubGraph, RenderSubGraph}, render_resource::TextureView, - sync_world::TemporaryRenderEntity, - sync_world::{RenderEntity, SyncToRenderWorld}, + sync_world::{RenderEntity, SyncToRenderWorld, TemporaryRenderEntity}, texture::GpuImage, view::{ ColorGrading, ExtractedView, ExtractedWindows, GpuCulling, Msaa, RenderLayers, @@ -1156,8 +1155,9 @@ pub fn extract_cameras( if let Some(perspective) = projection { commands.insert(perspective.clone()); } + if gpu_culling { - if *gpu_preprocessing_support == GpuPreprocessingSupport::Culling { + if gpu_preprocessing_support.max_supported_mode == GpuPreprocessingMode::Culling { commands.insert(GpuCulling); } else { warn_once!( diff --git a/crates/bevy_render/src/mesh/allocator.rs b/crates/bevy_render/src/mesh/allocator.rs index 506d3adeff305..4e96078464ed3 100644 --- a/crates/bevy_render/src/mesh/allocator.rs +++ b/crates/bevy_render/src/mesh/allocator.rs @@ -5,6 +5,7 @@ use core::{ fmt::{self, Display, Formatter}, ops::Range, }; +use nonmax::NonMaxU32; use bevy_app::{App, Plugin}; use bevy_asset::AssetId; @@ -15,6 +16,7 @@ use bevy_ecs::{ world::{FromWorld, World}, }; use bevy_utils::{ + default, hashbrown::{HashMap, HashSet}, tracing::error, }; @@ -152,9 +154,9 @@ pub struct MeshBufferSlice<'a> { } /// The index of a single slab. -#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, Debug)] +#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] #[repr(transparent)] -struct SlabId(u32); +pub struct SlabId(pub NonMaxU32); /// Data for a single slab. #[allow(clippy::large_enum_variant)] @@ -331,7 +333,7 @@ impl FromWorld for MeshAllocator { slab_layouts: HashMap::new(), mesh_id_to_vertex_slab: HashMap::new(), mesh_id_to_index_slab: HashMap::new(), - next_slab_id: SlabId(0), + next_slab_id: default(), general_vertex_slabs_supported, } } @@ -377,6 +379,19 @@ impl MeshAllocator { self.mesh_slice_in_slab(mesh_id, *self.mesh_id_to_index_slab.get(mesh_id)?) } + /// Returns the IDs of the vertex buffer and index buffer respectively for + /// the mesh with the given ID. + /// + /// If the mesh wasn't allocated, or has no index data in the case of the + /// index buffer, the corresponding element in the returned tuple will be + /// None. + pub fn mesh_slabs(&self, mesh_id: &AssetId) -> (Option, Option) { + ( + self.mesh_id_to_vertex_slab.get(mesh_id).cloned(), + self.mesh_id_to_index_slab.get(mesh_id).cloned(), + ) + } + /// Given a slab and a mesh with data located with it, returns the buffer /// and range of that mesh data within the slab. fn mesh_slice_in_slab( @@ -713,7 +728,7 @@ impl MeshAllocator { // If we still have no allocation, make a new slab. if mesh_allocation.is_none() { let new_slab_id = self.next_slab_id; - self.next_slab_id.0 += 1; + self.next_slab_id.0 = NonMaxU32::new(self.next_slab_id.0.get() + 1).unwrap_or_default(); let new_slab = GeneralSlab::new( new_slab_id, @@ -747,7 +762,7 @@ impl MeshAllocator { /// Allocates an object into its own dedicated slab. fn allocate_large(&mut self, mesh_id: &AssetId, layout: ElementLayout) { let new_slab_id = self.next_slab_id; - self.next_slab_id.0 += 1; + self.next_slab_id.0 = NonMaxU32::new(self.next_slab_id.0.get() + 1).unwrap_or_default(); self.record_allocation(mesh_id, new_slab_id, layout.class); diff --git a/crates/bevy_render/src/render_phase/mod.rs b/crates/bevy_render/src/render_phase/mod.rs index f460a37d0441c..5899bc9c01ea2 100644 --- a/crates/bevy_render/src/render_phase/mod.rs +++ b/crates/bevy_render/src/render_phase/mod.rs @@ -37,6 +37,7 @@ use encase::{internal::WriteInto, ShaderSize}; use nonmax::NonMaxU32; pub use rangefinder::*; +use crate::batching::gpu_preprocessing::GpuPreprocessingMode; use crate::sync_world::MainEntity; use crate::{ batching::{ @@ -53,14 +54,7 @@ use bevy_ecs::{ prelude::*, system::{lifetimeless::SRes, SystemParamItem}, }; -use core::{ - fmt::{self, Debug, Formatter}, - hash::Hash, - iter, - marker::PhantomData, - ops::Range, - slice::SliceIndex, -}; +use core::{fmt::Debug, hash::Hash, iter, marker::PhantomData, ops::Range, slice::SliceIndex}; use smallvec::SmallVec; /// Stores the rendering instructions for a single phase that uses bins in all @@ -133,7 +127,38 @@ where /// /// The unbatchable entities immediately follow the batches in the storage /// buffers. - pub(crate) batch_sets: Vec>, + pub(crate) batch_sets: BinnedRenderPhaseBatchSets, +} + +/// How we store and render the batch sets. +/// +/// Each one of these corresponds to a [`GpuPreprocessingMode`]. +pub enum BinnedRenderPhaseBatchSets { + /// Batches are grouped into batch sets based on dynamic uniforms. + /// + /// This corresponds to [`GpuPreprocessingMode::None`]. + DynamicUniforms(Vec>), + + /// Batches are never grouped into batch sets. + /// + /// This corresponds to [`GpuPreprocessingMode::PreprocessingOnly`]. + Direct(Vec), + + /// Batches are grouped together into batch sets based on their ability to + /// be multi-drawn together. + /// + /// This corresponds to [`GpuPreprocessingMode::Culling`]. + MultidrawIndirect(Vec>), +} + +impl BinnedRenderPhaseBatchSets { + fn clear(&mut self) { + match *self { + BinnedRenderPhaseBatchSets::DynamicUniforms(ref mut vec) => vec.clear(), + BinnedRenderPhaseBatchSets::Direct(ref mut vec) => vec.clear(), + BinnedRenderPhaseBatchSets::MultidrawIndirect(ref mut vec) => vec.clear(), + } + } } /// Information about a single batch of entities rendered using binned phase @@ -200,7 +225,7 @@ pub(crate) enum UnbatchableBinnedEntityIndexSet { /// The instance index and dynamic offset (if present) for an unbatchable entity. /// /// This is only useful on platforms that don't support storage buffers. -#[derive(Clone, Copy)] +#[derive(Clone)] pub(crate) struct UnbatchableBinnedEntityIndices { /// The instance index. pub(crate) instance_index: u32, @@ -257,11 +282,11 @@ impl ViewBinnedRenderPhases where BPI: BinnedPhaseItem, { - pub fn insert_or_clear(&mut self, entity: Entity) { + pub fn insert_or_clear(&mut self, entity: Entity, gpu_preprocessing: GpuPreprocessingMode) { match self.entry(entity) { Entry::Occupied(mut entry) => entry.get_mut().clear(), Entry::Vacant(entry) => { - entry.insert(default()); + entry.insert(BinnedRenderPhase::::new(gpu_preprocessing)); } } } @@ -345,24 +370,87 @@ where let draw_functions = world.resource::>(); let mut draw_functions = draw_functions.write(); - debug_assert_eq!(self.batchable_mesh_keys.len(), self.batch_sets.len()); + match self.batch_sets { + 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 in batch_set { + let binned_phase_item = BPI::new( + key.clone(), + batch.representative_entity, + batch.instance_range.clone(), + batch.extra_index.clone(), + ); + + // Fetch the draw function. + let Some(draw_function) = + draw_functions.get_mut(binned_phase_item.draw_function()) + else { + continue; + }; + + draw_function.draw(world, render_pass, view, &binned_phase_item)?; + } + } + } - for (key, batch_set) in self.batchable_mesh_keys.iter().zip(self.batch_sets.iter()) { - for batch in batch_set { - let binned_phase_item = BPI::new( - key.clone(), - batch.representative_entity, - batch.instance_range.clone(), - batch.extra_index, - ); + BinnedRenderPhaseBatchSets::Direct(ref batch_set) => { + for (batch, key) in batch_set.iter().zip(self.batchable_mesh_keys.iter()) { + let binned_phase_item = BPI::new( + key.clone(), + batch.representative_entity, + batch.instance_range.clone(), + batch.extra_index.clone(), + ); + + // Fetch the draw function. + let Some(draw_function) = + draw_functions.get_mut(binned_phase_item.draw_function()) + else { + continue; + }; - // Fetch the draw function. - let Some(draw_function) = draw_functions.get_mut(binned_phase_item.draw_function()) - else { - continue; - }; + draw_function.draw(world, render_pass, view, &binned_phase_item)?; + } + } - draw_function.draw(world, render_pass, view, &binned_phase_item)?; + 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 { + 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.representative_entity, + batch.instance_range.clone(), + match batch.extra_index { + PhaseItemExtraIndex::None => PhaseItemExtraIndex::None, + PhaseItemExtraIndex::DynamicOffset(ref dynamic_offset) => { + PhaseItemExtraIndex::DynamicOffset(*dynamic_offset) + } + PhaseItemExtraIndex::IndirectParametersIndex(ref range) => { + PhaseItemExtraIndex::IndirectParametersIndex( + range.start..(range.start + batch_set.len() as u32), + ) + } + }, + ); + + // Fetch the draw function. + let Some(draw_function) = + draw_functions.get_mut(binned_phase_item.draw_function()) + else { + continue; + }; + + draw_function.draw(world, render_pass, view, &binned_phase_item)?; + } } } @@ -393,17 +481,20 @@ where } => UnbatchableBinnedEntityIndices { instance_index: instance_range.start + entity_index as u32, extra_index: match first_indirect_parameters_index { - None => PhaseItemExtraIndex::NONE, + None => PhaseItemExtraIndex::None, Some(first_indirect_parameters_index) => { - PhaseItemExtraIndex::indirect_parameters_index( + let first_indirect_parameters_index_for_entity = u32::from(*first_indirect_parameters_index) - + entity_index as u32, + + entity_index as u32; + PhaseItemExtraIndex::IndirectParametersIndex( + first_indirect_parameters_index_for_entity + ..(first_indirect_parameters_index_for_entity + 1), ) } }, }, UnbatchableBinnedEntityIndexSet::Dense(ref dynamic_offsets) => { - dynamic_offsets[entity_index] + dynamic_offsets[entity_index].clone() } }; @@ -442,7 +533,7 @@ where for &(ref 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(0)); + let binned_phase_item = BPI::new(key.clone(), entity, 0..1, PhaseItemExtraIndex::None); let Some(draw_function) = draw_functions.get_mut(binned_phase_item.draw_function()) else { @@ -471,18 +562,26 @@ where } } -impl Default for BinnedRenderPhase +impl BinnedRenderPhase where BPI: BinnedPhaseItem, { - fn default() -> Self { + fn new(gpu_preprocessing: GpuPreprocessingMode) -> Self { Self { batchable_mesh_keys: vec![], batchable_mesh_values: HashMap::default(), unbatchable_mesh_keys: vec![], unbatchable_mesh_values: HashMap::default(), non_mesh_items: vec![], - batch_sets: vec![], + batch_sets: match gpu_preprocessing { + GpuPreprocessingMode::Culling => { + BinnedRenderPhaseBatchSets::MultidrawIndirect(vec![]) + } + GpuPreprocessingMode::PreprocessingOnly => { + BinnedRenderPhaseBatchSets::Direct(vec![]) + } + GpuPreprocessingMode::None => BinnedRenderPhaseBatchSets::DynamicUniforms(vec![]), + }, } } } @@ -505,19 +604,24 @@ impl UnbatchableBinnedEntityIndexSet { first_indirect_parameters_index: None, } => Some(UnbatchableBinnedEntityIndices { instance_index: instance_range.start + entity_index, - extra_index: PhaseItemExtraIndex::NONE, + extra_index: PhaseItemExtraIndex::None, }), UnbatchableBinnedEntityIndexSet::Sparse { instance_range, first_indirect_parameters_index: Some(first_indirect_parameters_index), - } => Some(UnbatchableBinnedEntityIndices { - instance_index: instance_range.start + entity_index, - extra_index: PhaseItemExtraIndex::indirect_parameters_index( - u32::from(*first_indirect_parameters_index) + entity_index, - ), - }), + } => { + let first_indirect_parameters_index_for_this_batch = + u32::from(*first_indirect_parameters_index) + entity_index; + Some(UnbatchableBinnedEntityIndices { + instance_index: instance_range.start + entity_index, + extra_index: PhaseItemExtraIndex::IndirectParametersIndex( + first_indirect_parameters_index_for_this_batch + ..(first_indirect_parameters_index_for_this_batch + 1), + ), + }) + } UnbatchableBinnedEntityIndexSet::Dense(ref indices) => { - indices.get(entity_index as usize).copied() + indices.get(entity_index as usize).cloned() } } } @@ -661,19 +765,27 @@ impl UnbatchableBinnedEntityIndexSet { pub fn add(&mut self, indices: UnbatchableBinnedEntityIndices) { match self { UnbatchableBinnedEntityIndexSet::NoEntities => { - if indices.extra_index.is_dynamic_offset() { - // This is the first entity we've seen, and we don't have - // compute shaders. Initialize an array. - *self = UnbatchableBinnedEntityIndexSet::Dense(vec![indices]); - } else { - // This is the first entity we've seen, and we have compute - // shaders. Initialize the fast path. - *self = UnbatchableBinnedEntityIndexSet::Sparse { - instance_range: indices.instance_index..indices.instance_index + 1, - first_indirect_parameters_index: indices - .extra_index - .as_indirect_parameters_index() - .and_then(|index| NonMaxU32::try_from(index).ok()), + match indices.extra_index { + PhaseItemExtraIndex::DynamicOffset(_) => { + // This is the first entity we've seen, and we don't have + // compute shaders. Initialize an array. + *self = UnbatchableBinnedEntityIndexSet::Dense(vec![indices]); + } + PhaseItemExtraIndex::None => { + // This is the first entity we've seen, and we have compute + // shaders. Initialize the fast path. + *self = UnbatchableBinnedEntityIndexSet::Sparse { + instance_range: indices.instance_index..indices.instance_index + 1, + first_indirect_parameters_index: None, + } + } + PhaseItemExtraIndex::IndirectParametersIndex(ref range) => { + // This is the first entity we've seen, and we have compute + // shaders. Initialize the fast path. + *self = UnbatchableBinnedEntityIndexSet::Sparse { + instance_range: indices.instance_index..indices.instance_index + 1, + first_indirect_parameters_index: NonMaxU32::new(range.start), + } } } } @@ -683,13 +795,17 @@ impl UnbatchableBinnedEntityIndexSet { first_indirect_parameters_index, } if instance_range.end == indices.instance_index && ((first_indirect_parameters_index.is_none() - && indices.extra_index == PhaseItemExtraIndex::NONE) + && indices.extra_index == PhaseItemExtraIndex::None) || first_indirect_parameters_index.is_some_and( - |first_indirect_parameters_index| { - Some( + |first_indirect_parameters_index| match indices.extra_index { + PhaseItemExtraIndex::IndirectParametersIndex(ref this_range) => { u32::from(first_indirect_parameters_index) + instance_range.end - - instance_range.start, - ) == indices.extra_index.as_indirect_parameters_index() + - instance_range.start + == this_range.start + } + PhaseItemExtraIndex::DynamicOffset(_) | PhaseItemExtraIndex::None => { + false + } }, )) => { @@ -891,114 +1007,40 @@ pub trait PhaseItem: Sized + Send + Sync + 'static { /// Note that our indirect draw functionality requires storage buffers, so it's /// impossible to have both a dynamic offset and an indirect parameters index. /// This convenient fact allows us to pack both indices into a single `u32`. -#[derive(Clone, Copy, PartialEq, Eq, Hash)] -pub struct PhaseItemExtraIndex(pub u32); - -impl Debug for PhaseItemExtraIndex { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - if self.is_dynamic_offset() { - write!(f, "DynamicOffset({})", self.offset()) - } else if self.is_indirect_parameters_index() { - write!(f, "IndirectParametersIndex({})", self.offset()) - } else { - write!(f, "None") - } - } +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +pub enum PhaseItemExtraIndex { + /// No extra index is present. + None, + /// A `wgpu` dynamic offset into the uniform buffer of instance data. This + /// is used on platforms that don't support storage buffers, to work around + /// uniform buffer size limitations. + DynamicOffset(u32), + /// An index into the buffer that specifies the indirect parameters for this + /// [`PhaseItem`]'s drawcall. This is used when indirect mode is on (as used + /// for GPU culling). + IndirectParametersIndex(Range), } impl PhaseItemExtraIndex { - /// The flag that indicates that this index is an indirect parameter. If not - /// set, this is a dynamic offset. - pub const INDIRECT_PARAMETER_INDEX: u32 = 1 << 31; - /// To extract the index from a packed [`PhaseItemExtraIndex`], bitwise-and - /// the contents with this value. - pub const OFFSET_MASK: u32 = Self::INDIRECT_PARAMETER_INDEX - 1; - /// To extract the flag from a packed [`PhaseItemExtraIndex`], bitwise-and - /// the contents with this value. - pub const FLAGS_MASK: u32 = !Self::OFFSET_MASK; - - /// The special value that indicates that no extra index is present. - pub const NONE: PhaseItemExtraIndex = PhaseItemExtraIndex(u32::MAX); - - /// Returns either the indirect parameters index or the dynamic offset, - /// depending on which is in use. - #[inline] - fn offset(&self) -> u32 { - self.0 & Self::OFFSET_MASK - } - - /// Determines whether this extra index is a dynamic offset. - #[inline] - fn is_dynamic_offset(&self) -> bool { - *self != Self::NONE && (self.0 & Self::INDIRECT_PARAMETER_INDEX) == 0 - } - - /// Determines whether this extra index is an indirect parameters index. - #[inline] - fn is_indirect_parameters_index(&self) -> bool { - *self != Self::NONE && (self.0 & Self::INDIRECT_PARAMETER_INDEX) != 0 - } - - /// Packs a indirect parameters index into this extra index. - #[inline] - pub fn indirect_parameters_index(indirect_parameter_index: u32) -> PhaseItemExtraIndex { - // Make sure we didn't overflow. - debug_assert_eq!(indirect_parameter_index & Self::FLAGS_MASK, 0); - PhaseItemExtraIndex(indirect_parameter_index | Self::INDIRECT_PARAMETER_INDEX) - } - /// Returns either an indirect parameters index or - /// [`PhaseItemExtraIndex::NONE`], as appropriate. - #[inline] + /// [`PhaseItemExtraIndex::None`], as appropriate. pub fn maybe_indirect_parameters_index( - maybe_indirect_parameters_index: Option, + indirect_parameters_index: Option, ) -> PhaseItemExtraIndex { - match maybe_indirect_parameters_index { - Some(indirect_parameters_index) => { - Self::indirect_parameters_index(indirect_parameters_index.into()) - } - None => PhaseItemExtraIndex::NONE, - } - } - - /// Packs a dynamic offset into this extra index. - #[inline] - pub fn dynamic_offset(dynamic_offset: u32) -> PhaseItemExtraIndex { - // Make sure we didn't overflow. - debug_assert_eq!(dynamic_offset & Self::FLAGS_MASK, 0); - - PhaseItemExtraIndex(dynamic_offset) - } - - /// Returns either a dynamic offset or [`PhaseItemExtraIndex::NONE`], as - /// appropriate. - #[inline] - pub fn maybe_dynamic_offset(maybe_dynamic_offset: Option) -> PhaseItemExtraIndex { - match maybe_dynamic_offset { - Some(dynamic_offset) => Self::dynamic_offset(dynamic_offset.into()), - None => PhaseItemExtraIndex::NONE, - } - } - - /// If this extra index describes a dynamic offset, returns it; otherwise, - /// returns `None`. - #[inline] - pub fn as_dynamic_offset(&self) -> Option { - if self.is_dynamic_offset() { - NonMaxU32::try_from(self.0 & Self::OFFSET_MASK).ok() - } else { - None + match indirect_parameters_index { + Some(indirect_parameters_index) => PhaseItemExtraIndex::IndirectParametersIndex( + u32::from(indirect_parameters_index)..(u32::from(indirect_parameters_index) + 1), + ), + None => PhaseItemExtraIndex::None, } } - /// If this extra index describes an indirect parameters index, returns it; - /// otherwise, returns `None`. - #[inline] - pub fn as_indirect_parameters_index(&self) -> Option { - if self.is_indirect_parameters_index() { - Some(self.0 & Self::OFFSET_MASK) - } else { - None + /// Returns either a dynamic offset index or [`PhaseItemExtraIndex::None`], + /// as appropriate. + pub fn maybe_dynamic_offset(dynamic_offset: Option) -> PhaseItemExtraIndex { + match dynamic_offset { + Some(dynamic_offset) => PhaseItemExtraIndex::DynamicOffset(dynamic_offset.into()), + None => PhaseItemExtraIndex::None, } } } @@ -1017,7 +1059,7 @@ 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: Clone + Send + Sync + Eq + Ord + Hash; + type BinKey: PhaseItemBinKey; /// Creates a new binned phase item from the key and per-entity data. /// @@ -1032,6 +1074,26 @@ pub trait BinnedPhaseItem: PhaseItem { ) -> 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. diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index 5b27a7c658ebf..8b1a31ac9c56d 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -602,7 +602,7 @@ pub fn queue_material2d_meshes( sort_key: FloatOrd(mesh_z + material_2d.properties.depth_bias), // Batching is done in batch_and_prepare_render_phase batch_range: 0..1, - extra_index: PhaseItemExtraIndex::NONE, + extra_index: PhaseItemExtraIndex::None, }); } } diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index bc67c75aa5466..d3e340a02c8b6 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -31,7 +31,9 @@ use bevy_render::{ RenderMeshBufferInfo, }, render_asset::RenderAssets, - render_phase::{PhaseItem, RenderCommand, RenderCommandResult, TrackedRenderPass}, + render_phase::{ + PhaseItem, PhaseItemExtraIndex, RenderCommand, RenderCommandResult, TrackedRenderPass, + }, render_resource::{binding_types::uniform_buffer, *}, renderer::{RenderDevice, RenderQueue}, sync_world::{MainEntity, MainEntityHashMap}, @@ -779,8 +781,8 @@ impl RenderCommand

for SetMesh2dBindGroup { ) -> RenderCommandResult { let mut dynamic_offsets: [u32; 1] = Default::default(); let mut offset_count = 0; - if let Some(dynamic_offset) = item.extra_index().as_dynamic_offset() { - dynamic_offsets[offset_count] = dynamic_offset.get(); + if let PhaseItemExtraIndex::DynamicOffset(dynamic_offset) = item.extra_index() { + dynamic_offsets[offset_count] = dynamic_offset; offset_count += 1; } pass.set_bind_group( diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index 9ef692452fcc9..a5569013ec4ed 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -577,7 +577,7 @@ pub fn queue_sprites( sort_key, // batch_range and dynamic_offset will be calculated in prepare_sprites batch_range: 0..0, - extra_index: PhaseItemExtraIndex::NONE, + extra_index: PhaseItemExtraIndex::None, }); } } diff --git a/crates/bevy_ui/src/render/box_shadow.rs b/crates/bevy_ui/src/render/box_shadow.rs index d0f8cafdc6017..365a06fcaf2d8 100644 --- a/crates/bevy_ui/src/render/box_shadow.rs +++ b/crates/bevy_ui/src/render/box_shadow.rs @@ -377,7 +377,7 @@ pub fn queue_shadows( entity.index(), ), batch_range: 0..0, - extra_index: PhaseItemExtraIndex::NONE, + extra_index: PhaseItemExtraIndex::None, }); } } diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 22d12e2179e77..a77d5cf144f2f 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -835,7 +835,7 @@ pub fn queue_uinodes( ), // batch_range will be calculated in prepare_uinodes batch_range: 0..0, - extra_index: PhaseItemExtraIndex::NONE, + extra_index: PhaseItemExtraIndex::None, }); } } diff --git a/crates/bevy_ui/src/render/render_pass.rs b/crates/bevy_ui/src/render/render_pass.rs index 29b2328c2fb6e..1e3a9d81ba43c 100644 --- a/crates/bevy_ui/src/render/render_pass.rs +++ b/crates/bevy_ui/src/render/render_pass.rs @@ -126,7 +126,7 @@ impl PhaseItem for TransparentUi { #[inline] fn extra_index(&self) -> PhaseItemExtraIndex { - self.extra_index + self.extra_index.clone() } #[inline] diff --git a/crates/bevy_ui/src/render/ui_material_pipeline.rs b/crates/bevy_ui/src/render/ui_material_pipeline.rs index 4e6330457fa94..481dce07d37f1 100644 --- a/crates/bevy_ui/src/render/ui_material_pipeline.rs +++ b/crates/bevy_ui/src/render/ui_material_pipeline.rs @@ -655,7 +655,7 @@ pub fn queue_ui_material_nodes( entity.index(), ), batch_range: 0..0, - extra_index: PhaseItemExtraIndex::NONE, + extra_index: PhaseItemExtraIndex::None, }); } } diff --git a/crates/bevy_ui/src/render/ui_texture_slice_pipeline.rs b/crates/bevy_ui/src/render/ui_texture_slice_pipeline.rs index dc19fd830bf64..ada56e89f4d69 100644 --- a/crates/bevy_ui/src/render/ui_texture_slice_pipeline.rs +++ b/crates/bevy_ui/src/render/ui_texture_slice_pipeline.rs @@ -373,7 +373,7 @@ pub fn queue_ui_slices( entity.index(), ), batch_range: 0..0, - extra_index: PhaseItemExtraIndex::NONE, + extra_index: PhaseItemExtraIndex::None, }); } } diff --git a/examples/2d/mesh2d_manual.rs b/examples/2d/mesh2d_manual.rs index f12e6a27cbad5..bf7ef52d3d5c1 100644 --- a/examples/2d/mesh2d_manual.rs +++ b/examples/2d/mesh2d_manual.rs @@ -410,7 +410,7 @@ pub fn queue_colored_mesh2d( sort_key: FloatOrd(mesh_z), // This material is not batched batch_range: 0..1, - extra_index: PhaseItemExtraIndex::NONE, + extra_index: PhaseItemExtraIndex::None, }); } } diff --git a/examples/shader/custom_phase_item.rs b/examples/shader/custom_phase_item.rs index 2dcf8dd83781a..360c664d9ec86 100644 --- a/examples/shader/custom_phase_item.rs +++ b/examples/shader/custom_phase_item.rs @@ -8,7 +8,7 @@ //! for better reuse of parts of Bevy's built-in mesh rendering logic. use bevy::{ - core_pipeline::core_3d::{Opaque3d, Opaque3dBinKey, CORE_3D_DEPTH_FORMAT}, + core_pipeline::core_3d::{Opaque3d, Opaque3dBatchSetKey, Opaque3dBinKey, CORE_3D_DEPTH_FORMAT}, ecs::{ query::ROQueryItem, system::{lifetimeless::SRes, SystemParamItem}, @@ -270,11 +270,15 @@ fn queue_custom_phase_item( // not be the ID of a [`Mesh`]. opaque_phase.add( Opaque3dBinKey { - draw_function: draw_custom_phase_item, - pipeline: pipeline_id, + batch_set_key: Opaque3dBatchSetKey { + draw_function: draw_custom_phase_item, + pipeline: pipeline_id, + material_bind_group_index: None, + lightmap_image: None, + vertex_slab: default(), + index_slab: None, + }, asset_id: AssetId::::invalid().untyped(), - material_bind_group_index: None, - lightmap_image: None, }, entity, BinnedRenderPhaseType::NonMesh, diff --git a/examples/shader/custom_shader_instancing.rs b/examples/shader/custom_shader_instancing.rs index f98f9bbfbe45d..385b4875d6e34 100644 --- a/examples/shader/custom_shader_instancing.rs +++ b/examples/shader/custom_shader_instancing.rs @@ -161,7 +161,7 @@ fn queue_custom( draw_function: draw_custom, distance: rangefinder.distance_translation(&mesh_instance.translation), batch_range: 0..1, - extra_index: PhaseItemExtraIndex::NONE, + extra_index: PhaseItemExtraIndex::None, }); } } diff --git a/examples/shader/specialized_mesh_pipeline.rs b/examples/shader/specialized_mesh_pipeline.rs index f6811f4ab8368..b83baa34867a7 100644 --- a/examples/shader/specialized_mesh_pipeline.rs +++ b/examples/shader/specialized_mesh_pipeline.rs @@ -7,7 +7,7 @@ //! [`SpecializedMeshPipeline`] let's you customize the entire pipeline used when rendering a mesh. use bevy::{ - core_pipeline::core_3d::{Opaque3d, Opaque3dBinKey, CORE_3D_DEPTH_FORMAT}, + core_pipeline::core_3d::{Opaque3d, Opaque3dBatchSetKey, Opaque3dBinKey, CORE_3D_DEPTH_FORMAT}, math::{vec3, vec4}, pbr::{ DrawMesh, MeshPipeline, MeshPipelineKey, MeshPipelineViewLayoutKey, RenderMeshInstances, @@ -335,14 +335,18 @@ fn queue_custom_mesh_pipeline( // Add the mesh with our specialized pipeline opaque_phase.add( Opaque3dBinKey { - draw_function: draw_function_id, - pipeline: pipeline_id, + batch_set_key: Opaque3dBatchSetKey { + draw_function: draw_function_id, + pipeline: pipeline_id, + material_bind_group_index: None, + vertex_slab: default(), + index_slab: None, + lightmap_image: 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(), - material_bind_group_index: None, - lightmap_image: None, }, (render_entity, visible_entity), // This example supports batching, but if your pipeline doesn't