diff --git a/crates/bevy_core_pipeline/src/core_2d/main_pass_2d_node.rs b/crates/bevy_core_pipeline/src/core_2d/main_pass_2d_node.rs index 5e61f475af..320141818a 100644 --- a/crates/bevy_core_pipeline/src/core_2d/main_pass_2d_node.rs +++ b/crates/bevy_core_pipeline/src/core_2d/main_pass_2d_node.rs @@ -4,7 +4,7 @@ use bevy_render::{ camera::ExtractedCamera, diagnostic::RecordDiagnostics, render_graph::{Node, NodeRunError, RenderGraphContext}, - render_phase::RenderPhase, + render_phase::SortedRenderPhase, render_resource::RenderPassDescriptor, renderer::RenderContext, view::{ExtractedView, ViewTarget}, @@ -16,7 +16,7 @@ pub struct MainPass2dNode { query: QueryState< ( &'static ExtractedCamera, - &'static RenderPhase, + &'static SortedRenderPhase, &'static ViewTarget, ), With, diff --git a/crates/bevy_core_pipeline/src/core_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs index 2541e6eae5..bc87295070 100644 --- a/crates/bevy_core_pipeline/src/core_2d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -38,7 +38,7 @@ use bevy_render::{ render_graph::{EmptyNode, RenderGraphApp, ViewNodeRunner}, render_phase::{ sort_phase_system, CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, PhaseItem, - RenderPhase, + SortedPhaseItem, SortedRenderPhase, }, render_resource::CachedRenderPipelineId, Extract, ExtractSchedule, Render, RenderApp, RenderSet, @@ -96,29 +96,16 @@ pub struct Transparent2d { } impl PhaseItem for Transparent2d { - type SortKey = FloatOrd; - #[inline] fn entity(&self) -> Entity { self.entity } - #[inline] - fn sort_key(&self) -> Self::SortKey { - self.sort_key - } - #[inline] fn draw_function(&self) -> DrawFunctionId { self.draw_function } - #[inline] - fn sort(items: &mut [Self]) { - // radsort is a stable radix sort that performed better than `slice::sort_by_key` or `slice::sort_unstable_by_key`. - radsort::sort_by_key(items, |item| item.sort_key().0); - } - #[inline] fn batch_range(&self) -> &Range { &self.batch_range @@ -140,6 +127,21 @@ impl PhaseItem for Transparent2d { } } +impl SortedPhaseItem for Transparent2d { + type SortKey = FloatOrd; + + #[inline] + fn sort_key(&self) -> Self::SortKey { + self.sort_key + } + + #[inline] + fn sort(items: &mut [Self]) { + // radsort is a stable radix sort that performed better than `slice::sort_by_key` or `slice::sort_unstable_by_key`. + radsort::sort_by_key(items, |item| item.sort_key().0); + } +} + impl CachedRenderPipelinePhaseItem for Transparent2d { #[inline] fn cached_pipeline(&self) -> CachedRenderPipelineId { @@ -155,7 +157,7 @@ pub fn extract_core_2d_camera_phases( if camera.is_active { commands .get_or_spawn(entity) - .insert(RenderPhase::::default()); + .insert(SortedRenderPhase::::default()); } } } diff --git a/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs b/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs index 53e732ca7d..2d490ac9bf 100644 --- a/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs +++ b/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs @@ -7,7 +7,7 @@ use bevy_render::{ camera::ExtractedCamera, diagnostic::RecordDiagnostics, render_graph::{NodeRunError, RenderGraphContext, ViewNode}, - render_phase::{RenderPhase, TrackedRenderPass}, + render_phase::{BinnedRenderPhase, TrackedRenderPass}, render_resource::{CommandEncoderDescriptor, PipelineCache, RenderPassDescriptor, StoreOp}, renderer::RenderContext, view::{ViewDepthTexture, ViewTarget, ViewUniformOffset}, @@ -17,14 +17,16 @@ use bevy_utils::tracing::info_span; use super::AlphaMask3d; -/// A [`bevy_render::render_graph::Node`] that runs the [`Opaque3d`] and [`AlphaMask3d`] [`RenderPhase`]. +/// A [`bevy_render::render_graph::Node`] that runs the [`Opaque3d`] +/// [`BinnedRenderPhase`] and [`AlphaMask3d`] +/// [`bevy_render::render_phase::SortedRenderPhase`]s. #[derive(Default)] pub struct MainOpaquePass3dNode; impl ViewNode for MainOpaquePass3dNode { type ViewQuery = ( &'static ExtractedCamera, - &'static RenderPhase, - &'static RenderPhase, + &'static BinnedRenderPhase, + &'static BinnedRenderPhase, &'static ViewTarget, &'static ViewDepthTexture, Option<&'static SkyboxPipelineId>, @@ -80,14 +82,14 @@ impl ViewNode for MainOpaquePass3dNode { } // Opaque draws - if !opaque_phase.items.is_empty() { + if !opaque_phase.is_empty() { #[cfg(feature = "trace")] let _opaque_main_pass_3d_span = info_span!("opaque_main_pass_3d").entered(); opaque_phase.render(&mut render_pass, world, view_entity); } // Alpha draws - if !alpha_mask_phase.items.is_empty() { + if !alpha_mask_phase.is_empty() { #[cfg(feature = "trace")] let _alpha_mask_main_pass_3d_span = info_span!("alpha_mask_main_pass_3d").entered(); alpha_mask_phase.render(&mut render_pass, world, view_entity); diff --git a/crates/bevy_core_pipeline/src/core_3d/main_transmissive_pass_3d_node.rs b/crates/bevy_core_pipeline/src/core_3d/main_transmissive_pass_3d_node.rs index 73a679ba04..54c6c623f1 100644 --- a/crates/bevy_core_pipeline/src/core_3d/main_transmissive_pass_3d_node.rs +++ b/crates/bevy_core_pipeline/src/core_3d/main_transmissive_pass_3d_node.rs @@ -4,7 +4,7 @@ use bevy_ecs::{prelude::*, query::QueryItem}; use bevy_render::{ camera::ExtractedCamera, render_graph::{NodeRunError, RenderGraphContext, ViewNode}, - render_phase::RenderPhase, + render_phase::SortedRenderPhase, render_resource::{Extent3d, RenderPassDescriptor, StoreOp}, renderer::RenderContext, view::{ViewDepthTexture, ViewTarget}, @@ -13,7 +13,8 @@ use bevy_render::{ use bevy_utils::tracing::info_span; use std::ops::Range; -/// A [`bevy_render::render_graph::Node`] that runs the [`Transmissive3d`] [`RenderPhase`]. +/// A [`bevy_render::render_graph::Node`] that runs the [`Transmissive3d`] +/// [`SortedRenderPhase`]. #[derive(Default)] pub struct MainTransmissivePass3dNode; @@ -21,7 +22,7 @@ impl ViewNode for MainTransmissivePass3dNode { type ViewQuery = ( &'static ExtractedCamera, &'static Camera3d, - &'static RenderPhase, + &'static SortedRenderPhase, &'static ViewTarget, Option<&'static ViewTransmissionTexture>, &'static ViewDepthTexture, diff --git a/crates/bevy_core_pipeline/src/core_3d/main_transparent_pass_3d_node.rs b/crates/bevy_core_pipeline/src/core_3d/main_transparent_pass_3d_node.rs index 18a19cd2fe..3c42434330 100644 --- a/crates/bevy_core_pipeline/src/core_3d/main_transparent_pass_3d_node.rs +++ b/crates/bevy_core_pipeline/src/core_3d/main_transparent_pass_3d_node.rs @@ -4,7 +4,7 @@ use bevy_render::{ camera::ExtractedCamera, diagnostic::RecordDiagnostics, render_graph::{NodeRunError, RenderGraphContext, ViewNode}, - render_phase::RenderPhase, + render_phase::SortedRenderPhase, render_resource::{RenderPassDescriptor, StoreOp}, renderer::RenderContext, view::{ViewDepthTexture, ViewTarget}, @@ -12,14 +12,15 @@ use bevy_render::{ #[cfg(feature = "trace")] use bevy_utils::tracing::info_span; -/// A [`bevy_render::render_graph::Node`] that runs the [`Transparent3d`] [`RenderPhase`]. +/// A [`bevy_render::render_graph::Node`] that runs the [`Transparent3d`] +/// [`SortedRenderPhase`]. #[derive(Default)] pub struct MainTransparentPass3dNode; impl ViewNode for MainTransparentPass3dNode { type ViewQuery = ( &'static ExtractedCamera, - &'static RenderPhase, + &'static SortedRenderPhase, &'static ViewTarget, &'static ViewDepthTexture, ); diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index fe8be345ec..7c622141a6 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -56,15 +56,15 @@ use bevy_render::{ prelude::Msaa, render_graph::{EmptyNode, RenderGraphApp, ViewNodeRunner}, render_phase::{ - sort_phase_system, CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, PhaseItem, - RenderPhase, + sort_phase_system, BinnedPhaseItem, BinnedRenderPhase, CachedRenderPipelinePhaseItem, + DrawFunctionId, DrawFunctions, PhaseItem, SortedPhaseItem, SortedRenderPhase, }, render_resource::{ - CachedRenderPipelineId, Extent3d, FilterMode, Sampler, SamplerDescriptor, Texture, - TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, TextureView, + BindGroupId, CachedRenderPipelineId, Extent3d, FilterMode, Sampler, SamplerDescriptor, + Texture, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, TextureView, }, renderer::RenderDevice, - texture::{BevyDefault, ColorAttachment, TextureCache}, + texture::{BevyDefault, ColorAttachment, Image, TextureCache}, view::{ExtractedView, ViewDepthTexture, ViewTarget}, Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; @@ -80,8 +80,8 @@ use crate::{ }, prepass::{ node::PrepassNode, AlphaMask3dPrepass, DeferredPrepass, DepthPrepass, MotionVectorPrepass, - NormalPrepass, Opaque3dPrepass, ViewPrepassTextures, MOTION_VECTOR_PREPASS_FORMAT, - NORMAL_PREPASS_FORMAT, + NormalPrepass, Opaque3dPrepass, OpaqueNoLightmap3dBinKey, ViewPrepassTextures, + MOTION_VECTOR_PREPASS_FORMAT, NORMAL_PREPASS_FORMAT, }, skybox::SkyboxPlugin, tonemapping::TonemappingNode, @@ -117,14 +117,8 @@ impl Plugin for Core3dPlugin { .add_systems( Render, ( - sort_phase_system::.in_set(RenderSet::PhaseSort), - sort_phase_system::.in_set(RenderSet::PhaseSort), sort_phase_system::.in_set(RenderSet::PhaseSort), sort_phase_system::.in_set(RenderSet::PhaseSort), - sort_phase_system::.in_set(RenderSet::PhaseSort), - sort_phase_system::.in_set(RenderSet::PhaseSort), - sort_phase_system::.in_set(RenderSet::PhaseSort), - sort_phase_system::.in_set(RenderSet::PhaseSort), prepare_core_3d_depth_textures.in_set(RenderSet::PrepareResources), prepare_core_3d_transmission_textures.in_set(RenderSet::PrepareResources), prepare_prepass_textures.in_set(RenderSet::PrepareResources), @@ -180,37 +174,49 @@ impl Plugin for Core3dPlugin { } } +/// Opaque 3D [`BinnedPhaseItem`]s. pub struct Opaque3d { - pub asset_id: AssetId, - pub pipeline: CachedRenderPipelineId, - pub entity: Entity, - pub draw_function: DrawFunctionId, + /// The key, which determines which can be batched. + pub key: Opaque3dBinKey, + /// An entity from which data will be fetched, including the mesh if + /// applicable. + pub representative_entity: Entity, + /// The ranges of instances. pub batch_range: Range, + /// The dynamic offset. pub dynamic_offset: Option, } -impl PhaseItem for Opaque3d { - type SortKey = (usize, AssetId); +/// Data that must be identical in order to batch meshes together. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Opaque3dBinKey { + /// The identifier of the render pipeline. + pub pipeline: CachedRenderPipelineId, + /// The function used to draw. + pub draw_function: DrawFunctionId, + + /// The mesh. + pub asset_id: AssetId, + + /// The ID of a bind group specific to the material. + /// + /// In the case of PBR, this is the `MaterialBindGroupId`. + pub material_bind_group_id: Option, + + /// The lightmap, if present. + pub lightmap_image: Option>, +} + +impl PhaseItem for Opaque3d { #[inline] fn entity(&self) -> Entity { - self.entity - } - - #[inline] - fn sort_key(&self) -> Self::SortKey { - // Sort by pipeline, then by mesh to massively decrease drawcall counts in real scenes. - (self.pipeline.id(), self.asset_id) + self.representative_entity } #[inline] fn draw_function(&self) -> DrawFunctionId { - self.draw_function - } - - #[inline] - fn sort(items: &mut [Self]) { - items.sort_unstable_by_key(Self::sort_key); + self.key.draw_function } #[inline] @@ -234,44 +240,48 @@ impl PhaseItem for Opaque3d { } } +impl BinnedPhaseItem for Opaque3d { + type BinKey = Opaque3dBinKey; + + #[inline] + fn new( + key: Self::BinKey, + representative_entity: Entity, + batch_range: Range, + dynamic_offset: Option, + ) -> Self { + Opaque3d { + key, + representative_entity, + batch_range, + dynamic_offset, + } + } +} + impl CachedRenderPipelinePhaseItem for Opaque3d { #[inline] fn cached_pipeline(&self) -> CachedRenderPipelineId { - self.pipeline + self.key.pipeline } } pub struct AlphaMask3d { - pub asset_id: AssetId, - pub pipeline: CachedRenderPipelineId, - pub entity: Entity, - pub draw_function: DrawFunctionId, + pub key: OpaqueNoLightmap3dBinKey, + pub representative_entity: Entity, pub batch_range: Range, pub dynamic_offset: Option, } impl PhaseItem for AlphaMask3d { - type SortKey = (usize, AssetId); - #[inline] fn entity(&self) -> Entity { - self.entity - } - - #[inline] - fn sort_key(&self) -> Self::SortKey { - // Sort by pipeline, then by mesh to massively decrease drawcall counts in real scenes. - (self.pipeline.id(), self.asset_id) + self.representative_entity } #[inline] fn draw_function(&self) -> DrawFunctionId { - self.draw_function - } - - #[inline] - fn sort(items: &mut [Self]) { - items.sort_unstable_by_key(Self::sort_key); + self.key.draw_function } #[inline] @@ -295,10 +305,29 @@ impl PhaseItem for AlphaMask3d { } } +impl BinnedPhaseItem for AlphaMask3d { + type BinKey = OpaqueNoLightmap3dBinKey; + + #[inline] + fn new( + key: Self::BinKey, + representative_entity: Entity, + batch_range: Range, + dynamic_offset: Option, + ) -> Self { + Self { + key, + representative_entity, + batch_range, + dynamic_offset, + } + } +} + impl CachedRenderPipelinePhaseItem for AlphaMask3d { #[inline] fn cached_pipeline(&self) -> CachedRenderPipelineId { - self.pipeline + self.key.pipeline } } @@ -312,9 +341,6 @@ pub struct Transmissive3d { } impl PhaseItem for Transmissive3d { - // NOTE: Values increase towards the camera. Back-to-front ordering for transmissive means we need an ascending sort. - type SortKey = FloatOrd; - /// For now, automatic batching is disabled for transmissive items because their rendering is /// split into multiple steps depending on [`Camera3d::screen_space_specular_transmission_steps`], /// which the batching system doesn't currently know about. @@ -331,21 +357,11 @@ impl PhaseItem for Transmissive3d { self.entity } - #[inline] - fn sort_key(&self) -> Self::SortKey { - FloatOrd(self.distance) - } - #[inline] fn draw_function(&self) -> DrawFunctionId { self.draw_function } - #[inline] - fn sort(items: &mut [Self]) { - radsort::sort_by_key(items, |item| item.distance); - } - #[inline] fn batch_range(&self) -> &Range { &self.batch_range @@ -367,6 +383,21 @@ impl PhaseItem for Transmissive3d { } } +impl SortedPhaseItem for Transmissive3d { + // NOTE: Values increase towards the camera. Back-to-front ordering for transmissive means we need an ascending sort. + type SortKey = FloatOrd; + + #[inline] + fn sort_key(&self) -> Self::SortKey { + FloatOrd(self.distance) + } + + #[inline] + fn sort(items: &mut [Self]) { + radsort::sort_by_key(items, |item| item.distance); + } +} + impl CachedRenderPipelinePhaseItem for Transmissive3d { #[inline] fn cached_pipeline(&self) -> CachedRenderPipelineId { @@ -384,29 +415,16 @@ pub struct Transparent3d { } impl PhaseItem for Transparent3d { - // NOTE: Values increase towards the camera. Back-to-front ordering for transparent means we need an ascending sort. - type SortKey = FloatOrd; - #[inline] fn entity(&self) -> Entity { self.entity } - #[inline] - fn sort_key(&self) -> Self::SortKey { - FloatOrd(self.distance) - } - #[inline] fn draw_function(&self) -> DrawFunctionId { self.draw_function } - #[inline] - fn sort(items: &mut [Self]) { - radsort::sort_by_key(items, |item| item.distance); - } - #[inline] fn batch_range(&self) -> &Range { &self.batch_range @@ -428,6 +446,21 @@ impl PhaseItem for Transparent3d { } } +impl SortedPhaseItem for Transparent3d { + // NOTE: Values increase towards the camera. Back-to-front ordering for transparent means we need an ascending sort. + type SortKey = FloatOrd; + + #[inline] + fn sort_key(&self) -> Self::SortKey { + FloatOrd(self.distance) + } + + #[inline] + fn sort(items: &mut [Self]) { + radsort::sort_by_key(items, |item| item.distance); + } +} + impl CachedRenderPipelinePhaseItem for Transparent3d { #[inline] fn cached_pipeline(&self) -> CachedRenderPipelineId { @@ -442,10 +475,10 @@ pub fn extract_core_3d_camera_phases( for (entity, camera) in &cameras_3d { if camera.is_active { commands.get_or_spawn(entity).insert(( - RenderPhase::::default(), - RenderPhase::::default(), - RenderPhase::::default(), - RenderPhase::::default(), + BinnedRenderPhase::::default(), + BinnedRenderPhase::::default(), + SortedRenderPhase::::default(), + SortedRenderPhase::::default(), )); } } @@ -476,15 +509,15 @@ pub fn extract_camera_prepass_phase( if depth_prepass || normal_prepass || motion_vector_prepass { entity.insert(( - RenderPhase::::default(), - RenderPhase::::default(), + BinnedRenderPhase::::default(), + BinnedRenderPhase::::default(), )); } if deferred_prepass { entity.insert(( - RenderPhase::::default(), - RenderPhase::::default(), + BinnedRenderPhase::::default(), + BinnedRenderPhase::::default(), )); } @@ -512,10 +545,10 @@ pub fn prepare_core_3d_depth_textures( views_3d: Query< (Entity, &ExtractedCamera, Option<&DepthPrepass>, &Camera3d), ( - With>, - With>, - With>, - With>, + With>, + With>, + With>, + With>, ), >, ) { @@ -595,12 +628,12 @@ pub fn prepare_core_3d_transmission_textures( &ExtractedCamera, &Camera3d, &ExtractedView, - &RenderPhase, + &SortedRenderPhase, ), ( - With>, - With>, - With>, + With>, + With>, + With>, ), >, ) { @@ -700,10 +733,10 @@ pub fn prepare_prepass_textures( Has, ), Or<( - With>, - With>, - With>, - With>, + With>, + With>, + With>, + With>, )>, >, ) { diff --git a/crates/bevy_core_pipeline/src/deferred/mod.rs b/crates/bevy_core_pipeline/src/deferred/mod.rs index a6b659fdbe..3ccd8caad0 100644 --- a/crates/bevy_core_pipeline/src/deferred/mod.rs +++ b/crates/bevy_core_pipeline/src/deferred/mod.rs @@ -3,15 +3,15 @@ pub mod node; use std::ops::Range; -use bevy_asset::AssetId; use bevy_ecs::prelude::*; use bevy_render::{ - mesh::Mesh, - render_phase::{CachedRenderPipelinePhaseItem, DrawFunctionId, PhaseItem}, + render_phase::{BinnedPhaseItem, CachedRenderPipelinePhaseItem, DrawFunctionId, PhaseItem}, render_resource::{CachedRenderPipelineId, TextureFormat}, }; use nonmax::NonMaxU32; +use crate::prepass::OpaqueNoLightmap3dBinKey; + pub const DEFERRED_PREPASS_FORMAT: TextureFormat = TextureFormat::Rgba32Uint; pub const DEFERRED_LIGHTING_PASS_ID_FORMAT: TextureFormat = TextureFormat::R8Uint; pub const DEFERRED_LIGHTING_PASS_ID_DEPTH_FORMAT: TextureFormat = TextureFormat::Depth16Unorm; @@ -21,37 +21,23 @@ pub const DEFERRED_LIGHTING_PASS_ID_DEPTH_FORMAT: TextureFormat = TextureFormat: /// Sorted by pipeline, then by mesh to improve batching. /// /// Used to render all 3D meshes with materials that have no transparency. +#[derive(PartialEq, Eq, Hash)] pub struct Opaque3dDeferred { - pub entity: Entity, - pub asset_id: AssetId, - pub pipeline_id: CachedRenderPipelineId, - pub draw_function: DrawFunctionId, + pub key: OpaqueNoLightmap3dBinKey, + pub representative_entity: Entity, pub batch_range: Range, pub dynamic_offset: Option, } impl PhaseItem for Opaque3dDeferred { - type SortKey = (usize, AssetId); - #[inline] fn entity(&self) -> Entity { - self.entity - } - - #[inline] - fn sort_key(&self) -> Self::SortKey { - // Sort by pipeline, then by mesh to massively decrease drawcall counts in real scenes. - (self.pipeline_id.id(), self.asset_id) + self.representative_entity } #[inline] fn draw_function(&self) -> DrawFunctionId { - self.draw_function - } - - #[inline] - fn sort(items: &mut [Self]) { - items.sort_unstable_by_key(Self::sort_key); + self.key.draw_function } #[inline] @@ -75,10 +61,29 @@ impl PhaseItem for Opaque3dDeferred { } } +impl BinnedPhaseItem for Opaque3dDeferred { + type BinKey = OpaqueNoLightmap3dBinKey; + + #[inline] + fn new( + key: Self::BinKey, + representative_entity: Entity, + batch_range: Range, + dynamic_offset: Option, + ) -> Self { + Self { + key, + representative_entity, + batch_range, + dynamic_offset, + } + } +} + impl CachedRenderPipelinePhaseItem for Opaque3dDeferred { #[inline] fn cached_pipeline(&self) -> CachedRenderPipelineId { - self.pipeline_id + self.key.pipeline } } @@ -88,36 +93,21 @@ impl CachedRenderPipelinePhaseItem for Opaque3dDeferred { /// /// Used to render all meshes with a material with an alpha mask. pub struct AlphaMask3dDeferred { - pub asset_id: AssetId, - pub entity: Entity, - pub pipeline_id: CachedRenderPipelineId, - pub draw_function: DrawFunctionId, + pub key: OpaqueNoLightmap3dBinKey, + pub representative_entity: Entity, pub batch_range: Range, pub dynamic_offset: Option, } impl PhaseItem for AlphaMask3dDeferred { - type SortKey = (usize, AssetId); - #[inline] fn entity(&self) -> Entity { - self.entity - } - - #[inline] - fn sort_key(&self) -> Self::SortKey { - // Sort by pipeline, then by mesh to massively decrease drawcall counts in real scenes. - (self.pipeline_id.id(), self.asset_id) + self.representative_entity } #[inline] fn draw_function(&self) -> DrawFunctionId { - self.draw_function - } - - #[inline] - fn sort(items: &mut [Self]) { - items.sort_unstable_by_key(Self::sort_key); + self.key.draw_function } #[inline] @@ -141,9 +131,27 @@ impl PhaseItem for AlphaMask3dDeferred { } } +impl BinnedPhaseItem for AlphaMask3dDeferred { + type BinKey = OpaqueNoLightmap3dBinKey; + + fn new( + key: Self::BinKey, + representative_entity: Entity, + batch_range: Range, + dynamic_offset: Option, + ) -> Self { + Self { + key, + representative_entity, + batch_range, + dynamic_offset, + } + } +} + impl CachedRenderPipelinePhaseItem for AlphaMask3dDeferred { #[inline] fn cached_pipeline(&self) -> CachedRenderPipelineId { - self.pipeline_id + self.key.pipeline } } diff --git a/crates/bevy_core_pipeline/src/deferred/node.rs b/crates/bevy_core_pipeline/src/deferred/node.rs index c2acfe5386..d599cb7c8b 100644 --- a/crates/bevy_core_pipeline/src/deferred/node.rs +++ b/crates/bevy_core_pipeline/src/deferred/node.rs @@ -2,12 +2,11 @@ use bevy_ecs::prelude::*; use bevy_ecs::query::QueryItem; use bevy_render::render_graph::ViewNode; -use bevy_render::render_phase::TrackedRenderPass; +use bevy_render::render_phase::{BinnedRenderPhase, TrackedRenderPass}; use bevy_render::render_resource::{CommandEncoderDescriptor, StoreOp}; use bevy_render::{ camera::ExtractedCamera, render_graph::{NodeRunError, RenderGraphContext}, - render_phase::RenderPhase, render_resource::RenderPassDescriptor, renderer::RenderContext, view::ViewDepthTexture, @@ -28,8 +27,8 @@ pub struct DeferredGBufferPrepassNode; impl ViewNode for DeferredGBufferPrepassNode { type ViewQuery = ( &'static ExtractedCamera, - &'static RenderPhase, - &'static RenderPhase, + &'static BinnedRenderPhase, + &'static BinnedRenderPhase, &'static ViewDepthTexture, &'static ViewPrepassTextures, ); @@ -138,14 +137,16 @@ impl ViewNode for DeferredGBufferPrepassNode { } // Opaque draws - if !opaque_deferred_phase.items.is_empty() { + if !opaque_deferred_phase.batchable_keys.is_empty() + || !opaque_deferred_phase.unbatchable_keys.is_empty() + { #[cfg(feature = "trace")] let _opaque_prepass_span = info_span!("opaque_deferred").entered(); opaque_deferred_phase.render(&mut render_pass, world, view_entity); } // Alpha masked draws - if !alpha_mask_deferred_phase.items.is_empty() { + if !alpha_mask_deferred_phase.is_empty() { #[cfg(feature = "trace")] let _alpha_mask_deferred_span = info_span!("alpha_mask_deferred").entered(); alpha_mask_deferred_phase.render(&mut render_pass, world, view_entity); diff --git a/crates/bevy_core_pipeline/src/prepass/mod.rs b/crates/bevy_core_pipeline/src/prepass/mod.rs index 348419336f..01fca93ddc 100644 --- a/crates/bevy_core_pipeline/src/prepass/mod.rs +++ b/crates/bevy_core_pipeline/src/prepass/mod.rs @@ -34,8 +34,8 @@ use bevy_ecs::prelude::*; use bevy_reflect::Reflect; use bevy_render::{ mesh::Mesh, - render_phase::{CachedRenderPipelinePhaseItem, DrawFunctionId, PhaseItem}, - render_resource::{CachedRenderPipelineId, Extent3d, TextureFormat, TextureView}, + render_phase::{BinnedPhaseItem, CachedRenderPipelinePhaseItem, DrawFunctionId, PhaseItem}, + render_resource::{BindGroupId, CachedRenderPipelineId, Extent3d, TextureFormat, TextureView}, texture::ColorAttachment, }; use nonmax::NonMaxU32; @@ -111,36 +111,45 @@ impl ViewPrepassTextures { /// /// Used to render all 3D meshes with materials that have no transparency. pub struct Opaque3dPrepass { - pub entity: Entity, - pub asset_id: AssetId, - pub pipeline_id: CachedRenderPipelineId, - pub draw_function: DrawFunctionId, + /// Information that separates items into bins. + pub key: OpaqueNoLightmap3dBinKey, + + /// An entity from which Bevy fetches data common to all instances in this + /// batch, such as the mesh. + pub representative_entity: Entity, + pub batch_range: Range, pub dynamic_offset: Option, } -impl PhaseItem for Opaque3dPrepass { - type SortKey = (usize, AssetId); +// TODO: Try interning these. +/// The data used to bin each opaque 3D mesh in the prepass and deferred pass. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct OpaqueNoLightmap3dBinKey { + /// The ID of the GPU pipeline. + pub pipeline: CachedRenderPipelineId, + /// The function used to draw the mesh. + pub draw_function: DrawFunctionId, + + /// The ID of the mesh. + pub asset_id: AssetId, + + /// The ID of a bind group specific to the material. + /// + /// In the case of PBR, this is the `MaterialBindGroupId`. + pub material_bind_group_id: Option, +} + +impl PhaseItem for Opaque3dPrepass { #[inline] fn entity(&self) -> Entity { - self.entity - } - - #[inline] - fn sort_key(&self) -> Self::SortKey { - // Sort by pipeline, then by mesh to massively decrease drawcall counts in real scenes. - (self.pipeline_id.id(), self.asset_id) + self.representative_entity } #[inline] fn draw_function(&self) -> DrawFunctionId { - self.draw_function - } - - #[inline] - fn sort(items: &mut [Self]) { - items.sort_unstable_by_key(Self::sort_key); + self.key.draw_function } #[inline] @@ -164,10 +173,29 @@ impl PhaseItem for Opaque3dPrepass { } } +impl BinnedPhaseItem for Opaque3dPrepass { + type BinKey = OpaqueNoLightmap3dBinKey; + + #[inline] + fn new( + key: Self::BinKey, + representative_entity: Entity, + batch_range: Range, + dynamic_offset: Option, + ) -> Self { + Opaque3dPrepass { + key, + representative_entity, + batch_range, + dynamic_offset, + } + } +} + impl CachedRenderPipelinePhaseItem for Opaque3dPrepass { #[inline] fn cached_pipeline(&self) -> CachedRenderPipelineId { - self.pipeline_id + self.key.pipeline } } @@ -177,36 +205,21 @@ impl CachedRenderPipelinePhaseItem for Opaque3dPrepass { /// /// Used to render all meshes with a material with an alpha mask. pub struct AlphaMask3dPrepass { - pub asset_id: AssetId, - pub entity: Entity, - pub pipeline_id: CachedRenderPipelineId, - pub draw_function: DrawFunctionId, + pub key: OpaqueNoLightmap3dBinKey, + pub representative_entity: Entity, pub batch_range: Range, pub dynamic_offset: Option, } impl PhaseItem for AlphaMask3dPrepass { - type SortKey = (usize, AssetId); - #[inline] fn entity(&self) -> Entity { - self.entity - } - - #[inline] - fn sort_key(&self) -> Self::SortKey { - // Sort by pipeline, then by mesh to massively decrease drawcall counts in real scenes. - (self.pipeline_id.id(), self.asset_id) + self.representative_entity } #[inline] fn draw_function(&self) -> DrawFunctionId { - self.draw_function - } - - #[inline] - fn sort(items: &mut [Self]) { - items.sort_unstable_by_key(Self::sort_key); + self.key.draw_function } #[inline] @@ -230,9 +243,28 @@ impl PhaseItem for AlphaMask3dPrepass { } } +impl BinnedPhaseItem for AlphaMask3dPrepass { + type BinKey = OpaqueNoLightmap3dBinKey; + + #[inline] + fn new( + key: Self::BinKey, + representative_entity: Entity, + batch_range: Range, + dynamic_offset: Option, + ) -> Self { + Self { + key, + representative_entity, + batch_range, + dynamic_offset, + } + } +} + impl CachedRenderPipelinePhaseItem for AlphaMask3dPrepass { #[inline] fn cached_pipeline(&self) -> CachedRenderPipelineId { - self.pipeline_id + self.key.pipeline } } diff --git a/crates/bevy_core_pipeline/src/prepass/node.rs b/crates/bevy_core_pipeline/src/prepass/node.rs index b22fdf1fb6..74de568e2b 100644 --- a/crates/bevy_core_pipeline/src/prepass/node.rs +++ b/crates/bevy_core_pipeline/src/prepass/node.rs @@ -4,7 +4,7 @@ use bevy_render::{ camera::ExtractedCamera, diagnostic::RecordDiagnostics, render_graph::{NodeRunError, RenderGraphContext, ViewNode}, - render_phase::{RenderPhase, TrackedRenderPass}, + render_phase::{BinnedRenderPhase, TrackedRenderPass}, render_resource::{CommandEncoderDescriptor, RenderPassDescriptor, StoreOp}, renderer::RenderContext, view::ViewDepthTexture, @@ -23,8 +23,8 @@ pub struct PrepassNode; impl ViewNode for PrepassNode { type ViewQuery = ( &'static ExtractedCamera, - &'static RenderPhase, - &'static RenderPhase, + &'static BinnedRenderPhase, + &'static BinnedRenderPhase, &'static ViewDepthTexture, &'static ViewPrepassTextures, Option<&'static DeferredPrepass>, @@ -95,14 +95,16 @@ impl ViewNode for PrepassNode { } // Opaque draws - if !opaque_prepass_phase.items.is_empty() { + if !opaque_prepass_phase.batchable_keys.is_empty() + || !opaque_prepass_phase.unbatchable_keys.is_empty() + { #[cfg(feature = "trace")] let _opaque_prepass_span = info_span!("opaque_prepass").entered(); opaque_prepass_phase.render(&mut render_pass, world, view_entity); } // Alpha masked draws - if !alpha_mask_prepass_phase.items.is_empty() { + if !alpha_mask_prepass_phase.is_empty() { #[cfg(feature = "trace")] let _alpha_mask_prepass_span = info_span!("alpha_mask_prepass").entered(); alpha_mask_prepass_phase.render(&mut render_pass, world, view_entity); diff --git a/crates/bevy_gizmos/src/pipeline_2d.rs b/crates/bevy_gizmos/src/pipeline_2d.rs index 43cdb6822d..4a886485e8 100644 --- a/crates/bevy_gizmos/src/pipeline_2d.rs +++ b/crates/bevy_gizmos/src/pipeline_2d.rs @@ -17,7 +17,7 @@ use bevy_ecs::{ use bevy_math::FloatOrd; use bevy_render::{ render_asset::{prepare_assets, RenderAssets}, - render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline}, + render_phase::{AddRenderCommand, DrawFunctions, SetItemPipeline, SortedRenderPhase}, render_resource::*, texture::BevyDefault, view::{ExtractedView, Msaa, RenderLayers, ViewTarget}, @@ -256,7 +256,7 @@ fn queue_line_gizmos_2d( line_gizmo_assets: Res>, mut views: Query<( &ExtractedView, - &mut RenderPhase, + &mut SortedRenderPhase, Option<&RenderLayers>, )>, ) { @@ -309,7 +309,7 @@ fn queue_line_joint_gizmos_2d( line_gizmo_assets: Res>, mut views: Query<( &ExtractedView, - &mut RenderPhase, + &mut SortedRenderPhase, Option<&RenderLayers>, )>, ) { diff --git a/crates/bevy_gizmos/src/pipeline_3d.rs b/crates/bevy_gizmos/src/pipeline_3d.rs index 8a7ec11285..1fe8d9977b 100644 --- a/crates/bevy_gizmos/src/pipeline_3d.rs +++ b/crates/bevy_gizmos/src/pipeline_3d.rs @@ -21,7 +21,7 @@ use bevy_ecs::{ use bevy_pbr::{MeshPipeline, MeshPipelineKey, SetMeshViewBindGroup}; use bevy_render::{ render_asset::{prepare_assets, RenderAssets}, - render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline}, + render_phase::{AddRenderCommand, DrawFunctions, SetItemPipeline, SortedRenderPhase}, render_resource::*, texture::BevyDefault, view::{ExtractedView, Msaa, RenderLayers, ViewTarget}, @@ -281,7 +281,7 @@ fn queue_line_gizmos_3d( line_gizmo_assets: Res>, mut views: Query<( &ExtractedView, - &mut RenderPhase, + &mut SortedRenderPhase, Option<&RenderLayers>, ( Has, @@ -364,7 +364,7 @@ fn queue_line_joint_gizmos_3d( line_gizmo_assets: Res>, mut views: Query<( &ExtractedView, - &mut RenderPhase, + &mut SortedRenderPhase, Option<&RenderLayers>, ( Has, diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index 327b48e249..0a7f095b27 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -94,7 +94,6 @@ use bevy_render::{ extract_resource::ExtractResourcePlugin, render_asset::prepare_assets, render_graph::RenderGraph, - render_phase::sort_phase_system, render_resource::Shader, texture::Image, view::VisibilitySystems, @@ -375,7 +374,6 @@ impl Plugin for PbrPlugin { prepare_lights .in_set(RenderSet::ManageViews) .after(prepare_assets::), - sort_phase_system::.in_set(RenderSet::PhaseSort), prepare_clusters.in_set(RenderSet::PrepareResources), ), ) @@ -390,7 +388,7 @@ impl Plugin for PbrPlugin { render_app.ignore_ambiguity( bevy_render::Render, bevy_core_pipeline::core_3d::prepare_core_3d_transmission_textures, - bevy_render::batching::batch_and_prepare_render_phase::< + bevy_render::batching::batch_and_prepare_sorted_render_phase::< bevy_core_pipeline::core_3d::Transmissive3d, MeshPipeline, >, diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 4e42cde7f5..774aff0745 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -7,10 +7,12 @@ use crate::*; use bevy_asset::{Asset, AssetEvent, AssetId, AssetServer}; use bevy_core_pipeline::{ core_3d::{ - AlphaMask3d, Camera3d, Opaque3d, ScreenSpaceTransmissionQuality, Transmissive3d, - Transparent3d, + AlphaMask3d, Camera3d, Opaque3d, Opaque3dBinKey, ScreenSpaceTransmissionQuality, + Transmissive3d, Transparent3d, + }, + prepass::{ + DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass, OpaqueNoLightmap3dBinKey, }, - prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass}, tonemapping::{DebandDither, Tonemapping}, }; use bevy_derive::{Deref, DerefMut}; @@ -541,10 +543,10 @@ pub fn queue_material_meshes( Option<&Camera3d>, Has, Option<&Projection>, - &mut RenderPhase, - &mut RenderPhase, - &mut RenderPhase, - &mut RenderPhase, + &mut BinnedRenderPhase, + &mut BinnedRenderPhase, + &mut SortedRenderPhase, + &mut SortedRenderPhase, ( Has>, Has>, @@ -679,10 +681,11 @@ pub fn queue_material_meshes( mesh_key |= alpha_mode_pipeline_key(material.properties.alpha_mode); - if render_lightmaps + let lightmap_image = render_lightmaps .render_lightmaps - .contains_key(visible_entity) - { + .get(visible_entity) + .map(|lightmap| lightmap.image); + if lightmap_image.is_some() { mesh_key |= MeshPipelineKey::LIGHTMAPPED; } @@ -722,14 +725,14 @@ pub fn queue_material_meshes( dynamic_offset: None, }); } else if forward { - opaque_phase.add(Opaque3d { - entity: *visible_entity, + let bin_key = Opaque3dBinKey { draw_function: draw_opaque_pbr, pipeline: pipeline_id, asset_id: mesh_instance.mesh_asset_id, - batch_range: 0..1, - dynamic_offset: None, - }); + material_bind_group_id: material.get_bind_group_id().0, + lightmap_image, + }; + opaque_phase.add(bin_key, *visible_entity, mesh_instance.should_batch()); } } AlphaMode::Mask(_) => { @@ -746,14 +749,17 @@ pub fn queue_material_meshes( dynamic_offset: None, }); } else if forward { - alpha_mask_phase.add(AlphaMask3d { - entity: *visible_entity, + let bin_key = OpaqueNoLightmap3dBinKey { draw_function: draw_alpha_mask_pbr, pipeline: pipeline_id, asset_id: mesh_instance.mesh_asset_id, - batch_range: 0..1, - dynamic_offset: None, - }); + material_bind_group_id: material.get_bind_group_id().0, + }; + alpha_mask_phase.add( + bin_key, + *visible_entity, + mesh_instance.should_batch(), + ); } } AlphaMode::Blend diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 6e78c8f4c8..ab8ea45d08 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -1,5 +1,6 @@ mod prepass_bindings; +use bevy_render::batching::{batch_and_prepare_binned_render_phase, sort_binned_render_phase}; use bevy_render::mesh::MeshVertexBufferLayoutRef; use bevy_render::render_resource::binding_types::uniform_buffer; pub use prepass_bindings::*; @@ -16,7 +17,6 @@ use bevy_ecs::{ }; use bevy_math::{Affine3A, Mat4}; use bevy_render::{ - batching::batch_and_prepare_render_phase, globals::{GlobalsBuffer, GlobalsUniform}, prelude::{Camera, Mesh}, render_asset::RenderAssets, @@ -155,11 +155,17 @@ where .add_systems( Render, ( - prepare_previous_view_projection_uniforms, - batch_and_prepare_render_phase::, - batch_and_prepare_render_phase::, + ( + sort_binned_render_phase::, + sort_binned_render_phase:: + ).in_set(RenderSet::PhaseSort), + ( + prepare_previous_view_projection_uniforms, + batch_and_prepare_binned_render_phase::, + batch_and_prepare_binned_render_phase::, + ).in_set(RenderSet::PrepareResources), ) - .in_set(RenderSet::PrepareResources), ); } @@ -710,20 +716,20 @@ pub fn queue_prepass_material_meshes( ( &ExtractedView, &VisibleEntities, - Option<&mut RenderPhase>, - Option<&mut RenderPhase>, - Option<&mut RenderPhase>, - Option<&mut RenderPhase>, + Option<&mut BinnedRenderPhase>, + Option<&mut BinnedRenderPhase>, + Option<&mut BinnedRenderPhase>, + Option<&mut BinnedRenderPhase>, Option<&DepthPrepass>, Option<&NormalPrepass>, Option<&MotionVectorPrepass>, Option<&DeferredPrepass>, ), Or<( - With>, - With>, - With>, - With>, + With>, + With>, + With>, + With>, )>, >, ) where @@ -848,50 +854,54 @@ pub fn queue_prepass_material_meshes( match alpha_mode { AlphaMode::Opaque => { if deferred { - opaque_deferred_phase - .as_mut() - .unwrap() - .add(Opaque3dDeferred { - entity: *visible_entity, + opaque_deferred_phase.as_mut().unwrap().add( + OpaqueNoLightmap3dBinKey { draw_function: opaque_draw_deferred, - pipeline_id, + pipeline: pipeline_id, asset_id: mesh_instance.mesh_asset_id, - batch_range: 0..1, - dynamic_offset: None, - }); + material_bind_group_id: material.get_bind_group_id().0, + }, + *visible_entity, + mesh_instance.should_batch(), + ); } else if let Some(opaque_phase) = opaque_phase.as_mut() { - opaque_phase.add(Opaque3dPrepass { - entity: *visible_entity, - draw_function: opaque_draw_prepass, - pipeline_id, - asset_id: mesh_instance.mesh_asset_id, - batch_range: 0..1, - dynamic_offset: None, - }); + opaque_phase.add( + OpaqueNoLightmap3dBinKey { + draw_function: opaque_draw_prepass, + pipeline: pipeline_id, + asset_id: mesh_instance.mesh_asset_id, + material_bind_group_id: material.get_bind_group_id().0, + }, + *visible_entity, + mesh_instance.should_batch(), + ); } } AlphaMode::Mask(_) => { if deferred { - alpha_mask_deferred_phase - .as_mut() - .unwrap() - .add(AlphaMask3dDeferred { - entity: *visible_entity, - draw_function: alpha_mask_draw_deferred, - pipeline_id, - asset_id: mesh_instance.mesh_asset_id, - batch_range: 0..1, - dynamic_offset: None, - }); - } else if let Some(alpha_mask_phase) = alpha_mask_phase.as_mut() { - alpha_mask_phase.add(AlphaMask3dPrepass { - entity: *visible_entity, - draw_function: alpha_mask_draw_prepass, - pipeline_id, + let bin_key = OpaqueNoLightmap3dBinKey { + pipeline: pipeline_id, + draw_function: alpha_mask_draw_deferred, asset_id: mesh_instance.mesh_asset_id, - batch_range: 0..1, - dynamic_offset: None, - }); + material_bind_group_id: material.get_bind_group_id().0, + }; + alpha_mask_deferred_phase.as_mut().unwrap().add( + bin_key, + *visible_entity, + mesh_instance.should_batch(), + ); + } else if let Some(alpha_mask_phase) = alpha_mask_phase.as_mut() { + let bin_key = OpaqueNoLightmap3dBinKey { + pipeline: pipeline_id, + draw_function: alpha_mask_draw_prepass, + asset_id: mesh_instance.mesh_asset_id, + material_bind_group_id: material.get_bind_group_id().0, + }; + alpha_mask_phase.add( + bin_key, + *visible_entity, + mesh_instance.should_batch(), + ); } } AlphaMode::Blend diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 12d8961f98..2dacc9943c 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -1,6 +1,7 @@ +use bevy_asset::AssetId; use bevy_core_pipeline::core_3d::{Transparent3d, CORE_3D_DEPTH_FORMAT}; -use bevy_ecs::entity::EntityHashMap; use bevy_ecs::prelude::*; +use bevy_ecs::{entity::EntityHashMap, system::lifetimeless::Read}; use bevy_math::{Mat4, UVec3, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles}; use bevy_render::{ camera::Camera, @@ -684,7 +685,7 @@ pub fn prepare_lights( mut light_meta: ResMut, views: Query< (Entity, &ExtractedView, &ExtractedClusterConfig), - With>, + With>, >, ambient_light: Res, point_light_shadow_map: Res, @@ -1057,7 +1058,7 @@ pub fn prepare_lights( color_grading: Default::default(), }, *frustum, - RenderPhase::::default(), + BinnedRenderPhase::::default(), LightEntity::Point { light_entity, face_index, @@ -1116,7 +1117,7 @@ pub fn prepare_lights( color_grading: Default::default(), }, *spot_light_frustum.unwrap(), - RenderPhase::::default(), + BinnedRenderPhase::::default(), LightEntity::Spot { light_entity }, )) .id(); @@ -1196,7 +1197,7 @@ pub fn prepare_lights( color_grading: Default::default(), }, frustum, - RenderPhase::::default(), + BinnedRenderPhase::::default(), LightEntity::Directional { light_entity, cascade_index, @@ -1548,7 +1549,7 @@ pub fn prepare_clusters( render_queue: Res, mesh_pipeline: Res, global_light_meta: Res, - views: Query<(Entity, &ExtractedClustersPointLights), With>>, + views: Query<(Entity, &ExtractedClustersPointLights), With>>, ) { let render_device = render_device.into_inner(); let supports_storage_buffers = matches!( @@ -1605,7 +1606,7 @@ pub fn queue_shadows( pipeline_cache: Res, render_lightmaps: Res, view_lights: Query<(Entity, &ViewLightEntities)>, - mut view_light_shadow_phases: Query<(&LightEntity, &mut RenderPhase)>, + mut view_light_shadow_phases: Query<(&LightEntity, &mut BinnedRenderPhase)>, point_light_entities: Query<&CubemapVisibleEntities, With>, directional_light_entities: Query<&CascadesVisibleEntities, With>, spot_light_entities: Query<&VisibleEntities, With>, @@ -1708,52 +1709,48 @@ pub fn queue_shadows( .material_bind_group_id .set(material.get_bind_group_id()); - shadow_phase.add(Shadow { - draw_function: draw_shadow_mesh, - pipeline: pipeline_id, + shadow_phase.add( + ShadowBinKey { + draw_function: draw_shadow_mesh, + pipeline: pipeline_id, + asset_id: mesh_instance.mesh_asset_id, + }, entity, - distance: 0.0, // TODO: sort front-to-back - batch_range: 0..1, - dynamic_offset: None, - }); + mesh_instance.should_batch(), + ); } } } } pub struct Shadow { - pub distance: f32, - pub entity: Entity, - pub pipeline: CachedRenderPipelineId, - pub draw_function: DrawFunctionId, + pub key: ShadowBinKey, + pub representative_entity: Entity, pub batch_range: Range, pub dynamic_offset: Option, } -impl PhaseItem for Shadow { - type SortKey = usize; +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ShadowBinKey { + /// The identifier of the render pipeline. + pub pipeline: CachedRenderPipelineId, + /// The function used to draw. + pub draw_function: DrawFunctionId, + + /// The mesh. + pub asset_id: AssetId, +} + +impl PhaseItem for Shadow { #[inline] fn entity(&self) -> Entity { - self.entity - } - - #[inline] - fn sort_key(&self) -> Self::SortKey { - self.pipeline.id() + self.representative_entity } #[inline] fn draw_function(&self) -> DrawFunctionId { - self.draw_function - } - - #[inline] - fn sort(items: &mut [Self]) { - // The shadow phase is sorted by pipeline id for performance reasons. - // Grouping all draw commands using the same pipeline together performs - // better than rebinding everything at a high rate. - radsort::sort_by_key(items, |item| item.sort_key()); + self.key.draw_function } #[inline] @@ -1777,16 +1774,35 @@ impl PhaseItem for Shadow { } } +impl BinnedPhaseItem for Shadow { + type BinKey = ShadowBinKey; + + #[inline] + fn new( + key: Self::BinKey, + representative_entity: Entity, + batch_range: Range, + dynamic_offset: Option, + ) -> Self { + Shadow { + key, + representative_entity, + batch_range, + dynamic_offset, + } + } +} + impl CachedRenderPipelinePhaseItem for Shadow { #[inline] fn cached_pipeline(&self) -> CachedRenderPipelineId { - self.pipeline + self.key.pipeline } } pub struct ShadowPassNode { - main_view_query: QueryState<&'static ViewLightEntities>, - view_light_query: QueryState<(&'static ShadowView, &'static RenderPhase)>, + main_view_query: QueryState>, + view_light_query: QueryState<(Read, Read>)>, } impl ShadowPassNode { diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 31f3a29352..bd06ceca9c 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -13,7 +13,8 @@ use bevy_ecs::{ use bevy_math::{Affine3, Rect, UVec2, Vec4}; use bevy_render::{ batching::{ - batch_and_prepare_render_phase, write_batched_instance_buffer, GetBatchData, + batch_and_prepare_binned_render_phase, batch_and_prepare_sorted_render_phase, + sort_binned_render_phase, write_batched_instance_buffer, GetBatchData, GetBinnedBatchData, NoAutomaticBatching, }, mesh::*, @@ -125,13 +126,24 @@ impl Plugin for MeshRenderPlugin { Render, ( ( - batch_and_prepare_render_phase::, - batch_and_prepare_render_phase::, - batch_and_prepare_render_phase::, - batch_and_prepare_render_phase::, - batch_and_prepare_render_phase::, - batch_and_prepare_render_phase::, - batch_and_prepare_render_phase::, + sort_binned_render_phase::, + sort_binned_render_phase::, + sort_binned_render_phase::, + sort_binned_render_phase::, + sort_binned_render_phase::, + ) + .in_set(RenderSet::PhaseSort), + ( + batch_and_prepare_binned_render_phase::, + batch_and_prepare_sorted_render_phase::, + batch_and_prepare_sorted_render_phase::, + batch_and_prepare_binned_render_phase::, + batch_and_prepare_binned_render_phase::, + batch_and_prepare_binned_render_phase::, + batch_and_prepare_binned_render_phase::< + AlphaMask3dDeferred, + MeshPipeline, + >, ) .in_set(RenderSet::PrepareResources), write_batched_instance_buffer:: @@ -471,6 +483,25 @@ impl GetBatchData for MeshPipeline { } } +impl GetBinnedBatchData for MeshPipeline { + type Param = (SRes, SRes); + + type BufferData = MeshUniform; + + fn get_batch_data( + (mesh_instances, lightmaps): &SystemParamItem, + entity: Entity, + ) -> Option { + let mesh_instance = mesh_instances.get(&entity)?; + let maybe_lightmap = lightmaps.render_lightmaps.get(&entity); + + Some(MeshUniform::new( + &mesh_instance.transforms, + maybe_lightmap.map(|lightmap| lightmap.uv_rect), + )) + } +} + bitflags::bitflags! { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[repr(transparent)] diff --git a/crates/bevy_render/src/batching/mod.rs b/crates/bevy_render/src/batching/mod.rs index 54b0573081..576bf641fb 100644 --- a/crates/bevy_render/src/batching/mod.rs +++ b/crates/bevy_render/src/batching/mod.rs @@ -5,9 +5,13 @@ use bevy_ecs::{ system::{Query, ResMut, StaticSystemParam, SystemParam, SystemParamItem}, }; use nonmax::NonMaxU32; +use smallvec::{smallvec, SmallVec}; use crate::{ - render_phase::{CachedRenderPipelinePhaseItem, DrawFunctionId, RenderPhase}, + render_phase::{ + BinnedPhaseItem, BinnedRenderPhase, BinnedRenderPhaseBatch, CachedRenderPipelinePhaseItem, + DrawFunctionId, SortedPhaseItem, SortedRenderPhase, + }, render_resource::{CachedRenderPipelineId, GpuArrayBuffer, GpuArrayBufferable}, renderer::{RenderDevice, RenderQueue}, }; @@ -56,6 +60,8 @@ impl BatchMeta { /// A trait to support getting data used for batching draw commands via phase /// items. pub trait GetBatchData { + /// The system parameters [`GetBatchData::get_batch_data`] needs in + /// order to compute the batch data. type Param: SystemParam + 'static; /// Data used for comparison between phase items. If the pipeline id, draw /// function id, per-instance data buffer dynamic offset and this data @@ -74,13 +80,35 @@ pub trait GetBatchData { ) -> Option<(Self::BufferData, Option)>; } -/// Batch the items in a render phase. This means comparing metadata needed to draw each phase item -/// and trying to combine the draws into a batch. -pub fn batch_and_prepare_render_phase( +/// When implemented on a pipeline, this trait allows the batching logic to +/// compute the per-batch data that will be uploaded to the GPU. +/// +/// This includes things like the mesh transforms. +pub trait GetBinnedBatchData { + /// The system parameters [`GetBinnedBatchData::get_batch_data`] needs + /// in order to compute the batch data. + type Param: SystemParam + 'static; + /// The per-instance data to be inserted into the [`GpuArrayBuffer`] + /// containing these data for all instances. + type BufferData: GpuArrayBufferable + Sync + Send + 'static; + + /// Get the per-instance data to be inserted into the [`GpuArrayBuffer`]. + fn get_batch_data( + param: &SystemParamItem, + entity: Entity, + ) -> Option; +} + +/// Batch the items in a sorted render phase. This means comparing metadata +/// needed to draw each phase item and trying to combine the draws into a batch. +pub fn batch_and_prepare_sorted_render_phase( gpu_array_buffer: ResMut>, - mut views: Query<&mut RenderPhase>, + mut views: Query<&mut SortedRenderPhase>, param: StaticSystemParam, -) { +) where + I: CachedRenderPipelinePhaseItem + SortedPhaseItem, + F: GetBatchData, +{ let gpu_array_buffer = gpu_array_buffer.into_inner(); let system_param_item = param.into_inner(); @@ -115,6 +143,80 @@ pub fn batch_and_prepare_render_phase(mut views: Query<&mut BinnedRenderPhase>) +where + BPI: BinnedPhaseItem, +{ + for mut phase in &mut views { + phase.batchable_keys.sort_unstable(); + phase.unbatchable_keys.sort_unstable(); + } +} + +/// Creates batches for a render phase that uses bins. +pub fn batch_and_prepare_binned_render_phase( + gpu_array_buffer: ResMut>, + mut views: Query<&mut BinnedRenderPhase>, + param: StaticSystemParam, +) where + BPI: BinnedPhaseItem, + GBBD: GetBinnedBatchData, +{ + let gpu_array_buffer = gpu_array_buffer.into_inner(); + let system_param_item = param.into_inner(); + + for mut phase in &mut views { + let phase = &mut *phase; // Borrow checker. + + // Prepare batchables. + + for key in &phase.batchable_keys { + let mut batch_set: SmallVec<[BinnedRenderPhaseBatch; 1]> = smallvec![]; + for &entity in &phase.batchable_values[key] { + let Some(buffer_data) = GBBD::get_batch_data(&system_param_item, entity) else { + continue; + }; + + let instance = gpu_array_buffer.push(buffer_data); + + // If the dynamic offset has changed, flush the batch. + // + // This is the only time we ever have more than one batch per + // bin. Note that dynamic offsets are only used on platforms + // with no storage buffers. + if !batch_set.last().is_some_and(|batch| { + batch.instance_range.end == instance.index + && batch.dynamic_offset == instance.dynamic_offset + }) { + batch_set.push(BinnedRenderPhaseBatch { + representative_entity: entity, + instance_range: instance.index..instance.index, + dynamic_offset: instance.dynamic_offset, + }); + } + + if let Some(batch) = batch_set.last_mut() { + batch.instance_range.end = instance.index + 1; + } + } + + phase.batch_sets.push(batch_set); + } + + // Prepare unbatchables. + for key in &phase.unbatchable_keys { + let unbatchables = phase.unbatchable_values.get_mut(key).unwrap(); + for &entity in &unbatchables.entities { + if let Some(buffer_data) = GBBD::get_batch_data(&system_param_item, entity) { + let instance = gpu_array_buffer.push(buffer_data); + unbatchables.buffer_indices.add(instance); + } + } + } + } +} + pub fn write_batched_instance_buffer( render_device: Res, render_queue: Res, diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 72c30775f4..8cd9428048 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -110,13 +110,15 @@ pub enum RenderSet { PrepareAssets, /// Create any additional views such as those used for shadow mapping. ManageViews, - /// Queue drawable entities as phase items in [`RenderPhase`](crate::render_phase::RenderPhase)s - /// ready for sorting + /// Queue drawable entities as phase items in render phases ready for + /// sorting (if necessary) Queue, /// A sub-set within [`Queue`](RenderSet::Queue) where mesh entity queue systems are executed. Ensures `prepare_assets::` is completed. QueueMeshes, - // TODO: This could probably be moved in favor of a system ordering abstraction in `Render` or `Queue` - /// Sort the [`RenderPhases`](render_phase::RenderPhase) here. + // TODO: This could probably be moved in favor of a system ordering + // abstraction in `Render` or `Queue` + /// Sort the [`SortedRenderPhase`](render_phase::SortedRenderPhase)s and + /// [`BinKey`](render_phase::BinnedPhaseItem::BinKey)s here. PhaseSort, /// Prepare render resources from extracted data for the GPU based on their sorted order. /// Create [`BindGroups`](render_resource::BindGroup) that depend on those data. diff --git a/crates/bevy_render/src/render_phase/draw.rs b/crates/bevy_render/src/render_phase/draw.rs index aa05fa3e49..4dc6fc3e08 100644 --- a/crates/bevy_render/src/render_phase/draw.rs +++ b/crates/bevy_render/src/render_phase/draw.rs @@ -39,7 +39,7 @@ pub trait Draw: Send + Sync + 'static { // TODO: make this generic? /// An identifier for a [`Draw`] function stored in [`DrawFunctions`]. -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)] pub struct DrawFunctionId(u32); /// Stores all [`Draw`] functions for the [`PhaseItem`] type. @@ -209,6 +209,7 @@ pub trait RenderCommand { } /// The result of a [`RenderCommand`]. +#[derive(Debug)] pub enum RenderCommandResult { Success, Failure, @@ -301,7 +302,7 @@ where /// Registers a [`RenderCommand`] as a [`Draw`] function. /// They are stored inside the [`DrawFunctions`] resource of the app. pub trait AddRenderCommand { - /// Adds the [`RenderCommand`] for the specified [`RenderPhase`](super::RenderPhase) to the app. + /// Adds the [`RenderCommand`] for the specified render phase to the app. fn add_render_command + Send + Sync + 'static>( &mut self, ) -> &mut Self diff --git a/crates/bevy_render/src/render_phase/mod.rs b/crates/bevy_render/src/render_phase/mod.rs index 1a3a544b46..40c4153f3f 100644 --- a/crates/bevy_render/src/render_phase/mod.rs +++ b/crates/bevy_render/src/render_phase/mod.rs @@ -1,7 +1,7 @@ //! The modular rendering abstraction responsible for queuing, preparing, sorting and drawing //! entities as part of separate render phases. //! -//! In Bevy each view (camera, or shadow-casting light, etc.) has one or multiple [`RenderPhase`]s +//! In Bevy each view (camera, or shadow-casting light, etc.) has one or multiple render phases //! (e.g. opaque, transparent, shadow, etc). //! They are used to queue entities for rendering. //! Multiple phases might be required due to different sorting/batching behaviors @@ -29,17 +29,20 @@ mod draw; mod draw_state; mod rangefinder; +use bevy_utils::{default, hashbrown::hash_map::Entry, HashMap}; pub use draw::*; pub use draw_state::*; +use encase::{internal::WriteInto, ShaderSize}; use nonmax::NonMaxU32; pub use rangefinder::*; -use crate::render_resource::{CachedRenderPipelineId, PipelineCache}; +use crate::render_resource::{CachedRenderPipelineId, GpuArrayBufferIndex, PipelineCache}; use bevy_ecs::{ prelude::*, system::{lifetimeless::SRes, SystemParamItem}, }; -use std::{ops::Range, slice::SliceIndex}; +use smallvec::SmallVec; +use std::{hash::Hash, ops::Range, slice::SliceIndex}; /// A collection of all rendering instructions, that will be executed by the GPU, for a /// single render phase for a single view. @@ -51,18 +54,351 @@ use std::{ops::Range, slice::SliceIndex}; /// the rendered texture of the previous phase (e.g. for screen-space reflections). /// All [`PhaseItem`]s are then rendered using a single [`TrackedRenderPass`]. /// The render pass might be reused for multiple phases to reduce GPU overhead. +/// +/// This flavor of render phase is used for phases in which the ordering is less +/// critical: for example, `Opaque3d`. It's generally faster than the +/// alternative [`SortedRenderPhase`]. #[derive(Component)] -pub struct RenderPhase { +pub struct BinnedRenderPhase +where + BPI: BinnedPhaseItem, +{ + /// A list of `BinKey`s for batchable items. + /// + /// These are accumulated in `queue_material_meshes` and then sorted in + /// `batch_and_prepare_binned_render_phase`. + pub batchable_keys: Vec, + + /// The batchable bins themselves. + /// + /// Each bin corresponds to a single batch set. For unbatchable entities, + /// prefer `unbatchable_values` instead. + pub(crate) batchable_values: HashMap>, + + /// 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_keys: Vec, + + /// The unbatchable bins. + /// + /// Each entity here is rendered in a separate drawcall. + pub(crate) unbatchable_values: HashMap, + + /// Information on each batch set. + /// + /// A *batch set* is a set of entities that will be batched together unless + /// we're on a platform that doesn't support storage buffers (e.g. WebGL 2) + /// and differing dynamic uniform indices force us to break batches. On + /// 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: Vec>, +} + +/// Information about a single batch of entities rendered using binned phase +/// items. +#[derive(Debug)] +pub struct BinnedRenderPhaseBatch { + /// An entity that's *representative* of this batch. + /// + /// Bevy uses this to fetch the mesh. It can be any entity in the batch. + pub representative_entity: Entity, + + /// The range of instance indices in this batch. + pub instance_range: Range, + + /// The dynamic offset of the batch. + /// + /// Note that dynamic offsets are only used on platforms that don't support + /// storage buffers. + pub dynamic_offset: Option, +} + +/// Information about the unbatchable entities in a bin. +pub(crate) struct UnbatchableBinnedEntities { + /// The entities. + pub(crate) entities: Vec, + + /// The GPU array buffer indices of each unbatchable binned entity. + pub(crate) buffer_indices: UnbatchableBinnedEntityBufferIndex, +} + +/// Stores instance indices and dynamic offsets for unbatchable entities in a +/// binned render phase. +/// +/// This is conceptually `Vec`, but it +/// avoids the overhead of storing dynamic offsets on platforms that support +/// them. In other words, this allows a fast path that avoids allocation on +/// platforms that aren't WebGL 2. +#[derive(Default)] + +pub(crate) enum UnbatchableBinnedEntityBufferIndex { + /// There are no unbatchable entities in this bin (yet). + #[default] + NoEntities, + + /// The instances for all unbatchable entities in this bin are contiguous, + /// and there are no dynamic uniforms. + /// + /// This is the typical case on platforms other than WebGL 2. We special + /// case this to avoid allocation on those platforms. + NoDynamicOffsets { + /// The range of indices. + instance_range: Range, + }, + + /// Dynamic uniforms are present for unbatchable entities in this bin. + /// + /// We fall back to this on WebGL 2. + DynamicOffsets(Vec), +} + +/// 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)] +pub(crate) struct UnbatchableBinnedEntityDynamicOffset { + /// The instance index. + instance_index: u32, + /// The dynamic offset, if present. + dynamic_offset: Option, +} + +impl BinnedRenderPhase +where + BPI: BinnedPhaseItem, +{ + /// Bins a new entity. + /// + /// `batchable` specifies whether the entity can be batched with other + /// entities of the same type. + pub fn add(&mut self, key: BPI::BinKey, entity: Entity, batchable: bool) { + if batchable { + match self.batchable_values.entry(key.clone()) { + Entry::Occupied(mut entry) => entry.get_mut().push(entity), + Entry::Vacant(entry) => { + self.batchable_keys.push(key); + entry.insert(vec![entity]); + } + } + } else { + match self.unbatchable_values.entry(key.clone()) { + Entry::Occupied(mut entry) => entry.get_mut().entities.push(entity), + Entry::Vacant(entry) => { + self.unbatchable_keys.push(key); + entry.insert(UnbatchableBinnedEntities { + entities: vec![entity], + buffer_indices: default(), + }); + } + } + } + } + + /// Encodes the GPU commands needed to render all entities in this phase. + pub fn render<'w>( + &self, + render_pass: &mut TrackedRenderPass<'w>, + world: &'w World, + view: Entity, + ) { + let draw_functions = world.resource::>(); + let mut draw_functions = draw_functions.write(); + draw_functions.prepare(world); + + // Encode draws for batchables. + debug_assert_eq!(self.batchable_keys.len(), self.batch_sets.len()); + for (key, batch_set) in self.batchable_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.dynamic_offset, + ); + + // 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); + } + } + + // Encode draws for unbatchables. + + for key in &self.unbatchable_keys { + let unbatchable_entities = &self.unbatchable_values[key]; + for (entity_index, &entity) in unbatchable_entities.entities.iter().enumerate() { + let unbatchable_dynamic_offset = match &unbatchable_entities.buffer_indices { + UnbatchableBinnedEntityBufferIndex::NoEntities => { + // Shouldn't happen… + continue; + } + UnbatchableBinnedEntityBufferIndex::NoDynamicOffsets { instance_range } => { + UnbatchableBinnedEntityDynamicOffset { + instance_index: instance_range.start + entity_index as u32, + dynamic_offset: None, + } + } + UnbatchableBinnedEntityBufferIndex::DynamicOffsets(ref dynamic_offsets) => { + dynamic_offsets[entity_index] + } + }; + + let binned_phase_item = BPI::new( + key.clone(), + entity, + unbatchable_dynamic_offset.instance_index + ..(unbatchable_dynamic_offset.instance_index + 1), + unbatchable_dynamic_offset.dynamic_offset, + ); + + // 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); + } + } + } + + pub fn is_empty(&self) -> bool { + self.batchable_keys.is_empty() && self.unbatchable_keys.is_empty() + } +} + +impl Default for BinnedRenderPhase +where + BPI: BinnedPhaseItem, +{ + fn default() -> Self { + Self { + batchable_keys: vec![], + batchable_values: HashMap::default(), + unbatchable_keys: vec![], + unbatchable_values: HashMap::default(), + batch_sets: vec![], + } + } +} + +impl UnbatchableBinnedEntityBufferIndex { + /// Adds a new entity to the list of unbatchable binned entities. + pub fn add(&mut self, gpu_array_buffer_index: GpuArrayBufferIndex) + where + T: ShaderSize + WriteInto + Clone, + { + match (&mut *self, gpu_array_buffer_index.dynamic_offset) { + (UnbatchableBinnedEntityBufferIndex::NoEntities, None) => { + // This is the first entity we've seen, and we're not on WebGL + // 2. Initialize the fast path. + *self = UnbatchableBinnedEntityBufferIndex::NoDynamicOffsets { + instance_range: gpu_array_buffer_index.index + ..(gpu_array_buffer_index.index + 1), + } + } + + (UnbatchableBinnedEntityBufferIndex::NoEntities, Some(dynamic_offset)) => { + // This is the first entity we've seen, and we're on WebGL 2. + // Initialize an array. + *self = UnbatchableBinnedEntityBufferIndex::DynamicOffsets(vec![ + UnbatchableBinnedEntityDynamicOffset { + instance_index: gpu_array_buffer_index.index, + dynamic_offset: Some(dynamic_offset), + }, + ]); + } + + ( + UnbatchableBinnedEntityBufferIndex::NoDynamicOffsets { + ref mut instance_range, + }, + None, + ) if instance_range.end == gpu_array_buffer_index.index => { + // This is the normal case on non-WebGL 2. + instance_range.end += 1; + } + + ( + UnbatchableBinnedEntityBufferIndex::DynamicOffsets(ref mut offsets), + dynamic_offset, + ) => { + // This is the normal case on WebGL 2. + offsets.push(UnbatchableBinnedEntityDynamicOffset { + instance_index: gpu_array_buffer_index.index, + dynamic_offset, + }); + } + + ( + UnbatchableBinnedEntityBufferIndex::NoDynamicOffsets { instance_range }, + dynamic_offset, + ) => { + // We thought we were in non-WebGL 2 mode, but we got a dynamic + // offset or non-contiguous index anyway. This shouldn't happen, + // but let's go ahead and do the sensible thing anyhow: demote + // the compressed `NoDynamicOffsets` field to the full + // `DynamicOffsets` array. + let mut new_dynamic_offsets: Vec<_> = instance_range + .map(|instance_index| UnbatchableBinnedEntityDynamicOffset { + instance_index, + dynamic_offset: None, + }) + .collect(); + new_dynamic_offsets.push(UnbatchableBinnedEntityDynamicOffset { + instance_index: gpu_array_buffer_index.index, + dynamic_offset, + }); + *self = UnbatchableBinnedEntityBufferIndex::DynamicOffsets(new_dynamic_offsets); + } + } + } +} + +/// A collection of all items to be rendered that will be encoded to GPU +/// commands for a single render phase for a single view. +/// +/// Each view (camera, or shadow-casting light, etc.) can have one or multiple render phases. +/// They are used to queue entities for rendering. +/// Multiple phases might be required due to different sorting/batching behaviors +/// (e.g. opaque: front to back, transparent: back to front) or because one phase depends on +/// the rendered texture of the previous phase (e.g. for screen-space reflections). +/// All [`PhaseItem`]s are then rendered using a single [`TrackedRenderPass`]. +/// The render pass might be reused for multiple phases to reduce GPU overhead. +/// +/// This flavor of render phase is used only for meshes that need to be sorted +/// back-to-front, such as transparent meshes. For items that don't need strict +/// sorting, [`BinnedRenderPhase`] is preferred, for performance. +#[derive(Component)] +pub struct SortedRenderPhase +where + I: SortedPhaseItem, +{ pub items: Vec, } -impl Default for RenderPhase { +impl Default for SortedRenderPhase +where + I: SortedPhaseItem, +{ fn default() -> Self { Self { items: Vec::new() } } } -impl RenderPhase { +impl SortedRenderPhase +where + I: SortedPhaseItem, +{ /// Adds a [`PhaseItem`] to this render phase. #[inline] pub fn add(&mut self, item: I) { @@ -123,22 +459,31 @@ impl RenderPhase { } /// An item (entity of the render world) which will be drawn to a texture or the screen, -/// as part of a [`RenderPhase`]. +/// as part of a render phase. /// /// The data required for rendering an entity is extracted from the main world in the /// [`ExtractSchedule`](crate::ExtractSchedule). /// Then it has to be queued up for rendering during the /// [`RenderSet::Queue`](crate::RenderSet::Queue), by adding a corresponding phase item to /// a render phase. -/// Afterwards it will be sorted and rendered automatically in the +/// Afterwards it will be possibly sorted and rendered automatically in the /// [`RenderSet::PhaseSort`](crate::RenderSet::PhaseSort) and /// [`RenderSet::Render`](crate::RenderSet::Render), respectively. +/// +/// `PhaseItem`s come in two flavors: [`BinnedPhaseItem`]s and +/// [`SortedPhaseItem`]s. +/// +/// * Binned phase items have a `BinKey` which specifies what bin they're to be +/// placed in. All items in the same bin are eligible to be batched together. +/// The `BinKey`s are sorted, but the individual bin items aren't. Binned phase +/// items are good for opaque meshes, in which the order of rendering isn't +/// important. Generally, binned phase items are faster than sorted phase items. +/// +/// * Sorted phase items, on the other hand, are placed into one large buffer +/// and then sorted all at once. This is needed for transparent meshes, which +/// have to be sorted back-to-front to render with the painter's algorithm. +/// These types of phase items are generally slower than binned phase items. pub trait PhaseItem: Sized + Send + Sync + 'static { - /// The type used for ordering the items. The smallest values are drawn first. - /// This order can be calculated using the [`ViewRangefinder3d`], - /// based on the view-space `Z` value of the corresponding view matrix. - type SortKey: Ord; - /// Whether or not this `PhaseItem` should be subjected to automatic batching. (Default: `true`) const AUTOMATIC_BATCHING: bool = true; @@ -148,12 +493,63 @@ pub trait PhaseItem: Sized + Send + Sync + 'static { /// from the render world . fn entity(&self) -> Entity; - /// Determines the order in which the items are drawn. - fn sort_key(&self) -> Self::SortKey; - /// Specifies the [`Draw`] function used to render the item. fn draw_function(&self) -> DrawFunctionId; + /// The range of instances that the batch covers. After doing a batched draw, batch range + /// length phase items will be skipped. This design is to avoid having to restructure the + /// render phase unnecessarily. + fn batch_range(&self) -> &Range; + fn batch_range_mut(&mut self) -> &mut Range; + + fn dynamic_offset(&self) -> Option; + fn dynamic_offset_mut(&mut self) -> &mut Option; +} + +/// Represents phase items that are placed into bins. The `BinKey` specifies +/// which bin they're to be placed in. Bin keys are sorted, and items within the +/// same bin are eligible to be batched together. The elements within the bins +/// aren't themselves sorted. +/// +/// An example of a binned phase item is `Opaque3d`, for which the rendering +/// order isn't critical. +pub trait BinnedPhaseItem: PhaseItem { + /// The key used for binning [`PhaseItem`]s into bins. Order the members of + /// [`BinnedPhaseItem::BinKey`] by the order of binding for best + /// performance. For example, pipeline id, draw function id, mesh asset id, + /// 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; + + /// Creates a new binned phase item from the key and per-entity data. + /// + /// Unlike [`SortedPhaseItem`]s, this is generally called "just in time" + /// before rendering. The resulting phase item isn't stored in any data + /// structures, resulting in significant memory savings. + fn new( + key: Self::BinKey, + representative_entity: Entity, + batch_range: Range, + dynamic_offset: Option, + ) -> Self; +} + +/// 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. +/// +/// An example of a sorted phase item is `Transparent3d`, which must be sorted +/// back to front in order to correctly render with the painter's algorithm. +pub trait SortedPhaseItem: PhaseItem { + /// The type used for ordering the items. The smallest values are drawn first. + /// This order can be calculated using the [`ViewRangefinder3d`], + /// based on the view-space `Z` value of the corresponding view matrix. + type SortKey: Ord; + + /// Determines the order in which the items are drawn. + fn sort_key(&self) -> Self::SortKey; + /// Sorts a slice of phase items into render order. Generally if the same type /// is batched this should use a stable sort like [`slice::sort_by_key`]. /// In almost all other cases, this should not be altered from the default, @@ -170,15 +566,6 @@ pub trait PhaseItem: Sized + Send + Sync + 'static { fn sort(items: &mut [Self]) { items.sort_unstable_by_key(|item| item.sort_key()); } - - /// The range of instances that the batch covers. After doing a batched draw, batch range - /// length phase items will be skipped. This design is to avoid having to restructure the - /// render phase unnecessarily. - fn batch_range(&self) -> &Range; - fn batch_range_mut(&mut self) -> &mut Range; - - fn dynamic_offset(&self) -> Option; - fn dynamic_offset_mut(&mut self) -> &mut Option; } /// A [`PhaseItem`] item, that automatically sets the appropriate render pipeline, @@ -218,8 +605,12 @@ impl RenderCommand

for SetItemPipeline { } } -/// This system sorts the [`PhaseItem`]s of all [`RenderPhase`]s of this type. -pub fn sort_phase_system(mut render_phases: Query<&mut RenderPhase>) { +/// This system sorts the [`PhaseItem`]s of all [`SortedRenderPhase`]s of this +/// type. +pub fn sort_phase_system(mut render_phases: Query<&mut SortedRenderPhase>) +where + I: SortedPhaseItem, +{ for mut phase in &mut render_phases { phase.sort(); } diff --git a/crates/bevy_render/src/render_resource/pipeline_cache.rs b/crates/bevy_render/src/render_resource/pipeline_cache.rs index 2e2f2eeafa..f24ab7d8b5 100644 --- a/crates/bevy_render/src/render_resource/pipeline_cache.rs +++ b/crates/bevy_render/src/render_resource/pipeline_cache.rs @@ -50,7 +50,7 @@ pub enum Pipeline { type CachedPipelineId = usize; /// Index of a cached render pipeline in a [`PipelineCache`]. -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)] pub struct CachedRenderPipelineId(CachedPipelineId); impl CachedRenderPipelineId { diff --git a/crates/bevy_render/src/render_resource/resource_macros.rs b/crates/bevy_render/src/render_resource/resource_macros.rs index c027a92e28..68896092ce 100644 --- a/crates/bevy_render/src/render_resource/resource_macros.rs +++ b/crates/bevy_render/src/render_resource/resource_macros.rs @@ -148,7 +148,7 @@ macro_rules! render_resource_wrapper { #[macro_export] macro_rules! define_atomic_id { ($atomic_id_type:ident) => { - #[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] + #[derive(Copy, Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)] pub struct $atomic_id_type(core::num::NonZeroU32); // We use new instead of default to indicate that each ID created will be unique. diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index 9938142095..a4533665e4 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -17,7 +17,7 @@ use bevy_render::{ render_asset::{prepare_assets, RenderAssets}, render_phase::{ AddRenderCommand, DrawFunctions, PhaseItem, RenderCommand, RenderCommandResult, - RenderPhase, SetItemPipeline, TrackedRenderPass, + SetItemPipeline, SortedRenderPhase, TrackedRenderPass, }, render_resource::{ AsBindGroup, AsBindGroupError, BindGroup, BindGroupId, BindGroupLayout, @@ -388,7 +388,7 @@ pub fn queue_material2d_meshes( &VisibleEntities, Option<&Tonemapping>, Option<&DebandDither>, - &mut RenderPhase, + &mut SortedRenderPhase, )>, ) where M::Data: PartialEq + Eq + Hash + Clone, diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index 5cd5fcb894..b014f10071 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -14,7 +14,7 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::mesh::MeshVertexBufferLayoutRef; use bevy_render::{ batching::{ - batch_and_prepare_render_phase, write_batched_instance_buffer, GetBatchData, + batch_and_prepare_sorted_render_phase, write_batched_instance_buffer, GetBatchData, NoAutomaticBatching, }, globals::{GlobalsBuffer, GlobalsUniform}, @@ -101,7 +101,7 @@ impl Plugin for Mesh2dRenderPlugin { .add_systems( Render, ( - batch_and_prepare_render_phase:: + batch_and_prepare_sorted_render_phase:: .in_set(RenderSet::PrepareResources), write_batched_instance_buffer:: .in_set(RenderSet::PrepareResourcesFlush), diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index d22c7865d3..a3160342bc 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -19,8 +19,8 @@ use bevy_math::{Affine3A, FloatOrd, Quat, Rect, Vec2, Vec4}; use bevy_render::{ render_asset::RenderAssets, render_phase::{ - DrawFunctions, PhaseItem, RenderCommand, RenderCommandResult, RenderPhase, SetItemPipeline, - TrackedRenderPass, + DrawFunctions, PhaseItem, RenderCommand, RenderCommandResult, SetItemPipeline, + SortedRenderPhase, TrackedRenderPass, }, render_resource::{ binding_types::{sampler, texture_2d, uniform_buffer}, @@ -448,7 +448,7 @@ pub fn queue_sprites( msaa: Res, extracted_sprites: Res, mut views: Query<( - &mut RenderPhase, + &mut SortedRenderPhase, &VisibleEntities, &ExtractedView, Option<&Tonemapping>, @@ -530,7 +530,7 @@ pub fn prepare_sprites( mut image_bind_groups: ResMut, gpu_images: Res>, extracted_sprites: Res, - mut phases: Query<&mut RenderPhase>, + mut phases: Query<&mut SortedRenderPhase>, events: Res, ) { // If an image has changed, the GpuImage has (probably) changed diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index cd2d3d17ec..5ba9ba77ed 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -29,7 +29,7 @@ use bevy_render::{ camera::Camera, render_asset::RenderAssets, render_graph::{RenderGraph, RunGraphOnViewNode}, - render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions, RenderPhase}, + render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions, SortedRenderPhase}, render_resource::*, renderer::{RenderDevice, RenderQueue}, texture::Image, @@ -704,7 +704,7 @@ pub fn extract_default_ui_camera_view( .id(); commands.get_or_spawn(entity).insert(( DefaultCameraView(default_camera_view), - RenderPhase::::default(), + SortedRenderPhase::::default(), )); } } @@ -874,7 +874,7 @@ pub fn queue_uinodes( extracted_uinodes: Res, ui_pipeline: Res, mut pipelines: ResMut>, - mut views: Query<(&ExtractedView, &mut RenderPhase)>, + mut views: Query<(&ExtractedView, &mut SortedRenderPhase)>, pipeline_cache: Res, draw_functions: Res>, ) { @@ -921,7 +921,7 @@ pub fn prepare_uinodes( ui_pipeline: Res, mut image_bind_groups: ResMut, gpu_images: Res>, - mut phases: Query<&mut RenderPhase>, + mut phases: Query<&mut SortedRenderPhase>, events: Res, mut previous_len: Local, ) { diff --git a/crates/bevy_ui/src/render/render_pass.rs b/crates/bevy_ui/src/render/render_pass.rs index 1cc0b766e6..e398a46d93 100644 --- a/crates/bevy_ui/src/render/render_pass.rs +++ b/crates/bevy_ui/src/render/render_pass.rs @@ -20,7 +20,7 @@ use nonmax::NonMaxU32; pub struct UiPassNode { ui_view_query: QueryState< ( - &'static RenderPhase, + &'static SortedRenderPhase, &'static ViewTarget, &'static ExtractedCamera, ), @@ -96,28 +96,16 @@ pub struct TransparentUi { } impl PhaseItem for TransparentUi { - type SortKey = (FloatOrd, u32); - #[inline] fn entity(&self) -> Entity { self.entity } - #[inline] - fn sort_key(&self) -> Self::SortKey { - self.sort_key - } - #[inline] fn draw_function(&self) -> DrawFunctionId { self.draw_function } - #[inline] - fn sort(items: &mut [Self]) { - items.sort_by_key(|item| item.sort_key()); - } - #[inline] fn batch_range(&self) -> &Range { &self.batch_range @@ -139,6 +127,20 @@ impl PhaseItem for TransparentUi { } } +impl SortedPhaseItem for TransparentUi { + type SortKey = (FloatOrd, u32); + + #[inline] + fn sort_key(&self) -> Self::SortKey { + self.sort_key + } + + #[inline] + fn sort(items: &mut [Self]) { + items.sort_by_key(|item| item.sort_key()); + } +} + impl CachedRenderPipelinePhaseItem for TransparentUi { #[inline] fn cached_pipeline(&self) -> CachedRenderPipelineId { diff --git a/crates/bevy_ui/src/render/ui_material_pipeline.rs b/crates/bevy_ui/src/render/ui_material_pipeline.rs index 7316905dd0..2ab70e7223 100644 --- a/crates/bevy_ui/src/render/ui_material_pipeline.rs +++ b/crates/bevy_ui/src/render/ui_material_pipeline.rs @@ -460,7 +460,7 @@ pub fn prepare_uimaterial_nodes( view_uniforms: Res, globals_buffer: Res, ui_material_pipeline: Res>, - mut phases: Query<&mut RenderPhase>, + mut phases: Query<&mut SortedRenderPhase>, mut previous_len: Local, ) { if let (Some(view_binding), Some(globals_binding)) = ( @@ -757,7 +757,7 @@ pub fn queue_ui_material_nodes( mut pipelines: ResMut>>, pipeline_cache: Res, render_materials: Res>, - mut views: Query<(&ExtractedView, &mut RenderPhase)>, + mut views: Query<(&ExtractedView, &mut SortedRenderPhase)>, ) where M::Data: PartialEq + Eq + Hash + Clone, { diff --git a/examples/2d/mesh2d_manual.rs b/examples/2d/mesh2d_manual.rs index fa760345e5..25af819323 100644 --- a/examples/2d/mesh2d_manual.rs +++ b/examples/2d/mesh2d_manual.rs @@ -13,7 +13,7 @@ use bevy::{ render::{ mesh::{Indices, MeshVertexAttribute}, render_asset::{RenderAssetUsages, RenderAssets}, - render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline}, + render_phase::{AddRenderCommand, DrawFunctions, SetItemPipeline, SortedRenderPhase}, render_resource::{ BlendState, ColorTargetState, ColorWrites, Face, FragmentState, FrontFace, MultisampleState, PipelineCache, PolygonMode, PrimitiveState, PrimitiveTopology, @@ -360,7 +360,7 @@ pub fn queue_colored_mesh2d( render_mesh_instances: Res, mut views: Query<( &VisibleEntities, - &mut RenderPhase, + &mut SortedRenderPhase, &ExtractedView, )>, ) { diff --git a/examples/shader/shader_instancing.rs b/examples/shader/shader_instancing.rs index 47c7b6e4ba..575f287962 100644 --- a/examples/shader/shader_instancing.rs +++ b/examples/shader/shader_instancing.rs @@ -16,7 +16,7 @@ use bevy::{ render_asset::RenderAssets, render_phase::{ AddRenderCommand, DrawFunctions, PhaseItem, RenderCommand, RenderCommandResult, - RenderPhase, SetItemPipeline, TrackedRenderPass, + SetItemPipeline, SortedRenderPhase, TrackedRenderPass, }, render_resource::*, renderer::RenderDevice, @@ -117,7 +117,7 @@ fn queue_custom( meshes: Res>, render_mesh_instances: Res, material_meshes: Query>, - mut views: Query<(&ExtractedView, &mut RenderPhase)>, + mut views: Query<(&ExtractedView, &mut SortedRenderPhase)>, ) { let draw_custom = transparent_3d_draw_functions.read().id::();