diff --git a/crates/bevy_core_pipeline/src/core_2d/main_transparent_pass_2d_node.rs b/crates/bevy_core_pipeline/src/core_2d/main_transparent_pass_2d_node.rs index 9d3e89e877..c5dafd45b7 100644 --- a/crates/bevy_core_pipeline/src/core_2d/main_transparent_pass_2d_node.rs +++ b/crates/bevy_core_pipeline/src/core_2d/main_transparent_pass_2d_node.rs @@ -4,7 +4,7 @@ use bevy_render::{ camera::ExtractedCamera, diagnostic::RecordDiagnostics, render_graph::{NodeRunError, RenderGraphContext, ViewNode}, - render_phase::SortedRenderPhase, + render_phase::ViewSortedRenderPhases, render_resource::RenderPassDescriptor, renderer::RenderContext, view::ViewTarget, @@ -16,20 +16,25 @@ use bevy_utils::tracing::info_span; pub struct MainTransparentPass2dNode {} impl ViewNode for MainTransparentPass2dNode { - type ViewQuery = ( - &'static ExtractedCamera, - &'static SortedRenderPhase, - &'static ViewTarget, - ); + type ViewQuery = (&'static ExtractedCamera, &'static ViewTarget); fn run<'w>( &self, graph: &mut RenderGraphContext, render_context: &mut RenderContext<'w>, - (camera, transparent_phase, target): bevy_ecs::query::QueryItem<'w, Self::ViewQuery>, + (camera, target): bevy_ecs::query::QueryItem<'w, Self::ViewQuery>, world: &'w World, ) -> Result<(), NodeRunError> { + let Some(transparent_phases) = + world.get_resource::>() + else { + return Ok(()); + }; + let view_entity = graph.view_entity(); + let Some(transparent_phase) = transparent_phases.get(&view_entity) else { + return Ok(()); + }; // This needs to run at least once to clear the background color, even if there are no items to render { diff --git a/crates/bevy_core_pipeline/src/core_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs index cf48b4030f..2cfe3b8e18 100644 --- a/crates/bevy_core_pipeline/src/core_2d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -32,7 +32,7 @@ pub use camera_2d::*; pub use main_transparent_pass_2d_node::*; use bevy_app::{App, Plugin}; -use bevy_ecs::prelude::*; +use bevy_ecs::{entity::EntityHashSet, prelude::*}; use bevy_math::FloatOrd; use bevy_render::{ camera::Camera, @@ -40,7 +40,7 @@ use bevy_render::{ render_graph::{EmptyNode, RenderGraphApp, ViewNodeRunner}, render_phase::{ sort_phase_system, CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, PhaseItem, - PhaseItemExtraIndex, SortedPhaseItem, SortedRenderPhase, + PhaseItemExtraIndex, SortedPhaseItem, ViewSortedRenderPhases, }, render_resource::CachedRenderPipelineId, Extract, ExtractSchedule, Render, RenderApp, RenderSet, @@ -62,6 +62,7 @@ impl Plugin for Core2dPlugin { }; render_app .init_resource::>() + .init_resource::>() .add_systems(ExtractSchedule, extract_core_2d_camera_phases) .add_systems( Render, @@ -158,13 +159,23 @@ impl CachedRenderPipelinePhaseItem for Transparent2d { pub fn extract_core_2d_camera_phases( mut commands: Commands, + mut transparent_2d_phases: ResMut>, cameras_2d: Extract>>, + mut live_entities: Local, ) { + live_entities.clear(); + for (entity, camera) in &cameras_2d { - if camera.is_active { - commands - .get_or_spawn(entity) - .insert(SortedRenderPhase::::default()); + if !camera.is_active { + continue; } + + commands.get_or_spawn(entity); + transparent_2d_phases.insert_or_clear(entity); + + live_entities.insert(entity); } + + // Clear out all dead views. + transparent_2d_phases.retain(|camera_entity, _| live_entities.contains(camera_entity)); } 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 2d490ac9bf..29fcc7d846 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 @@ -2,12 +2,12 @@ use crate::{ core_3d::Opaque3d, skybox::{SkyboxBindGroup, SkyboxPipelineId}, }; -use bevy_ecs::{prelude::World, query::QueryItem}; +use bevy_ecs::{entity::Entity, prelude::World, query::QueryItem}; use bevy_render::{ camera::ExtractedCamera, diagnostic::RecordDiagnostics, render_graph::{NodeRunError, RenderGraphContext, ViewNode}, - render_phase::{BinnedRenderPhase, TrackedRenderPass}, + render_phase::{TrackedRenderPass, ViewBinnedRenderPhases}, render_resource::{CommandEncoderDescriptor, PipelineCache, RenderPassDescriptor, StoreOp}, renderer::RenderContext, view::{ViewDepthTexture, ViewTarget, ViewUniformOffset}, @@ -24,9 +24,8 @@ use super::AlphaMask3d; pub struct MainOpaquePass3dNode; impl ViewNode for MainOpaquePass3dNode { type ViewQuery = ( + Entity, &'static ExtractedCamera, - &'static BinnedRenderPhase, - &'static BinnedRenderPhase, &'static ViewTarget, &'static ViewDepthTexture, Option<&'static SkyboxPipelineId>, @@ -39,9 +38,8 @@ impl ViewNode for MainOpaquePass3dNode { graph: &mut RenderGraphContext, render_context: &mut RenderContext<'w>, ( + view, camera, - opaque_phase, - alpha_mask_phase, target, depth, skybox_pipeline, @@ -50,6 +48,19 @@ impl ViewNode for MainOpaquePass3dNode { ): QueryItem<'w, Self::ViewQuery>, world: &'w World, ) -> Result<(), NodeRunError> { + let (Some(opaque_phases), Some(alpha_mask_phases)) = ( + world.get_resource::>(), + world.get_resource::>(), + ) else { + return Ok(()); + }; + + let (Some(opaque_phase), Some(alpha_mask_phase)) = + (opaque_phases.get(&view), alpha_mask_phases.get(&view)) + else { + return Ok(()); + }; + let diagnostics = render_context.diagnostic_recorder(); let color_attachments = [Some(target.get_color_attachment())]; 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 54c6c623f1..32aef340a8 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::SortedRenderPhase, + render_phase::ViewSortedRenderPhases, render_resource::{Extent3d, RenderPassDescriptor, StoreOp}, renderer::RenderContext, view::{ViewDepthTexture, ViewTarget}, @@ -22,7 +22,6 @@ impl ViewNode for MainTransmissivePass3dNode { type ViewQuery = ( &'static ExtractedCamera, &'static Camera3d, - &'static SortedRenderPhase, &'static ViewTarget, Option<&'static ViewTransmissionTexture>, &'static ViewDepthTexture, @@ -32,13 +31,21 @@ impl ViewNode for MainTransmissivePass3dNode { &self, graph: &mut RenderGraphContext, render_context: &mut RenderContext, - (camera, camera_3d, transmissive_phase, target, transmission, depth): QueryItem< - Self::ViewQuery, - >, + (camera, camera_3d, target, transmission, depth): QueryItem, world: &World, ) -> Result<(), NodeRunError> { let view_entity = graph.view_entity(); + let Some(transmissive_phases) = + world.get_resource::>() + else { + return Ok(()); + }; + + let Some(transmissive_phase) = transmissive_phases.get(&view_entity) else { + return Ok(()); + }; + let physical_target_size = camera.physical_target_size.unwrap(); let render_pass_descriptor = RenderPassDescriptor { 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 3c42434330..a95a0519c2 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::SortedRenderPhase, + render_phase::ViewSortedRenderPhases, render_resource::{RenderPassDescriptor, StoreOp}, renderer::RenderContext, view::{ViewDepthTexture, ViewTarget}, @@ -20,7 +20,6 @@ pub struct MainTransparentPass3dNode; impl ViewNode for MainTransparentPass3dNode { type ViewQuery = ( &'static ExtractedCamera, - &'static SortedRenderPhase, &'static ViewTarget, &'static ViewDepthTexture, ); @@ -28,11 +27,21 @@ impl ViewNode for MainTransparentPass3dNode { &self, graph: &mut RenderGraphContext, render_context: &mut RenderContext, - (camera, transparent_phase, target, depth): QueryItem, + (camera, target, depth): QueryItem, world: &World, ) -> Result<(), NodeRunError> { let view_entity = graph.view_entity(); + let Some(transparent_phases) = + world.get_resource::>() + else { + return Ok(()); + }; + + let Some(transparent_phase) = transparent_phases.get(&view_entity) else { + return Ok(()); + }; + if !transparent_phase.items.is_empty() { // Run the transparent pass, sorted back-to-front // NOTE: Scoped to drop the mutable borrow of render_context diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index 4d608a6a0e..52f3b95578 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -50,7 +50,7 @@ pub use main_opaque_pass_3d_node::*; pub use main_transparent_pass_3d_node::*; use bevy_app::{App, Plugin, PostUpdate}; -use bevy_ecs::prelude::*; +use bevy_ecs::{entity::EntityHashSet, prelude::*}; use bevy_math::FloatOrd; use bevy_render::{ camera::{Camera, ExtractedCamera}, @@ -59,9 +59,9 @@ use bevy_render::{ prelude::Msaa, render_graph::{EmptyNode, RenderGraphApp, ViewNodeRunner}, render_phase::{ - sort_phase_system, BinnedPhaseItem, BinnedRenderPhase, CachedRenderPipelinePhaseItem, - DrawFunctionId, DrawFunctions, PhaseItem, PhaseItemExtraIndex, SortedPhaseItem, - SortedRenderPhase, + sort_phase_system, BinnedPhaseItem, CachedRenderPipelinePhaseItem, DrawFunctionId, + DrawFunctions, PhaseItem, PhaseItemExtraIndex, SortedPhaseItem, ViewBinnedRenderPhases, + ViewSortedRenderPhases, }, render_resource::{ BindGroupId, CachedRenderPipelineId, Extent3d, FilterMode, Sampler, SamplerDescriptor, @@ -473,23 +473,43 @@ impl CachedRenderPipelinePhaseItem for Transparent3d { pub fn extract_core_3d_camera_phases( mut commands: Commands, + mut opaque_3d_phases: ResMut>, + mut alpha_mask_3d_phases: ResMut>, + mut transmissive_3d_phases: ResMut>, + mut transparent_3d_phases: ResMut>, cameras_3d: Extract>>, + mut live_entities: Local, ) { + live_entities.clear(); + for (entity, camera) in &cameras_3d { - if camera.is_active { - commands.get_or_spawn(entity).insert(( - BinnedRenderPhase::::default(), - BinnedRenderPhase::::default(), - SortedRenderPhase::::default(), - SortedRenderPhase::::default(), - )); + if !camera.is_active { + continue; } + + commands.get_or_spawn(entity); + + opaque_3d_phases.insert_or_clear(entity); + alpha_mask_3d_phases.insert_or_clear(entity); + transmissive_3d_phases.insert_or_clear(entity); + transparent_3d_phases.insert_or_clear(entity); + + live_entities.insert(entity); } + + opaque_3d_phases.retain(|entity, _| live_entities.contains(entity)); + alpha_mask_3d_phases.retain(|entity, _| live_entities.contains(entity)); + transmissive_3d_phases.retain(|entity, _| live_entities.contains(entity)); + transparent_3d_phases.retain(|entity, _| live_entities.contains(entity)); } // Extract the render phases for the prepass pub fn extract_camera_prepass_phase( mut commands: Commands, + mut opaque_3d_prepass_phases: ResMut>, + mut alpha_mask_3d_prepass_phases: ResMut>, + mut opaque_3d_deferred_phases: ResMut>, + mut alpha_mask_3d_deferred_phases: ResMut>, cameras_3d: Extract< Query< ( @@ -503,60 +523,79 @@ pub fn extract_camera_prepass_phase( With, >, >, + mut live_entities: Local, ) { + live_entities.clear(); + for (entity, camera, depth_prepass, normal_prepass, motion_vector_prepass, deferred_prepass) in cameras_3d.iter() { - if camera.is_active { - let mut entity = commands.get_or_spawn(entity); + if !camera.is_active { + continue; + } - if depth_prepass || normal_prepass || motion_vector_prepass { - entity.insert(( - BinnedRenderPhase::::default(), - BinnedRenderPhase::::default(), - )); - } + 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); + } else { + opaque_3d_prepass_phases.remove(&entity); + alpha_mask_3d_prepass_phases.remove(&entity); + } - if deferred_prepass { - entity.insert(( - BinnedRenderPhase::::default(), - BinnedRenderPhase::::default(), - )); - } + if deferred_prepass { + opaque_3d_deferred_phases.insert_or_clear(entity); + alpha_mask_3d_deferred_phases.insert_or_clear(entity); + } else { + opaque_3d_deferred_phases.remove(&entity); + alpha_mask_3d_deferred_phases.remove(&entity); + } - if depth_prepass { - entity.insert(DepthPrepass); - } - if normal_prepass { - entity.insert(NormalPrepass); - } - if motion_vector_prepass { - entity.insert(MotionVectorPrepass); - } - if deferred_prepass { - entity.insert(DeferredPrepass); - } + live_entities.insert(entity); + + let mut entity = commands.get_or_spawn(entity); + + if depth_prepass { + entity.insert(DepthPrepass); + } + if normal_prepass { + entity.insert(NormalPrepass); + } + if motion_vector_prepass { + entity.insert(MotionVectorPrepass); + } + if deferred_prepass { + entity.insert(DeferredPrepass); } } + + opaque_3d_prepass_phases.retain(|entity, _| live_entities.contains(entity)); + alpha_mask_3d_prepass_phases.retain(|entity, _| live_entities.contains(entity)); + opaque_3d_deferred_phases.retain(|entity, _| live_entities.contains(entity)); + alpha_mask_3d_deferred_phases.retain(|entity, _| live_entities.contains(entity)); } +#[allow(clippy::too_many_arguments)] pub fn prepare_core_3d_depth_textures( mut commands: Commands, mut texture_cache: ResMut, msaa: Res, render_device: Res, - views_3d: Query< - (Entity, &ExtractedCamera, Option<&DepthPrepass>, &Camera3d), - ( - With>, - With>, - With>, - With>, - ), - >, + opaque_3d_phases: Res>, + alpha_mask_3d_phases: Res>, + transmissive_3d_phases: Res>, + transparent_3d_phases: Res>, + views_3d: Query<(Entity, &ExtractedCamera, Option<&DepthPrepass>, &Camera3d)>, ) { let mut render_target_usage = HashMap::default(); - for (_, camera, depth_prepass, camera_3d) in &views_3d { + for (view, camera, depth_prepass, camera_3d) in &views_3d { + if !opaque_3d_phases.contains_key(&view) + || !alpha_mask_3d_phases.contains_key(&view) + || !transmissive_3d_phases.contains_key(&view) + || !transparent_3d_phases.contains_key(&view) + { + continue; + }; + // Default usage required to write to the depth texture let mut usage: TextureUsages = camera_3d.depth_texture_usages.into(); if depth_prepass.is_some() { @@ -621,27 +660,30 @@ pub struct ViewTransmissionTexture { pub sampler: Sampler, } +#[allow(clippy::too_many_arguments)] pub fn prepare_core_3d_transmission_textures( mut commands: Commands, mut texture_cache: ResMut, render_device: Res, - views_3d: Query< - ( - Entity, - &ExtractedCamera, - &Camera3d, - &ExtractedView, - &SortedRenderPhase, - ), - ( - With>, - With>, - With>, - ), - >, + opaque_3d_phases: Res>, + alpha_mask_3d_phases: Res>, + transmissive_3d_phases: Res>, + transparent_3d_phases: Res>, + views_3d: Query<(Entity, &ExtractedCamera, &Camera3d, &ExtractedView)>, ) { let mut textures = HashMap::default(); - for (entity, camera, camera_3d, view, transmissive_3d_phase) in &views_3d { + for (entity, camera, camera_3d, view) in &views_3d { + if !opaque_3d_phases.contains_key(&entity) + || !alpha_mask_3d_phases.contains_key(&entity) + || !transparent_3d_phases.contains_key(&entity) + { + continue; + }; + + let Some(transmissive_3d_phase) = transmissive_3d_phases.get(&entity) else { + continue; + }; + let Some(physical_target_size) = camera.physical_target_size else { continue; }; @@ -721,27 +763,24 @@ pub fn check_msaa( } // Prepares the textures used by the prepass +#[allow(clippy::too_many_arguments)] pub fn prepare_prepass_textures( mut commands: Commands, mut texture_cache: ResMut, msaa: Res, render_device: Res, - views_3d: Query< - ( - Entity, - &ExtractedCamera, - Has, - Has, - Has, - Has, - ), - Or<( - With>, - With>, - With>, - With>, - )>, - >, + opaque_3d_prepass_phases: Res>, + alpha_mask_3d_prepass_phases: Res>, + opaque_3d_deferred_phases: Res>, + alpha_mask_3d_deferred_phases: Res>, + views_3d: Query<( + Entity, + &ExtractedCamera, + Has, + Has, + Has, + Has, + )>, ) { let mut depth_textures = HashMap::default(); let mut normal_textures = HashMap::default(); @@ -751,6 +790,14 @@ pub fn prepare_prepass_textures( for (entity, camera, depth_prepass, normal_prepass, motion_vector_prepass, deferred_prepass) in &views_3d { + if !opaque_3d_prepass_phases.contains_key(&entity) + && !alpha_mask_3d_prepass_phases.contains_key(&entity) + && !opaque_3d_deferred_phases.contains_key(&entity) + && !alpha_mask_3d_deferred_phases.contains_key(&entity) + { + continue; + }; + let Some(physical_target_size) = camera.physical_target_size else { continue; }; diff --git a/crates/bevy_core_pipeline/src/deferred/node.rs b/crates/bevy_core_pipeline/src/deferred/node.rs index d599cb7c8b..b4c9bb2857 100644 --- a/crates/bevy_core_pipeline/src/deferred/node.rs +++ b/crates/bevy_core_pipeline/src/deferred/node.rs @@ -2,7 +2,7 @@ use bevy_ecs::prelude::*; use bevy_ecs::query::QueryItem; use bevy_render::render_graph::ViewNode; -use bevy_render::render_phase::{BinnedRenderPhase, TrackedRenderPass}; +use bevy_render::render_phase::{TrackedRenderPass, ViewBinnedRenderPhases}; use bevy_render::render_resource::{CommandEncoderDescriptor, StoreOp}; use bevy_render::{ camera::ExtractedCamera, @@ -26,9 +26,8 @@ pub struct DeferredGBufferPrepassNode; impl ViewNode for DeferredGBufferPrepassNode { type ViewQuery = ( + Entity, &'static ExtractedCamera, - &'static BinnedRenderPhase, - &'static BinnedRenderPhase, &'static ViewDepthTexture, &'static ViewPrepassTextures, ); @@ -37,15 +36,23 @@ impl ViewNode for DeferredGBufferPrepassNode { &self, graph: &mut RenderGraphContext, render_context: &mut RenderContext<'w>, - ( - camera, - opaque_deferred_phase, - alpha_mask_deferred_phase, - view_depth_texture, - view_prepass_textures, - ): QueryItem<'w, Self::ViewQuery>, + (view, camera, view_depth_texture, view_prepass_textures): QueryItem<'w, Self::ViewQuery>, world: &'w World, ) -> Result<(), NodeRunError> { + let (Some(opaque_deferred_phases), Some(alpha_mask_deferred_phases)) = ( + world.get_resource::>(), + world.get_resource::>(), + ) else { + return Ok(()); + }; + + let (Some(opaque_deferred_phase), Some(alpha_mask_deferred_phase)) = ( + opaque_deferred_phases.get(&view), + alpha_mask_deferred_phases.get(&view), + ) else { + return Ok(()); + }; + let mut color_attachments = vec![]; color_attachments.push( view_prepass_textures diff --git a/crates/bevy_core_pipeline/src/prepass/node.rs b/crates/bevy_core_pipeline/src/prepass/node.rs index 74de568e2b..7e22665fa3 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::{BinnedRenderPhase, TrackedRenderPass}, + render_phase::{TrackedRenderPass, ViewBinnedRenderPhases}, render_resource::{CommandEncoderDescriptor, RenderPassDescriptor, StoreOp}, renderer::RenderContext, view::ViewDepthTexture, @@ -22,9 +22,8 @@ pub struct PrepassNode; impl ViewNode for PrepassNode { type ViewQuery = ( + Entity, &'static ExtractedCamera, - &'static BinnedRenderPhase, - &'static BinnedRenderPhase, &'static ViewDepthTexture, &'static ViewPrepassTextures, Option<&'static DeferredPrepass>, @@ -34,16 +33,26 @@ impl ViewNode for PrepassNode { &self, graph: &mut RenderGraphContext, render_context: &mut RenderContext<'w>, - ( - camera, - opaque_prepass_phase, - alpha_mask_prepass_phase, - view_depth_texture, - view_prepass_textures, - deferred_prepass, - ): QueryItem<'w, Self::ViewQuery>, + (view, camera, view_depth_texture, view_prepass_textures, deferred_prepass): QueryItem< + 'w, + Self::ViewQuery, + >, world: &'w World, ) -> Result<(), NodeRunError> { + let (Some(opaque_prepass_phases), Some(alpha_mask_prepass_phases)) = ( + world.get_resource::>(), + world.get_resource::>(), + ) else { + return Ok(()); + }; + + let (Some(opaque_prepass_phase), Some(alpha_mask_prepass_phase)) = ( + opaque_prepass_phases.get(&view), + alpha_mask_prepass_phases.get(&view), + ) else { + return Ok(()); + }; + let diagnostics = render_context.diagnostic_recorder(); let mut color_attachments = vec![ diff --git a/crates/bevy_gizmos/src/pipeline_2d.rs b/crates/bevy_gizmos/src/pipeline_2d.rs index 1a86976ff0..df84a48256 100644 --- a/crates/bevy_gizmos/src/pipeline_2d.rs +++ b/crates/bevy_gizmos/src/pipeline_2d.rs @@ -19,7 +19,8 @@ use bevy_math::FloatOrd; use bevy_render::{ render_asset::{prepare_assets, RenderAssets}, render_phase::{ - AddRenderCommand, DrawFunctions, PhaseItemExtraIndex, SetItemPipeline, SortedRenderPhase, + AddRenderCommand, DrawFunctions, PhaseItemExtraIndex, SetItemPipeline, + ViewSortedRenderPhases, }, render_resource::*, texture::BevyDefault, @@ -257,15 +258,16 @@ fn queue_line_gizmos_2d( msaa: Res, line_gizmos: Query<(Entity, &Handle, &GizmoMeshConfig)>, line_gizmo_assets: Res>, - mut views: Query<( - &ExtractedView, - &mut SortedRenderPhase, - Option<&RenderLayers>, - )>, + mut transparent_render_phases: ResMut>, + mut views: Query<(Entity, &ExtractedView, Option<&RenderLayers>)>, ) { let draw_function = draw_functions.read().get_id::().unwrap(); - for (view, mut transparent_phase, render_layers) in &mut views { + for (view_entity, view, render_layers) in &mut views { + let Some(transparent_phase) = transparent_render_phases.get_mut(&view_entity) else { + continue; + }; + let mesh_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples()) | Mesh2dPipelineKey::from_hdr(view.hdr); @@ -310,18 +312,19 @@ fn queue_line_joint_gizmos_2d( msaa: Res, line_gizmos: Query<(Entity, &Handle, &GizmoMeshConfig)>, line_gizmo_assets: Res>, - mut views: Query<( - &ExtractedView, - &mut SortedRenderPhase, - Option<&RenderLayers>, - )>, + mut transparent_render_phases: ResMut>, + mut views: Query<(Entity, &ExtractedView, Option<&RenderLayers>)>, ) { let draw_function = draw_functions .read() .get_id::() .unwrap(); - for (view, mut transparent_phase, render_layers) in &mut views { + for (view_entity, view, render_layers) in &mut views { + let Some(transparent_phase) = transparent_render_phases.get_mut(&view_entity) else { + continue; + }; + let mesh_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples()) | Mesh2dPipelineKey::from_hdr(view.hdr); diff --git a/crates/bevy_gizmos/src/pipeline_3d.rs b/crates/bevy_gizmos/src/pipeline_3d.rs index bdcd75764b..761278ca3e 100644 --- a/crates/bevy_gizmos/src/pipeline_3d.rs +++ b/crates/bevy_gizmos/src/pipeline_3d.rs @@ -23,7 +23,8 @@ use bevy_pbr::{MeshPipeline, MeshPipelineKey, SetMeshViewBindGroup}; use bevy_render::{ render_asset::{prepare_assets, RenderAssets}, render_phase::{ - AddRenderCommand, DrawFunctions, PhaseItemExtraIndex, SetItemPipeline, SortedRenderPhase, + AddRenderCommand, DrawFunctions, PhaseItemExtraIndex, SetItemPipeline, + ViewSortedRenderPhases, }, render_resource::*, texture::BevyDefault, @@ -282,9 +283,10 @@ fn queue_line_gizmos_3d( msaa: Res, line_gizmos: Query<(Entity, &Handle, &GizmoMeshConfig)>, line_gizmo_assets: Res>, + mut transparent_render_phases: ResMut>, mut views: Query<( + Entity, &ExtractedView, - &mut SortedRenderPhase, Option<&RenderLayers>, ( Has, @@ -297,12 +299,16 @@ fn queue_line_gizmos_3d( let draw_function = draw_functions.read().get_id::().unwrap(); for ( + view_entity, view, - mut transparent_phase, render_layers, (normal_prepass, depth_prepass, motion_vector_prepass, deferred_prepass), ) in &mut views { + let Some(transparent_phase) = transparent_render_phases.get_mut(&view_entity) else { + continue; + }; + let render_layers = render_layers.unwrap_or_default(); let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples()) @@ -365,9 +371,10 @@ fn queue_line_joint_gizmos_3d( msaa: Res, line_gizmos: Query<(Entity, &Handle, &GizmoMeshConfig)>, line_gizmo_assets: Res>, + mut transparent_render_phases: ResMut>, mut views: Query<( + Entity, &ExtractedView, - &mut SortedRenderPhase, Option<&RenderLayers>, ( Has, @@ -383,12 +390,16 @@ fn queue_line_joint_gizmos_3d( .unwrap(); for ( + view_entity, view, - mut transparent_phase, render_layers, (normal_prepass, depth_prepass, motion_vector_prepass, deferred_prepass), ) in &mut views { + let Some(transparent_phase) = transparent_render_phases.get_mut(&view_entity) else { + continue; + }; + let render_layers = render_layers.unwrap_or_default(); let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples()) diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index ede7b1672a..adf08f3068 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -516,10 +516,17 @@ pub const fn screen_space_specular_transmission_pipeline_key( /// them to [`BinnedRenderPhase`]s or [`SortedRenderPhase`]s as appropriate. #[allow(clippy::too_many_arguments)] pub fn queue_material_meshes( - opaque_draw_functions: Res>, - alpha_mask_draw_functions: Res>, - transmissive_draw_functions: Res>, - transparent_draw_functions: Res>, + ( + opaque_draw_functions, + alpha_mask_draw_functions, + transmissive_draw_functions, + transparent_draw_functions, + ): ( + Res>, + Res>, + Res>, + Res>, + ), material_pipeline: Res>, mut pipelines: ResMut>>, pipeline_cache: Res, @@ -530,7 +537,12 @@ pub fn queue_material_meshes( render_material_instances: Res>, render_lightmaps: Res, render_visibility_ranges: Res, + mut opaque_render_phases: ResMut>, + mut alpha_mask_render_phases: ResMut>, + mut transmissive_render_phases: ResMut>, + mut transparent_render_phases: ResMut>, mut views: Query<( + Entity, &ExtractedView, &VisibleEntities, Option<&Tonemapping>, @@ -546,10 +558,6 @@ pub fn queue_material_meshes( Option<&Camera3d>, Has, Option<&Projection>, - &mut BinnedRenderPhase, - &mut BinnedRenderPhase, - &mut SortedRenderPhase, - &mut SortedRenderPhase, ( Has>, Has>, @@ -559,6 +567,7 @@ pub fn queue_material_meshes( M::Data: PartialEq + Eq + Hash + Clone, { for ( + view_entity, view, visible_entities, tonemapping, @@ -569,13 +578,24 @@ pub fn queue_material_meshes( camera_3d, temporal_jitter, projection, - mut opaque_phase, - mut alpha_mask_phase, - mut transmissive_phase, - mut transparent_phase, (has_environment_maps, has_irradiance_volumes), ) in &mut views { + let ( + Some(opaque_phase), + Some(alpha_mask_phase), + Some(transmissive_phase), + Some(transparent_phase), + ) = ( + opaque_render_phases.get_mut(&view_entity), + alpha_mask_render_phases.get_mut(&view_entity), + transmissive_render_phases.get_mut(&view_entity), + transparent_render_phases.get_mut(&view_entity), + ) + else { + continue; + }; + let draw_opaque_pbr = opaque_draw_functions.read().id::>(); let draw_alpha_mask_pbr = alpha_mask_draw_functions.read().id::>(); let draw_transmissive_pbr = transmissive_draw_functions.read().id::>(); diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 7a1b831f8e..1a9695cc6a 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -700,10 +700,17 @@ pub fn prepare_prepass_view_bind_group( #[allow(clippy::too_many_arguments)] pub fn queue_prepass_material_meshes( - opaque_draw_functions: Res>, - alpha_mask_draw_functions: Res>, - opaque_deferred_draw_functions: Res>, - alpha_mask_deferred_draw_functions: Res>, + ( + opaque_draw_functions, + alpha_mask_draw_functions, + opaque_deferred_draw_functions, + alpha_mask_deferred_draw_functions, + ): ( + Res>, + Res>, + Res>, + Res>, + ), prepass_pipeline: Res>, mut pipelines: ResMut>>, pipeline_cache: Res, @@ -713,25 +720,20 @@ pub fn queue_prepass_material_meshes( render_materials: Res>>, render_material_instances: Res>, render_lightmaps: Res, + mut opaque_prepass_render_phases: ResMut>, + mut alpha_mask_prepass_render_phases: ResMut>, + mut opaque_deferred_render_phases: ResMut>, + mut alpha_mask_deferred_render_phases: ResMut>, mut views: Query< ( - &ExtractedView, + Entity, &VisibleEntities, - 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, >, ) where M::Data: PartialEq + Eq + Hash + Clone, @@ -753,18 +755,35 @@ pub fn queue_prepass_material_meshes( .get_id::>() .unwrap(); for ( - _view, + view, visible_entities, - mut opaque_phase, - mut alpha_mask_phase, - mut opaque_deferred_phase, - mut alpha_mask_deferred_phase, depth_prepass, normal_prepass, motion_vector_prepass, deferred_prepass, ) in &mut views { + let ( + mut opaque_phase, + mut alpha_mask_phase, + mut opaque_deferred_phase, + mut alpha_mask_deferred_phase, + ) = ( + opaque_prepass_render_phases.get_mut(&view), + alpha_mask_prepass_render_phases.get_mut(&view), + opaque_deferred_render_phases.get_mut(&view), + alpha_mask_deferred_render_phases.get_mut(&view), + ); + + // Skip if there's no place to put the mesh. + if opaque_phase.is_none() + && alpha_mask_phase.is_none() + && opaque_deferred_phase.is_none() + && alpha_mask_deferred_phase.is_none() + { + continue; + } + let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples()); if depth_prepass.is_some() { view_key |= MeshPipelineKey::DEPTH_PREPASS; diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 1e9709ab00..b425a251aa 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -1,5 +1,6 @@ use bevy_asset::AssetId; -use bevy_core_pipeline::core_3d::{Transparent3d, CORE_3D_DEPTH_FORMAT}; +use bevy_core_pipeline::core_3d::CORE_3D_DEPTH_FORMAT; +use bevy_ecs::entity::EntityHashSet; use bevy_ecs::prelude::*; use bevy_ecs::{entity::EntityHashMap, system::lifetimeless::Read}; use bevy_math::{Mat4, UVec3, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles}; @@ -688,18 +689,16 @@ pub fn prepare_lights( render_queue: Res, mut global_light_meta: ResMut, mut light_meta: ResMut, - views: Query< - ( - Entity, - &ExtractedView, - &ExtractedClusterConfig, - Option<&RenderLayers>, - ), - With>, - >, + views: Query<( + Entity, + &ExtractedView, + &ExtractedClusterConfig, + Option<&RenderLayers>, + )>, ambient_light: Res, point_light_shadow_map: Res, directional_light_shadow_map: Res, + mut shadow_render_phases: ResMut>, mut max_directional_lights_warning_emitted: Local, mut max_cascades_per_light_warning_emitted: Local, point_lights: Query<( @@ -708,6 +707,7 @@ pub fn prepare_lights( AnyOf<(&CubemapFrusta, &Frustum)>, )>, directional_lights: Query<(Entity, &ExtractedDirectionalLight)>, + mut live_shadow_mapping_lights: Local, ) { let views_iter = views.iter(); let views_count = views_iter.len(); @@ -958,6 +958,8 @@ pub fn prepare_lights( .gpu_point_lights .write_buffer(&render_device, &render_queue); + live_shadow_mapping_lights.clear(); + // set up light data for each view for (entity, extracted_view, clusters, maybe_layers) in &views { let point_light_depth_texture = texture_cache.get( @@ -1088,7 +1090,6 @@ pub fn prepare_lights( color_grading: Default::default(), }, *frustum, - BinnedRenderPhase::::default(), LightEntity::Point { light_entity, face_index, @@ -1096,6 +1097,9 @@ pub fn prepare_lights( )) .id(); view_lights.push(view_light_entity); + + shadow_render_phases.insert_or_clear(view_light_entity); + live_shadow_mapping_lights.insert(view_light_entity); } } @@ -1147,12 +1151,14 @@ pub fn prepare_lights( color_grading: Default::default(), }, *spot_light_frustum.unwrap(), - BinnedRenderPhase::::default(), LightEntity::Spot { light_entity }, )) .id(); view_lights.push(view_light_entity); + + shadow_render_phases.insert_or_clear(view_light_entity); + live_shadow_mapping_lights.insert(view_light_entity); } // directional lights @@ -1241,7 +1247,6 @@ pub fn prepare_lights( color_grading: Default::default(), }, frustum, - BinnedRenderPhase::::default(), LightEntity::Directional { light_entity, cascade_index, @@ -1249,6 +1254,9 @@ pub fn prepare_lights( )) .id(); view_lights.push(view_light_entity); + + shadow_render_phases.insert_or_clear(view_light_entity); + live_shadow_mapping_lights.insert(view_light_entity); } } @@ -1315,6 +1323,8 @@ pub fn prepare_lights( }, )); } + + shadow_render_phases.retain(|entity, _| live_shadow_mapping_lights.contains(entity)); } // this must match CLUSTER_COUNT_SIZE in pbr.wgsl @@ -1593,7 +1603,7 @@ pub fn prepare_clusters( render_queue: Res, mesh_pipeline: Res, global_light_meta: Res, - views: Query<(Entity, &ExtractedClustersPointLights), With>>, + views: Query<(Entity, &ExtractedClustersPointLights)>, ) { let render_device = render_device.into_inner(); let supports_storage_buffers = matches!( @@ -1649,11 +1659,12 @@ pub fn queue_shadows( render_mesh_instances: Res, render_materials: Res>>, render_material_instances: Res>, + mut shadow_render_phases: ResMut>, mut pipelines: ResMut>>, pipeline_cache: Res, render_lightmaps: Res, view_lights: Query<(Entity, &ViewLightEntities)>, - mut view_light_shadow_phases: Query<(&LightEntity, &mut BinnedRenderPhase)>, + mut view_light_entities: Query<&LightEntity>, point_light_entities: Query<&CubemapVisibleEntities, With>, directional_light_entities: Query<&CascadesVisibleEntities, With>, spot_light_entities: Query<&VisibleEntities, With>, @@ -1663,8 +1674,13 @@ pub fn queue_shadows( for (entity, view_lights) in &view_lights { let draw_shadow_mesh = shadow_draw_functions.read().id::>(); for view_light_entity in view_lights.lights.iter().copied() { - let (light_entity, mut shadow_phase) = - view_light_shadow_phases.get_mut(view_light_entity).unwrap(); + let Ok(light_entity) = view_light_entities.get_mut(view_light_entity) else { + continue; + }; + let Some(shadow_phase) = shadow_render_phases.get_mut(&view_light_entity) else { + continue; + }; + let is_directional_light = matches!(light_entity, LightEntity::Directional { .. }); let visible_entities = match light_entity { LightEntity::Directional { @@ -1851,7 +1867,7 @@ impl CachedRenderPipelinePhaseItem for Shadow { pub struct ShadowPassNode { main_view_query: QueryState>, - view_light_query: QueryState<(Read, Read>)>, + view_light_query: QueryState>, } impl ShadowPassNode { @@ -1876,12 +1892,23 @@ impl Node for ShadowPassNode { world: &'w World, ) -> Result<(), NodeRunError> { let diagnostics = render_context.diagnostic_recorder(); - let time_span = diagnostics.time_span(render_context.command_encoder(), "shadows"); let view_entity = graph.view_entity(); + + let Some(shadow_render_phases) = world.get_resource::>() + else { + return Ok(()); + }; + + let time_span = diagnostics.time_span(render_context.command_encoder(), "shadows"); + if let Ok(view_lights) = self.main_view_query.get_manual(world, view_entity) { for view_light_entity in view_lights.lights.iter().copied() { - let (view_light, shadow_phase) = self + let Some(shadow_phase) = shadow_render_phases.get(&view_light_entity) else { + continue; + }; + + let view_light = self .view_light_query .get_manual(world, view_light_entity) .unwrap(); diff --git a/crates/bevy_render/src/batching/gpu_preprocessing.rs b/crates/bevy_render/src/batching/gpu_preprocessing.rs index 2430d6a2ab..515f025b9f 100644 --- a/crates/bevy_render/src/batching/gpu_preprocessing.rs +++ b/crates/bevy_render/src/batching/gpu_preprocessing.rs @@ -18,8 +18,9 @@ use wgpu::{BindingResource, BufferUsages, DownlevelFlags, Features}; use crate::{ render_phase::{ - BinnedPhaseItem, BinnedRenderPhase, BinnedRenderPhaseBatch, CachedRenderPipelinePhaseItem, + BinnedPhaseItem, BinnedRenderPhaseBatch, CachedRenderPipelinePhaseItem, PhaseItemExtraIndex, SortedPhaseItem, SortedRenderPhase, UnbatchableBinnedEntityIndices, + ViewBinnedRenderPhases, ViewSortedRenderPhases, }, render_resource::{BufferVec, GpuArrayBufferable, RawBufferVec, UninitBufferVec}, renderer::{RenderAdapter, RenderDevice, RenderQueue}, @@ -372,7 +373,8 @@ pub fn delete_old_work_item_buffers( pub fn batch_and_prepare_sorted_render_phase( gpu_array_buffer: ResMut>, mut indirect_parameters_buffer: ResMut, - mut views: Query<(Entity, &mut SortedRenderPhase, Has)>, + mut sorted_render_phases: ResMut>, + mut views: Query<(Entity, Has)>, system_param_item: StaticSystemParam, ) where I: CachedRenderPipelinePhaseItem + SortedPhaseItem, @@ -385,7 +387,11 @@ pub fn batch_and_prepare_sorted_render_phase( .. } = gpu_array_buffer.into_inner(); - for (view, mut phase, gpu_culling) in &mut views { + for (view, gpu_culling) in &mut views { + let Some(phase) = sorted_render_phases.get_mut(&view) else { + continue; + }; + // Create the work item buffer if necessary. let work_item_buffer = work_item_buffers @@ -433,7 +439,7 @@ pub fn batch_and_prepare_sorted_render_phase( if !can_batch { // Break a batch if we need to. if let Some(batch) = batch.take() { - batch.flush(output_index, &mut phase); + batch.flush(output_index, phase); } // Start a new batch. @@ -471,7 +477,7 @@ pub fn batch_and_prepare_sorted_render_phase( // Flush the final batch if necessary. if let Some(batch) = batch.take() { - batch.flush(data_buffer.len() as u32, &mut phase); + batch.flush(data_buffer.len() as u32, phase); } } } @@ -480,7 +486,8 @@ pub fn batch_and_prepare_sorted_render_phase( pub fn batch_and_prepare_binned_render_phase( gpu_array_buffer: ResMut>, mut indirect_parameters_buffer: ResMut, - mut views: Query<(Entity, &mut BinnedRenderPhase, Has)>, + mut binned_render_phases: ResMut>, + mut views: Query<(Entity, Has)>, param: StaticSystemParam, ) where BPI: BinnedPhaseItem, @@ -494,8 +501,10 @@ pub fn batch_and_prepare_binned_render_phase( .. } = gpu_array_buffer.into_inner(); - for (view, mut phase, gpu_culling) in &mut views { - let phase = &mut *phase; // Borrow checker. + for (view, gpu_culling) in &mut views { + let Some(phase) = binned_render_phases.get_mut(&view) else { + continue; + }; // Create the work item buffer if necessary; otherwise, just mark it as // used this frame. diff --git a/crates/bevy_render/src/batching/mod.rs b/crates/bevy_render/src/batching/mod.rs index 0e1a6ada7c..b7bd3e892c 100644 --- a/crates/bevy_render/src/batching/mod.rs +++ b/crates/bevy_render/src/batching/mod.rs @@ -1,15 +1,15 @@ use bevy_ecs::{ component::Component, entity::Entity, - system::{Query, SystemParam, SystemParamItem}, + system::{ResMut, SystemParam, SystemParamItem}, }; use bytemuck::Pod; use nonmax::NonMaxU32; use crate::{ render_phase::{ - BinnedPhaseItem, BinnedRenderPhase, CachedRenderPipelinePhaseItem, DrawFunctionId, - SortedPhaseItem, SortedRenderPhase, + BinnedPhaseItem, CachedRenderPipelinePhaseItem, DrawFunctionId, SortedPhaseItem, + SortedRenderPhase, ViewBinnedRenderPhases, }, render_resource::{CachedRenderPipelineId, GpuArrayBufferable}, }; @@ -151,11 +151,11 @@ pub trait GetFullBatchData: GetBatchData { } /// Sorts a render phase that uses bins. -pub fn sort_binned_render_phase(mut views: Query<&mut BinnedRenderPhase>) +pub fn sort_binned_render_phase(mut phases: ResMut>) where BPI: BinnedPhaseItem, { - for mut phase in &mut views { + for phase in phases.values_mut() { phase.batchable_keys.sort_unstable(); phase.unbatchable_keys.sort_unstable(); } diff --git a/crates/bevy_render/src/batching/no_gpu_preprocessing.rs b/crates/bevy_render/src/batching/no_gpu_preprocessing.rs index 15dfa7842a..98df8098ca 100644 --- a/crates/bevy_render/src/batching/no_gpu_preprocessing.rs +++ b/crates/bevy_render/src/batching/no_gpu_preprocessing.rs @@ -1,14 +1,14 @@ //! Batching functionality when GPU preprocessing isn't in use. use bevy_derive::{Deref, DerefMut}; -use bevy_ecs::system::{Query, Res, ResMut, Resource, StaticSystemParam}; +use bevy_ecs::system::{Res, ResMut, Resource, StaticSystemParam}; use smallvec::{smallvec, SmallVec}; use wgpu::BindingResource; use crate::{ render_phase::{ - BinnedPhaseItem, BinnedRenderPhase, BinnedRenderPhaseBatch, CachedRenderPipelinePhaseItem, - PhaseItemExtraIndex, SortedPhaseItem, SortedRenderPhase, + BinnedPhaseItem, BinnedRenderPhaseBatch, CachedRenderPipelinePhaseItem, + PhaseItemExtraIndex, SortedPhaseItem, ViewBinnedRenderPhases, ViewSortedRenderPhases, }, render_resource::{GpuArrayBuffer, GpuArrayBufferable}, renderer::{RenderDevice, RenderQueue}, @@ -61,7 +61,7 @@ pub fn clear_batched_cpu_instance_buffers( /// and trying to combine the draws into a batch. pub fn batch_and_prepare_sorted_render_phase( batched_instance_buffer: ResMut>, - mut views: Query<&mut SortedRenderPhase>, + mut phases: ResMut>, param: StaticSystemParam, ) where I: CachedRenderPipelinePhaseItem + SortedPhaseItem, @@ -72,8 +72,8 @@ pub fn batch_and_prepare_sorted_render_phase( // We only process CPU-built batch data in this function. let batched_instance_buffer = batched_instance_buffer.into_inner(); - for mut phase in &mut views { - super::batch_and_prepare_sorted_render_phase::(&mut phase, |item| { + for phase in phases.values_mut() { + super::batch_and_prepare_sorted_render_phase::(phase, |item| { let (buffer_data, compare_data) = GBD::get_batch_data(&system_param_item, item.entity())?; let buffer_index = batched_instance_buffer.push(buffer_data); @@ -92,7 +92,7 @@ pub fn batch_and_prepare_sorted_render_phase( /// building isn't in use. pub fn batch_and_prepare_binned_render_phase( gpu_array_buffer: ResMut>, - mut views: Query<&mut BinnedRenderPhase>, + mut phases: ResMut>, param: StaticSystemParam, ) where BPI: BinnedPhaseItem, @@ -101,9 +101,7 @@ pub fn batch_and_prepare_binned_render_phase( 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. - + for phase in phases.values_mut() { // Prepare batchables. for key in &phase.batchable_keys { diff --git a/crates/bevy_render/src/render_phase/mod.rs b/crates/bevy_render/src/render_phase/mod.rs index a1125fa938..36ec549e0e 100644 --- a/crates/bevy_render/src/render_phase/mod.rs +++ b/crates/bevy_render/src/render_phase/mod.rs @@ -29,6 +29,7 @@ mod draw_state; mod rangefinder; use bevy_app::{App, Plugin}; +use bevy_derive::{Deref, DerefMut}; use bevy_utils::{default, hashbrown::hash_map::Entry, HashMap}; pub use draw::*; pub use draw_state::*; @@ -47,6 +48,7 @@ use crate::{ Render, RenderApp, RenderSet, }; use bevy_ecs::{ + entity::EntityHashMap, prelude::*, system::{lifetimeless::SRes, SystemParamItem}, }; @@ -60,6 +62,16 @@ use std::{ slice::SliceIndex, }; +/// Stores the rendering instructions for a single phase that uses bins in all +/// views. +/// +/// They're cleared out every frame, but storing them in a resource like this +/// allows us to reuse allocations. +#[derive(Resource, Deref, DerefMut)] +pub struct ViewBinnedRenderPhases(pub EntityHashMap>) +where + BPI: BinnedPhaseItem; + /// A collection of all rendering instructions, that will be executed by the GPU, for a /// single render phase for a single view. /// @@ -74,7 +86,6 @@ use std::{ /// 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 BinnedRenderPhase where BPI: BinnedPhaseItem, @@ -200,6 +211,29 @@ where } } +impl Default for ViewBinnedRenderPhases +where + BPI: BinnedPhaseItem, +{ + fn default() -> Self { + Self(default()) + } +} + +impl ViewBinnedRenderPhases +where + BPI: BinnedPhaseItem, +{ + pub fn insert_or_clear(&mut self, entity: Entity) { + match self.entry(entity) { + Entry::Occupied(mut entry) => entry.get_mut().clear(), + Entry::Vacant(entry) => { + entry.insert(default()); + } + } + } +} + impl BinnedRenderPhase where BPI: BinnedPhaseItem, @@ -315,6 +349,14 @@ where pub fn is_empty(&self) -> bool { self.batchable_keys.is_empty() && self.unbatchable_keys.is_empty() } + + pub fn clear(&mut self) { + self.batchable_keys.clear(); + self.batchable_values.clear(); + self.unbatchable_keys.clear(); + self.unbatchable_values.clear(); + self.batch_sets.clear(); + } } impl Default for BinnedRenderPhase @@ -398,22 +440,58 @@ where return; }; - render_app.add_systems( - Render, - ( - batching::sort_binned_render_phase::.in_set(RenderSet::PhaseSort), + render_app + .init_resource::>() + .add_systems( + Render, ( - no_gpu_preprocessing::batch_and_prepare_binned_render_phase:: - .run_if(resource_exists::>), - gpu_preprocessing::batch_and_prepare_binned_render_phase::.run_if( - resource_exists::< - BatchedInstanceBuffers, - >, - ), - ) - .in_set(RenderSet::PrepareResources), - ), - ); + batching::sort_binned_render_phase::.in_set(RenderSet::PhaseSort), + ( + no_gpu_preprocessing::batch_and_prepare_binned_render_phase:: + .run_if(resource_exists::>), + gpu_preprocessing::batch_and_prepare_binned_render_phase:: + .run_if( + resource_exists::< + BatchedInstanceBuffers, + >, + ), + ) + .in_set(RenderSet::PrepareResources), + ), + ); + } +} + +/// Stores the rendering instructions for a single phase that sorts items in all +/// views. +/// +/// They're cleared out every frame, but storing them in a resource like this +/// allows us to reuse allocations. +#[derive(Resource, Deref, DerefMut)] +pub struct ViewSortedRenderPhases(pub EntityHashMap>) +where + SPI: SortedPhaseItem; + +impl Default for ViewSortedRenderPhases +where + SPI: SortedPhaseItem, +{ + fn default() -> Self { + Self(default()) + } +} + +impl ViewSortedRenderPhases +where + SPI: SortedPhaseItem, +{ + pub fn insert_or_clear(&mut self, entity: Entity) { + match self.entry(entity) { + Entry::Occupied(mut entry) => entry.get_mut().clear(), + Entry::Vacant(entry) => { + entry.insert(default()); + } + } } } @@ -447,19 +525,21 @@ where return; }; - render_app.add_systems( - Render, - ( - no_gpu_preprocessing::batch_and_prepare_sorted_render_phase:: - .run_if(resource_exists::>), - gpu_preprocessing::batch_and_prepare_sorted_render_phase::.run_if( - resource_exists::< - BatchedInstanceBuffers, - >, - ), - ) - .in_set(RenderSet::PrepareResources), - ); + render_app + .init_resource::>() + .add_systems( + Render, + ( + no_gpu_preprocessing::batch_and_prepare_sorted_render_phase:: + .run_if(resource_exists::>), + gpu_preprocessing::batch_and_prepare_sorted_render_phase::.run_if( + resource_exists::< + BatchedInstanceBuffers, + >, + ), + ) + .in_set(RenderSet::PrepareResources), + ); } } @@ -538,7 +618,6 @@ impl UnbatchableBinnedEntityIndexSet { /// 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, @@ -566,6 +645,12 @@ where self.items.push(item); } + /// Removes all [`PhaseItem`]s from this render phase. + #[inline] + pub fn clear(&mut self) { + self.items.clear(); + } + /// Sorts all of its [`PhaseItem`]s. pub fn sort(&mut self) { I::sort(&mut self.items); @@ -902,11 +987,11 @@ impl RenderCommand

for SetItemPipeline { /// This system sorts the [`PhaseItem`]s of all [`SortedRenderPhase`]s of this /// type. -pub fn sort_phase_system(mut render_phases: Query<&mut SortedRenderPhase>) +pub fn sort_phase_system(mut render_phases: ResMut>) where I: SortedPhaseItem, { - for mut phase in &mut render_phases { + for phase in render_phases.values_mut() { phase.sort(); } } diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index d58083763a..687c3328db 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -18,7 +18,7 @@ use bevy_render::{ }, render_phase::{ AddRenderCommand, DrawFunctions, PhaseItem, PhaseItemExtraIndex, RenderCommand, - RenderCommandResult, SetItemPipeline, SortedRenderPhase, TrackedRenderPass, + RenderCommandResult, SetItemPipeline, TrackedRenderPass, ViewSortedRenderPhases, }, render_resource::{ AsBindGroup, AsBindGroupError, BindGroup, BindGroupId, BindGroupLayout, @@ -374,12 +374,13 @@ pub fn queue_material2d_meshes( render_materials: Res>>, mut render_mesh_instances: ResMut, render_material_instances: Res>, + mut transparent_render_phases: ResMut>, mut views: Query<( + Entity, &ExtractedView, &VisibleEntities, Option<&Tonemapping>, Option<&DebandDither>, - &mut SortedRenderPhase, )>, ) where M::Data: PartialEq + Eq + Hash + Clone, @@ -388,7 +389,11 @@ pub fn queue_material2d_meshes( return; } - for (view, visible_entities, tonemapping, dither, mut transparent_phase) in &mut views { + for (view_entity, view, visible_entities, tonemapping, dither) in &mut views { + let Some(transparent_phase) = transparent_render_phases.get_mut(&view_entity) else { + continue; + }; + let draw_transparent_2d = transparent_draw_functions.read().id::>(); let mut view_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples()) diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index 9f1ff76a20..0965b3d3d3 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -20,7 +20,7 @@ use bevy_render::{ render_asset::RenderAssets, render_phase::{ DrawFunctions, PhaseItem, PhaseItemExtraIndex, RenderCommand, RenderCommandResult, - SetItemPipeline, SortedRenderPhase, TrackedRenderPass, + SetItemPipeline, TrackedRenderPass, ViewSortedRenderPhases, }, render_resource::{ binding_types::{sampler, texture_2d, uniform_buffer}, @@ -447,8 +447,9 @@ pub fn queue_sprites( pipeline_cache: Res, msaa: Res, extracted_sprites: Res, + mut transparent_render_phases: ResMut>, mut views: Query<( - &mut SortedRenderPhase, + Entity, &VisibleEntities, &ExtractedView, Option<&Tonemapping>, @@ -459,7 +460,11 @@ pub fn queue_sprites( let draw_sprite_function = draw_functions.read().id::(); - for (mut transparent_phase, visible_entities, view, tonemapping, dither) in &mut views { + for (view_entity, visible_entities, view, tonemapping, dither) in &mut views { + let Some(transparent_phase) = transparent_render_phases.get_mut(&view_entity) else { + continue; + }; + let mut view_key = SpritePipelineKey::from_hdr(view.hdr) | msaa_key; if !view.hdr { @@ -534,7 +539,7 @@ pub fn prepare_sprites( mut image_bind_groups: ResMut, gpu_images: Res>, extracted_sprites: Res, - mut phases: Query<&mut SortedRenderPhase>, + mut phases: ResMut>, events: Res, ) { // If an image has changed, the GpuImage has (probably) changed @@ -567,7 +572,7 @@ pub fn prepare_sprites( let image_bind_groups = &mut *image_bind_groups; - for mut transparent_phase in &mut phases { + for transparent_phase in phases.values_mut() { let mut batch_item_index = 0; let mut batch_image_size = Vec2::ZERO; let mut batch_image_handle = AssetId::invalid(); diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 3aaca1004a..462eefea23 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -7,6 +7,7 @@ use bevy_core_pipeline::core_2d::graph::{Core2d, Node2d}; use bevy_core_pipeline::core_3d::graph::{Core3d, Node3d}; use bevy_core_pipeline::{core_2d::Camera2d, core_3d::Camera3d}; use bevy_hierarchy::Parent; +use bevy_render::render_phase::ViewSortedRenderPhases; use bevy_render::{ render_phase::{PhaseItem, PhaseItemExtraIndex}, texture::GpuImage, @@ -27,14 +28,14 @@ use crate::{ use bevy_app::prelude::*; use bevy_asset::{load_internal_asset, AssetEvent, AssetId, Assets, Handle}; -use bevy_ecs::entity::EntityHashMap; +use bevy_ecs::entity::{EntityHashMap, EntityHashSet}; use bevy_ecs::prelude::*; use bevy_math::{FloatOrd, Mat4, Rect, URect, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles}; use bevy_render::{ camera::Camera, render_asset::RenderAssets, render_graph::{RenderGraph, RunGraphOnViewNode}, - render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions, SortedRenderPhase}, + render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions}, render_resource::*, renderer::{RenderDevice, RenderQueue}, texture::Image, @@ -85,6 +86,7 @@ pub fn build_ui_render(app: &mut App) { .init_resource::() .allow_ambiguous_resource::() .init_resource::>() + .init_resource::>() .add_render_command::() .configure_sets( ExtractSchedule, @@ -657,9 +659,13 @@ pub struct DefaultCameraView(pub Entity); pub fn extract_default_ui_camera_view( mut commands: Commands, + mut transparent_render_phases: ResMut>, ui_scale: Extract>, query: Extract>>, + mut live_entities: Local, ) { + live_entities.clear(); + let scale = ui_scale.0.recip(); for (entity, camera) in &query { // ignore inactive cameras @@ -707,12 +713,16 @@ pub fn extract_default_ui_camera_view( color_grading: Default::default(), }) .id(); - commands.get_or_spawn(entity).insert(( - DefaultCameraView(default_camera_view), - SortedRenderPhase::::default(), - )); + commands + .get_or_spawn(entity) + .insert(DefaultCameraView(default_camera_view)); + transparent_render_phases.insert_or_clear(entity); + + live_entities.insert(entity); } } + + transparent_render_phases.retain(|entity, _| live_entities.contains(entity)); } #[cfg(feature = "bevy_text")] @@ -879,14 +889,18 @@ pub fn queue_uinodes( extracted_uinodes: Res, ui_pipeline: Res, mut pipelines: ResMut>, - mut views: Query<(&ExtractedView, &mut SortedRenderPhase)>, + mut transparent_render_phases: ResMut>, + mut views: Query<(Entity, &ExtractedView)>, pipeline_cache: Res, draw_functions: Res>, ) { let draw_function = draw_functions.read().id::(); for (entity, extracted_uinode) in extracted_uinodes.uinodes.iter() { - let Ok((view, mut transparent_phase)) = views.get_mut(extracted_uinode.camera_entity) - else { + let Ok((view_entity, view)) = views.get_mut(extracted_uinode.camera_entity) else { + continue; + }; + + let Some(transparent_phase) = transparent_render_phases.get_mut(&view_entity) else { continue; }; @@ -926,7 +940,7 @@ pub fn prepare_uinodes( ui_pipeline: Res, mut image_bind_groups: ResMut, gpu_images: Res>, - mut phases: Query<&mut SortedRenderPhase>, + mut phases: ResMut>, events: Res, mut previous_len: Local, ) { @@ -958,7 +972,7 @@ pub fn prepare_uinodes( let mut vertices_index = 0; let mut indices_index = 0; - for mut ui_phase in &mut phases { + for ui_phase in phases.values_mut() { let mut batch_item_index = 0; let mut batch_image_handle = AssetId::invalid(); diff --git a/crates/bevy_ui/src/render/render_pass.rs b/crates/bevy_ui/src/render/render_pass.rs index d403a44bed..3813c00e10 100644 --- a/crates/bevy_ui/src/render/render_pass.rs +++ b/crates/bevy_ui/src/render/render_pass.rs @@ -17,14 +17,7 @@ use bevy_render::{ }; pub struct UiPassNode { - ui_view_query: QueryState< - ( - &'static SortedRenderPhase, - &'static ViewTarget, - &'static ExtractedCamera, - ), - With, - >, + ui_view_query: QueryState<(&'static ViewTarget, &'static ExtractedCamera), With>, default_camera_view_query: QueryState<&'static DefaultCameraView>, } @@ -51,11 +44,19 @@ impl Node for UiPassNode { ) -> Result<(), NodeRunError> { let input_view_entity = graph.view_entity(); - let Ok((transparent_phase, target, camera)) = - self.ui_view_query.get_manual(world, input_view_entity) + let Some(transparent_render_phases) = + world.get_resource::>() else { return Ok(()); }; + + let Some(transparent_phase) = transparent_render_phases.get(&input_view_entity) else { + return Ok(()); + }; + + let Ok((target, camera)) = self.ui_view_query.get_manual(world, input_view_entity) else { + return Ok(()); + }; if transparent_phase.items.is_empty() { return Ok(()); } diff --git a/crates/bevy_ui/src/render/ui_material_pipeline.rs b/crates/bevy_ui/src/render/ui_material_pipeline.rs index f1f2b00ba9..60e342f3cb 100644 --- a/crates/bevy_ui/src/render/ui_material_pipeline.rs +++ b/crates/bevy_ui/src/render/ui_material_pipeline.rs @@ -454,7 +454,7 @@ pub fn prepare_uimaterial_nodes( view_uniforms: Res, globals_buffer: Res, ui_material_pipeline: Res>, - mut phases: Query<&mut SortedRenderPhase>, + mut phases: ResMut>, mut previous_len: Local, ) { if let (Some(view_binding), Some(globals_binding)) = ( @@ -471,7 +471,7 @@ pub fn prepare_uimaterial_nodes( )); let mut index = 0; - for mut ui_phase in &mut phases { + for ui_phase in phases.values_mut() { let mut batch_item_index = 0; let mut batch_shader_handle = AssetId::invalid(); @@ -636,7 +636,8 @@ pub fn queue_ui_material_nodes( mut pipelines: ResMut>>, pipeline_cache: Res, render_materials: Res>>, - mut views: Query<(&ExtractedView, &mut SortedRenderPhase)>, + mut transparent_render_phases: ResMut>, + mut views: Query<&ExtractedView>, ) where M::Data: PartialEq + Eq + Hash + Clone, { @@ -646,7 +647,11 @@ pub fn queue_ui_material_nodes( let Some(material) = render_materials.get(extracted_uinode.material) else { continue; }; - let Ok((view, mut transparent_phase)) = views.get_mut(extracted_uinode.camera_entity) + let Ok(view) = views.get_mut(extracted_uinode.camera_entity) else { + continue; + }; + let Some(transparent_phase) = + transparent_render_phases.get_mut(&extracted_uinode.camera_entity) else { continue; }; diff --git a/examples/2d/mesh2d_manual.rs b/examples/2d/mesh2d_manual.rs index 343db4be1d..ffdcc0184c 100644 --- a/examples/2d/mesh2d_manual.rs +++ b/examples/2d/mesh2d_manual.rs @@ -15,7 +15,7 @@ use bevy::{ render_asset::{RenderAssetUsages, RenderAssets}, render_phase::{ AddRenderCommand, DrawFunctions, PhaseItemExtraIndex, SetItemPipeline, - SortedRenderPhase, + ViewSortedRenderPhases, }, render_resource::{ BlendState, ColorTargetState, ColorWrites, Face, FragmentState, FrontFace, @@ -354,17 +354,18 @@ pub fn queue_colored_mesh2d( msaa: Res, render_meshes: Res>, render_mesh_instances: Res, - mut views: Query<( - &VisibleEntities, - &mut SortedRenderPhase, - &ExtractedView, - )>, + mut transparent_render_phases: ResMut>, + mut views: Query<(Entity, &VisibleEntities, &ExtractedView)>, ) { if render_mesh_instances.is_empty() { return; } // Iterate each view (a camera is a view) - for (visible_entities, mut transparent_phase, view) in &mut views { + for (view_entity, visible_entities, view) in &mut views { + let Some(transparent_phase) = transparent_render_phases.get_mut(&view_entity) else { + continue; + }; + let draw_colored_mesh2d = transparent_draw_functions.read().id::(); let mesh_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples()) diff --git a/examples/shader/shader_instancing.rs b/examples/shader/shader_instancing.rs index 17fd7823c9..8f0bffd83a 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, PhaseItemExtraIndex, RenderCommand, - RenderCommandResult, SetItemPipeline, SortedRenderPhase, TrackedRenderPass, + RenderCommandResult, SetItemPipeline, TrackedRenderPass, ViewSortedRenderPhases, }, render_resource::*, renderer::RenderDevice, @@ -117,13 +117,18 @@ fn queue_custom( meshes: Res>, render_mesh_instances: Res, material_meshes: Query>, - mut views: Query<(&ExtractedView, &mut SortedRenderPhase)>, + mut transparent_render_phases: ResMut>, + mut views: Query<(Entity, &ExtractedView)>, ) { let draw_custom = transparent_3d_draw_functions.read().id::(); let msaa_key = MeshPipelineKey::from_msaa_samples(msaa.samples()); - for (view, mut transparent_phase) in &mut views { + for (view_entity, view) in &mut views { + let Some(transparent_phase) = transparent_render_phases.get_mut(&view_entity) else { + continue; + }; + let view_key = msaa_key | MeshPipelineKey::from_hdr(view.hdr); let rangefinder = view.rangefinder3d(); for entity in &material_meshes {