From 9da0b2a0ecdb8257084e3fa01bc879f3b6898a90 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Tue, 21 May 2024 11:23:04 -0700 Subject: [PATCH] Make render phases render world resources instead of components. (#13277) This commit makes us stop using the render world ECS for `BinnedRenderPhase` and `SortedRenderPhase` and instead use resources with `EntityHashMap`s inside. There are three reasons to do this: 1. We can use `clear()` to clear out the render phase collections instead of recreating the components from scratch, allowing us to reuse allocations. 2. This is a prerequisite for retained bins, because components can't be retained from frame to frame in the render world, but resources can. 3. We want to move away from storing anything in components in the render world ECS, and this is a step in that direction. This patch results in a small performance benefit, due to point (1) above. ## Changelog ### Changed * The `BinnedRenderPhase` and `SortedRenderPhase` render world components have been replaced with `ViewBinnedRenderPhases` and `ViewSortedRenderPhases` resources. ## Migration Guide * The `BinnedRenderPhase` and `SortedRenderPhase` render world components have been replaced with `ViewBinnedRenderPhases` and `ViewSortedRenderPhases` resources. Instead of querying for the components, look the camera entity up in the `ViewBinnedRenderPhases`/`ViewSortedRenderPhases` tables. --- .../core_2d/main_transparent_pass_2d_node.rs | 19 +- crates/bevy_core_pipeline/src/core_2d/mod.rs | 23 +- .../src/core_3d/main_opaque_pass_3d_node.rs | 23 +- .../core_3d/main_transmissive_pass_3d_node.rs | 17 +- .../core_3d/main_transparent_pass_3d_node.rs | 15 +- crates/bevy_core_pipeline/src/core_3d/mod.rs | 203 +++++++++++------- .../bevy_core_pipeline/src/deferred/node.rs | 27 ++- crates/bevy_core_pipeline/src/prepass/node.rs | 31 ++- crates/bevy_gizmos/src/pipeline_2d.rs | 29 +-- crates/bevy_gizmos/src/pipeline_3d.rs | 21 +- crates/bevy_pbr/src/material.rs | 44 ++-- crates/bevy_pbr/src/prepass/mod.rs | 59 +++-- crates/bevy_pbr/src/render/light.rs | 67 ++++-- .../src/batching/gpu_preprocessing.rs | 25 ++- crates/bevy_render/src/batching/mod.rs | 10 +- .../src/batching/no_gpu_preprocessing.rs | 18 +- crates/bevy_render/src/render_phase/mod.rs | 149 ++++++++++--- crates/bevy_sprite/src/mesh2d/material.rs | 11 +- crates/bevy_sprite/src/render/mod.rs | 15 +- crates/bevy_ui/src/render/mod.rs | 36 +++- crates/bevy_ui/src/render/render_pass.rs | 21 +- .../src/render/ui_material_pipeline.rs | 13 +- examples/2d/mesh2d_manual.rs | 15 +- examples/shader/shader_instancing.rs | 11 +- 24 files changed, 608 insertions(+), 294 deletions(-) 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 {