From 1634310359db2b32690e1c75a22ab289c8e9cf6b Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Fri, 24 Jan 2025 23:07:39 -0800 Subject: [PATCH 01/35] Start cold-specialization. --- crates/bevy_pbr/src/material.rs | 145 ++++++++- crates/bevy_pbr/src/mesh_material.rs | 10 +- crates/bevy_render/Cargo.toml | 1 + crates/bevy_render/src/camera/camera.rs | 19 ++ crates/bevy_render/src/lib.rs | 6 +- crates/bevy_render/src/mesh/components.rs | 18 +- crates/bevy_render/src/specialization/mod.rs | 296 +++++++++++++++++++ crates/bevy_sprite/src/mesh2d/material.rs | 10 +- 8 files changed, 499 insertions(+), 6 deletions(-) create mode 100644 crates/bevy_render/src/specialization/mod.rs diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 96ccdf781e840..b2497d2f8dfe9 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -6,6 +6,7 @@ use crate::meshlet::{ InstanceManager, }; use crate::*; +use bevy_asset::prelude::AssetChanged; use bevy_asset::{Asset, AssetId, AssetServer, UntypedAssetId}; use bevy_core_pipeline::{ core_3d::{ @@ -20,6 +21,8 @@ use bevy_core_pipeline::{ tonemapping::{DebandDither, Tonemapping}, }; use bevy_derive::{Deref, DerefMut}; +use bevy_ecs::query::QueryItem; +use bevy_ecs::system::lifetimeless::Read; use bevy_ecs::{ prelude::*, system::{ @@ -30,6 +33,7 @@ use bevy_ecs::{ use bevy_platform_support::collections::HashMap; use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::Reflect; +use bevy_render::specialization::{CheckSpecialization, CheckSpecializationPlugin, EntitySpecializedSender}; use bevy_render::{ batching::gpu_preprocessing::GpuPreprocessingSupport, camera::TemporalJitter, @@ -271,7 +275,10 @@ where fn build(&self, app: &mut App) { app.init_asset::() .register_type::>() - .add_plugins(RenderAssetPlugin::>::default()) + .add_plugins(( + RenderAssetPlugin::>::default(), + CheckSpecializationPlugin::>::default(), + )) .add_systems( PostUpdate, mark_meshes_as_changed_if_their_materials_changed:: @@ -591,6 +598,136 @@ pub const fn screen_space_specular_transmission_pipeline_key( } } +impl CheckSpecialization for MeshMaterial3d { + type ViewKey = MeshPipelineKey; + type VisibilityClass = Mesh3d; + type ViewKeyQueryData = ( + Read, + Read, + Option>, + Option>, + Option>, + Has, + ( + Has, + Has, + Has, + Has, + ), + Option>, + Has, + Option>, + Has, + ( + Has>, + Has>, + ), + Has, + ); + type EntityQueryFilter = Or<( + Changed, + AssetChanged, + Changed>, + AssetChanged>, + )>; + + fn get_view_key( + ( + view, + msaa, + tonemapping, + dither, + shadow_filter_method, + ssao, + (normal_prepass, depth_prepass, motion_vector_prepass, deferred_prepass), + camera_3d, + temporal_jitter, + projection, + distance_fog, + (has_environment_maps, has_irradiance_volumes), + has_oit, + ): QueryItem, + ) -> Self::ViewKey { + let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples()) + | MeshPipelineKey::from_hdr(view.hdr); + + if normal_prepass { + view_key |= MeshPipelineKey::NORMAL_PREPASS; + } + + if depth_prepass { + view_key |= MeshPipelineKey::DEPTH_PREPASS; + } + + if motion_vector_prepass { + view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS; + } + + if deferred_prepass { + view_key |= MeshPipelineKey::DEFERRED_PREPASS; + } + + if temporal_jitter { + view_key |= MeshPipelineKey::TEMPORAL_JITTER; + } + + if has_environment_maps { + view_key |= MeshPipelineKey::ENVIRONMENT_MAP; + } + + if has_irradiance_volumes { + view_key |= MeshPipelineKey::IRRADIANCE_VOLUME; + } + + if has_oit { + view_key |= MeshPipelineKey::OIT_ENABLED; + } + + if let Some(projection) = projection { + view_key |= match projection { + Projection::Perspective(_) => MeshPipelineKey::VIEW_PROJECTION_PERSPECTIVE, + Projection::Orthographic(_) => MeshPipelineKey::VIEW_PROJECTION_ORTHOGRAPHIC, + Projection::Custom(_) => MeshPipelineKey::VIEW_PROJECTION_NONSTANDARD, + }; + } + + match shadow_filter_method.unwrap_or(&ShadowFilteringMethod::default()) { + ShadowFilteringMethod::Hardware2x2 => { + view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_HARDWARE_2X2; + } + ShadowFilteringMethod::Gaussian => { + view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_GAUSSIAN; + } + ShadowFilteringMethod::Temporal => { + view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_TEMPORAL; + } + } + + if !view.hdr { + if let Some(tonemapping) = tonemapping { + view_key |= MeshPipelineKey::TONEMAP_IN_SHADER; + view_key |= tonemapping_pipeline_key(*tonemapping); + } + if let Some(DebandDither::Enabled) = dither { + view_key |= MeshPipelineKey::DEBAND_DITHER; + } + } + if ssao { + view_key |= MeshPipelineKey::SCREEN_SPACE_AMBIENT_OCCLUSION; + } + if distance_fog { + view_key |= MeshPipelineKey::DISTANCE_FOG; + } + if let Some(camera_3d) = camera_3d { + view_key |= screen_space_specular_transmission_pipeline_key( + camera_3d.screen_space_specular_transmission_quality, + ); + } + + view_key + } +} + /// A system that ensures that /// [`crate::render::mesh::extract_meshes_for_gpu_building`] re-extracts meshes /// whose materials changed. @@ -676,16 +813,18 @@ pub fn queue_material_meshes( render_material_instances: Res>, render_lightmaps: Res, render_visibility_ranges: Res, - (mesh_allocator, material_bind_group_allocator, gpu_preprocessing_support): ( + (mesh_allocator, material_bind_group_allocator, gpu_preprocessing_support, tx): ( Res, Res>, Res, + ResMut>>, ), mut opaque_render_phases: ResMut>, mut alpha_mask_render_phases: ResMut>, mut transmissive_render_phases: ResMut>, mut transparent_render_phases: ResMut>, views: Query<( + &MainEntity, &ExtractedView, &RenderVisibleEntities, &Msaa, @@ -713,6 +852,7 @@ pub fn queue_material_meshes( M::Data: PartialEq + Eq + Hash + Clone, { for ( + main_view_entity, view, visible_entities, msaa, @@ -896,6 +1036,7 @@ pub fn queue_material_meshes( }, &mesh.layout, ); + tx.send(visible_entity.entity(), main_view_entity.entity()); let pipeline_id = match pipeline_id { Ok(id) => id, Err(err) => { diff --git a/crates/bevy_pbr/src/mesh_material.rs b/crates/bevy_pbr/src/mesh_material.rs index 84eaf7cffa79a..e8d7dd6c5391c 100644 --- a/crates/bevy_pbr/src/mesh_material.rs +++ b/crates/bevy_pbr/src/mesh_material.rs @@ -1,5 +1,5 @@ use crate::Material; -use bevy_asset::{AssetId, Handle}; +use bevy_asset::{AsAssetId, AssetId, Handle}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{component::Component, reflect::ReflectComponent}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; @@ -57,3 +57,11 @@ impl From<&MeshMaterial3d> for AssetId { material.id() } } + +impl AsAssetId for MeshMaterial3d { + type Asset = M; + + fn as_asset_id(&self) -> AssetId { + self.id() + } +} \ No newline at end of file diff --git a/crates/bevy_render/Cargo.toml b/crates/bevy_render/Cargo.toml index 4962125622270..577a082acbcea 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -96,6 +96,7 @@ smallvec = { version = "1.11", features = ["const_new"] } offset-allocator = "0.2" variadics_please = "1.1" tracing = { version = "0.1", default-features = false, features = ["std"] } +crossbeam-channel = "0.5" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] # Omit the `glsl` feature in non-WebAssembly by default. diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index dc977ceb8404f..45d02a0e3b5d9 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -3,6 +3,8 @@ reason = "The parent module contains all things viewport-related, while this module handles cameras as a component. However, a rename/refactor which should clear up this lint is being discussed; see #17196." )] use super::{ClearColorConfig, Projection}; +use crate::specialization::{EntitiesToSpecialize, RenderEntitiesToSpecialize}; +use crate::sync_world::MainEntity; use crate::{ batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport}, camera::{CameraProjection, ManualTextureViewHandle, ManualTextureViews}, @@ -44,6 +46,7 @@ use bevy_window::{ }; use core::ops::Range; use derive_more::derive::From; +use std::ops::Deref; use tracing::warn; use wgpu::{BlendState, TextureFormat, TextureUsages}; @@ -296,6 +299,7 @@ pub enum ViewportConversionError { Frustum, CameraMainTextureUsages, VisibleEntities, + EntitiesToSpecialize, Transform, Visibility, Msaa, @@ -1052,6 +1056,7 @@ pub fn extract_cameras( &CameraRenderGraph, &GlobalTransform, &VisibleEntities, + &EntitiesToSpecialize, &Frustum, Option<&ColorGrading>, Option<&Exposure>, @@ -1073,6 +1078,7 @@ pub fn extract_cameras( camera_render_graph, transform, visible_entities, + entities_to_specialize, frustum, color_grading, exposure, @@ -1134,6 +1140,18 @@ pub fn extract_cameras( }) .collect(), }; + + let entities_to_specialize = RenderEntitiesToSpecialize { + entities: entities_to_specialize + .entities + .iter() + .map(|(type_id, entities)| { + let entities = entities.iter().copied().map(MainEntity::from).collect(); + (*type_id, entities) + }) + .collect(), + }; + let mut commands = commands.entity(render_entity); commands.insert(( ExtractedCamera { @@ -1168,6 +1186,7 @@ pub fn extract_cameras( color_grading, }, render_visible_entities, + entities_to_specialize, *frustum, )); diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 0f9c504edeeb9..0cbcd452e7a4c 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -46,6 +46,7 @@ pub mod sync_component; pub mod sync_world; pub mod texture; pub mod view; +pub mod specialization; /// The render prelude. /// @@ -102,6 +103,7 @@ use bevy_ecs::{prelude::*, schedule::ScheduleLabel}; use core::ops::{Deref, DerefMut}; use std::sync::Mutex; use tracing::debug; +use crate::specialization::SpecializationPlugin; /// Contains the default Bevy rendering backend based on wgpu. /// @@ -267,7 +269,9 @@ pub const COLOR_OPERATIONS_SHADER_HANDLE: Handle = impl Plugin for RenderPlugin { /// Initializes the renderer, sets up the [`RenderSet`] and creates the rendering sub-app. fn build(&self, app: &mut App) { - app.init_asset::() + app + .add_plugins(SpecializationPlugin) + .init_asset::() .init_asset_loader::(); match &self.render_creation { diff --git a/crates/bevy_render/src/mesh/components.rs b/crates/bevy_render/src/mesh/components.rs index 9bfa05968d733..7c0d87cf51de3 100644 --- a/crates/bevy_render/src/mesh/components.rs +++ b/crates/bevy_render/src/mesh/components.rs @@ -2,7 +2,7 @@ use crate::{ mesh::Mesh, view::{self, Visibility, VisibilityClass}, }; -use bevy_asset::{AssetEvent, AssetId, Handle}; +use bevy_asset::{AsAssetId, AssetEvent, AssetId, Handle}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ change_detection::DetectChangesMut, component::Component, event::EventReader, prelude::require, @@ -58,6 +58,14 @@ impl From<&Mesh2d> for AssetId { } } +impl AsAssetId for Mesh2d { + type Asset = Mesh; + + fn as_asset_id(&self) -> AssetId { + self.id() + } +} + /// A component for 3D meshes. Requires a [`MeshMaterial3d`] to be rendered, commonly using a [`StandardMaterial`]. /// /// [`MeshMaterial3d`]: @@ -106,6 +114,14 @@ impl From<&Mesh3d> for AssetId { } } +impl AsAssetId for Mesh3d { + type Asset = Mesh; + + fn as_asset_id(&self) -> AssetId { + self.id() + } +} + /// A system that marks a [`Mesh3d`] as changed if the associated [`Mesh`] asset /// has changed. /// diff --git a/crates/bevy_render/src/specialization/mod.rs b/crates/bevy_render/src/specialization/mod.rs new file mode 100644 index 0000000000000..185b74682cc36 --- /dev/null +++ b/crates/bevy_render/src/specialization/mod.rs @@ -0,0 +1,296 @@ +use crate::extract_resource::ExtractResource; +use crate::sync_world::{MainEntity, MainEntityHashSet}; +use crate::view::VisibilitySystems::CheckVisibility; +use crate::view::VisibleEntities; +use crate::RenderApp; +use bevy_app::{App, Plugin, PostUpdate}; +use bevy_derive::{Deref, DerefMut}; +use bevy_ecs::component::{Component, Tick}; +use bevy_ecs::entity::hash_map::EntityHashMap; +use bevy_ecs::entity::hash_set::EntityHashSet; +use bevy_ecs::entity::{Entity, EntityHash}; +use bevy_ecs::prelude::SystemSet; +use bevy_ecs::query::{QueryFilter, QueryItem, ReadOnlyQueryData}; +use bevy_ecs::resource::Resource; +use bevy_ecs::schedule::IntoSystemConfigs; +use bevy_ecs::schedule::IntoSystemSetConfigs; +use bevy_ecs::system::{Commands, Local, Query, Res, ResMut, SystemChangeTick}; +use bevy_platform_support::collections::{HashMap, HashSet}; +use bevy_reflect::prelude::ReflectDefault; +use bevy_reflect::Reflect; +use bevy_utils::{Parallel, TypeIdMap}; +use core::marker::PhantomData; +use crossbeam_channel::{Receiver, Sender}; +use std::any::TypeId; + +pub struct SpecializationPlugin; + +impl Plugin for SpecializationPlugin { + fn build(&self, app: &mut App) { + use SpecializationSystems::*; + + app.configure_sets( + PostUpdate, + ( + UpdateLastSpecialized.after(CheckVisibility), + CheckSpecialization.after(UpdateLastSpecialized), + MarkEntitiesToSpecialize.after(CheckSpecialization), + ), + ) + .register_type::() + .init_resource::() + .register_type::() + .init_resource::(); + } +} + +pub struct CheckSpecializationPlugin(PhantomData); + +impl Default for CheckSpecializationPlugin { + fn default() -> Self { + Self(Default::default()) + } +} + +impl Plugin for CheckSpecializationPlugin +where + M: CheckSpecialization, +{ + fn build(&self, app: &mut App) { + use SpecializationSystems::*; + app.init_resource::>().add_systems( + PostUpdate, + ( + update_last_specialized::.in_set(UpdateLastSpecialized), + ( + check_entities_needs_specialization::, + check_views_need_specialization::, + ) + .in_set(CheckSpecialization), + mark_entities_for_specialization::.in_set(MarkEntitiesToSpecialize), + ), + ); + } + + fn finish(&self, app: &mut App) { + let (tx, rx) = crossbeam_channel::unbounded(); + app.insert_resource(EntitySpecializedReceiver::::new(rx)); + + if let Some(render_app) = app.get_sub_app_mut(RenderApp) { + render_app.insert_resource(EntitySpecializedSender::::new(tx)); + } + } +} + +#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] +enum SpecializationSystems { + UpdateLastSpecialized, + CheckSpecialization, + MarkEntitiesToSpecialize, +} + +#[derive(Clone, Resource, Default, Debug, Reflect)] +#[reflect(Default)] +pub struct EntitySpecializationTicks { + #[reflect(ignore)] + pub entities: TypeIdMap>, +} + +#[derive(Clone, Resource, Default, Debug, Reflect)] +#[reflect(Default)] +pub struct LastSpecializedTicks { + // (entity, view_entity) -> tick + #[reflect(ignore)] + pub entity_views: TypeIdMap>, +} + +#[derive(Clone, Component, Default, Debug, Reflect)] +#[reflect(Default)] +pub struct EntitiesToSpecialize { + #[reflect(ignore)] + pub entities: TypeIdMap>, +} + +#[derive(Clone, Component, Default, Debug, Reflect)] +#[reflect(Default)] +pub struct RenderEntitiesToSpecialize { + #[reflect(ignore)] + pub entities: TypeIdMap>, +} + +#[derive(Resource)] +pub struct EntitySpecializedSender { + tx: Sender<(Entity, Entity)>, + _marker: PhantomData, +} + +impl EntitySpecializedSender { + pub fn new(tx: Sender<(Entity, Entity)>) -> Self { + Self { + tx, + _marker: Default::default(), + } + } + + pub fn send(&self, entity: Entity, view_entity: Entity) { + self.tx.send((entity, view_entity)).unwrap(); + } +} + +#[derive(Resource)] +pub struct EntitySpecializedReceiver { + rx: Receiver<(Entity, Entity)>, + _marker: PhantomData, +} + +impl EntitySpecializedReceiver { + pub fn new(rx: Receiver<(Entity, Entity)>) -> Self { + Self { + rx, + _marker: Default::default(), + } + } + + pub fn recv(&self) -> Option<(Entity, Entity)> { + self.rx.try_recv().ok() + } +} + +pub fn update_last_specialized( + mut last_specialized_ticks: ResMut, + entity_specialized_receiver: Res>, + ticks: SystemChangeTick, +) where + M: CheckSpecialization, +{ + while let Some((entity, view_entity)) = entity_specialized_receiver.recv() { + let last_specialized_ticks = last_specialized_ticks + .entity_views + .entry(TypeId::of::()) + .or_default(); + last_specialized_ticks.insert((entity, view_entity), ticks.this_run()); + } +} + +/// Marks entities that need specialization in the render world. +pub fn mark_entities_for_specialization( + entity_specialization_ticks: Res, + last_specialized_ticks: Res, + mut views: Query<(Entity, &VisibleEntities, &mut EntitiesToSpecialize)>, + ticks: SystemChangeTick, +) where + M: CheckSpecialization, +{ + for (view_entity, visible_entities, mut entities_to_specialize) in views.iter_mut() { + let entities_to_specialize = entities_to_specialize + .entities + .entry(TypeId::of::()) + .or_default(); + entities_to_specialize.clear(); + + for entity in visible_entities.iter(TypeId::of::()) { + let entity_specialization_ticks = entity_specialization_ticks + .entities + .get(&TypeId::of::()) + .unwrap(); + let last_specialized_ticks = last_specialized_ticks + .entity_views + .get(&TypeId::of::()); + let last_specialized_tick = + last_specialized_ticks.and_then(|ticks| ticks.get(&(*entity, view_entity))); + if last_specialized_tick.is_none() + || entity_specialization_ticks + .get(entity) + .unwrap() + .is_newer_than(*last_specialized_tick.unwrap(), ticks.this_run()) + || entity_specialization_ticks + .get(&view_entity) + .unwrap() + .is_newer_than(*last_specialized_tick.unwrap(), ticks.this_run()) + { + println!("Marking entity for specialization: {:?}", entity); + entities_to_specialize.push(*entity); + } + } + } +} + +pub fn check_entities_needs_specialization( + mut thread_queues: Local>>, + mut needs_specialization: Query<(Entity,), M::EntityQueryFilter>, + mut entity_specialization_ticks: ResMut, + ticks: SystemChangeTick, +) where + M: CheckSpecialization, +{ + needs_specialization.par_iter_mut().for_each_init( + || thread_queues.borrow_local_mut(), + |queue, query_item| { + let (entity,) = query_item; + queue.push(entity); + }, + ); + + let mut entity_specialization_ticks = entity_specialization_ticks + .entities + .entry(TypeId::of::()) + .or_default(); + let size = thread_queues.iter_mut().map(|queue| queue.len()).sum(); + entity_specialization_ticks.reserve(size); + let this_run = ticks.this_run(); + for queue in thread_queues.iter_mut() { + for entity in queue.drain(..) { + // Update the entity's specialization tick with this run's tick + entity_specialization_ticks.insert(entity, this_run); + } + } +} + +#[derive(Resource, Deref, DerefMut, ExtractResource, Clone)] +pub struct ViewKeyCache(EntityHashMap) +where + M: CheckSpecialization; + +impl Default for ViewKeyCache +where + M: CheckSpecialization, +{ + fn default() -> Self { + Self(EntityHashMap::default()) + } +} + +pub trait CheckSpecialization: Component { + type ViewKey: PartialEq + Send + Sync + 'static; + type VisibilityClass: Component; + type ViewKeyQueryData: ReadOnlyQueryData + 'static; + type EntityQueryFilter: QueryFilter + 'static; + + fn get_view_key<'w>(view_query: QueryItem<'w, Self::ViewKeyQueryData>) -> Self::ViewKey; +} + +pub fn check_views_need_specialization( + mut view_key_cache: ResMut>, + mut view_specialization_ticks: ResMut, + mut views: Query<(Entity, M::ViewKeyQueryData)>, + ticks: SystemChangeTick, +) where + M: CheckSpecialization, +{ + let view_specialization_ticks = view_specialization_ticks + .entities + .entry(TypeId::of::()) + .or_default(); + for (view_entity, view_query) in views.iter_mut() { + let view_key = M::get_view_key(view_query); + if let Some(current_key) = view_key_cache.get_mut(&view_entity) { + if *current_key != view_key { + view_key_cache.insert(view_entity, view_key); + view_specialization_ticks.insert(view_entity, ticks.this_run()); + } + } else { + view_key_cache.insert(view_entity, view_key); + view_specialization_ticks.insert(view_entity, ticks.this_run()); + } + } +} diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index e44b27a005fa3..b2e8af7a0c300 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -3,7 +3,7 @@ use crate::{ SetMesh2dBindGroup, SetMesh2dViewBindGroup, }; use bevy_app::{App, Plugin}; -use bevy_asset::{Asset, AssetApp, AssetId, AssetServer, Handle}; +use bevy_asset::{AsAssetId, Asset, AssetApp, AssetId, AssetServer, Handle}; use bevy_core_pipeline::{ core_2d::{ AlphaMask2d, AlphaMask2dBinKey, BatchSetKey2d, Opaque2d, Opaque2dBinKey, Transparent2d, @@ -202,6 +202,14 @@ impl From<&MeshMaterial2d> for AssetId { } } +impl AsAssetId for MeshMaterial2d { + type Asset = M; + + fn as_asset_id(&self) -> AssetId { + self.id() + } +} + /// Sets how a 2d material's base color alpha channel is used for transparency. /// Currently, this only works with [`Mesh2d`]. Sprites are always transparent. /// From 62e01ac15e1be4893fc43a2c41104596303a6539 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Sat, 25 Jan 2025 19:03:57 -0800 Subject: [PATCH 02/35] Use ticks in render world. --- crates/bevy_pbr/src/material.rs | 647 ++++++++---------- crates/bevy_pbr/src/render/mesh.rs | 148 +++- crates/bevy_render/src/camera/camera.rs | 16 - crates/bevy_render/src/lib.rs | 2 - crates/bevy_render/src/specialization/mod.rs | 341 ++++----- crates/bevy_render/src/specialization/view.rs | 100 +++ examples/3d/3d_scene.rs | 8 + 7 files changed, 651 insertions(+), 611 deletions(-) create mode 100644 crates/bevy_render/src/specialization/view.rs diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index b2497d2f8dfe9..a069fbe5a964b 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -8,6 +8,8 @@ use crate::meshlet::{ use crate::*; use bevy_asset::prelude::AssetChanged; use bevy_asset::{Asset, AssetId, AssetServer, UntypedAssetId}; +use bevy_core_pipeline::deferred::{AlphaMask3dDeferred, Opaque3dDeferred}; +use bevy_core_pipeline::prepass::{AlphaMask3dPrepass, Opaque3dPrepass}; use bevy_core_pipeline::{ core_3d::{ AlphaMask3d, Camera3d, Opaque3d, Opaque3dBatchSetKey, Opaque3dBinKey, @@ -21,7 +23,7 @@ use bevy_core_pipeline::{ tonemapping::{DebandDither, Tonemapping}, }; use bevy_derive::{Deref, DerefMut}; -use bevy_ecs::query::QueryItem; +use bevy_ecs::query::{QueryItem, ROQueryItem}; use bevy_ecs::system::lifetimeless::Read; use bevy_ecs::{ prelude::*, @@ -33,7 +35,10 @@ use bevy_ecs::{ use bevy_platform_support::collections::HashMap; use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::Reflect; -use bevy_render::specialization::{CheckSpecialization, CheckSpecializationPlugin, EntitySpecializedSender}; +use bevy_render::specialization::view::ViewKeyCache; +use bevy_render::specialization::{ + CheckSpecializationPlugin, NeedsSpecialization, SpecializedPipelines, +}; use bevy_render::{ batching::gpu_preprocessing::GpuPreprocessingSupport, camera::TemporalJitter, @@ -302,9 +307,15 @@ where ) .add_systems( Render, - queue_material_meshes:: - .in_set(RenderSet::QueueMeshes) - .after(prepare_assets::>), + ( + specialize_material_meshes:: + .in_set(RenderSet::PrepareAssets) + .after(prepare_assets::>) + .after(prepare_assets::), + queue_material_meshes:: + .in_set(RenderSet::QueueMeshes) + .after(prepare_assets::>), + ), ) .add_systems( Render, @@ -598,133 +609,19 @@ pub const fn screen_space_specular_transmission_pipeline_key( } } -impl CheckSpecialization for MeshMaterial3d { +impl NeedsSpecialization for MeshMaterial3d { type ViewKey = MeshPipelineKey; - type VisibilityClass = Mesh3d; - type ViewKeyQueryData = ( - Read, - Read, - Option>, - Option>, - Option>, - Has, - ( - Has, - Has, - Has, - Has, - ), - Option>, - Has, - Option>, - Has, - ( - Has>, - Has>, - ), - Has, - ); - type EntityQueryFilter = Or<( + type QueryData = (); + type QueryFilter = Or<( Changed, AssetChanged, Changed>, AssetChanged>, )>; - fn get_view_key( - ( - view, - msaa, - tonemapping, - dither, - shadow_filter_method, - ssao, - (normal_prepass, depth_prepass, motion_vector_prepass, deferred_prepass), - camera_3d, - temporal_jitter, - projection, - distance_fog, - (has_environment_maps, has_irradiance_volumes), - has_oit, - ): QueryItem, - ) -> Self::ViewKey { - let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples()) - | MeshPipelineKey::from_hdr(view.hdr); - - if normal_prepass { - view_key |= MeshPipelineKey::NORMAL_PREPASS; - } - - if depth_prepass { - view_key |= MeshPipelineKey::DEPTH_PREPASS; - } - - if motion_vector_prepass { - view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS; - } - - if deferred_prepass { - view_key |= MeshPipelineKey::DEFERRED_PREPASS; - } - - if temporal_jitter { - view_key |= MeshPipelineKey::TEMPORAL_JITTER; - } - - if has_environment_maps { - view_key |= MeshPipelineKey::ENVIRONMENT_MAP; - } - - if has_irradiance_volumes { - view_key |= MeshPipelineKey::IRRADIANCE_VOLUME; - } - - if has_oit { - view_key |= MeshPipelineKey::OIT_ENABLED; - } - - if let Some(projection) = projection { - view_key |= match projection { - Projection::Perspective(_) => MeshPipelineKey::VIEW_PROJECTION_PERSPECTIVE, - Projection::Orthographic(_) => MeshPipelineKey::VIEW_PROJECTION_ORTHOGRAPHIC, - Projection::Custom(_) => MeshPipelineKey::VIEW_PROJECTION_NONSTANDARD, - }; - } - - match shadow_filter_method.unwrap_or(&ShadowFilteringMethod::default()) { - ShadowFilteringMethod::Hardware2x2 => { - view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_HARDWARE_2X2; - } - ShadowFilteringMethod::Gaussian => { - view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_GAUSSIAN; - } - ShadowFilteringMethod::Temporal => { - view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_TEMPORAL; - } - } - - if !view.hdr { - if let Some(tonemapping) = tonemapping { - view_key |= MeshPipelineKey::TONEMAP_IN_SHADER; - view_key |= tonemapping_pipeline_key(*tonemapping); - } - if let Some(DebandDither::Enabled) = dither { - view_key |= MeshPipelineKey::DEBAND_DITHER; - } - } - if ssao { - view_key |= MeshPipelineKey::SCREEN_SPACE_AMBIENT_OCCLUSION; - } - if distance_fog { - view_key |= MeshPipelineKey::DISTANCE_FOG; - } - if let Some(camera_3d) = camera_3d { - view_key |= screen_space_specular_transmission_pipeline_key( - camera_3d.screen_space_specular_transmission_quality, - ); - } - - view_key + fn needs_specialization(_item: ROQueryItem<'_, Self::QueryData>) -> bool { + // Matching the filter is enough to trigger the specialization. + true } } @@ -789,21 +686,9 @@ fn extract_mesh_materials( } } } - /// For each view, iterates over all the meshes visible from that view and adds /// them to [`BinnedRenderPhase`]s or [`SortedRenderPhase`]s as appropriate. -pub fn queue_material_meshes( - ( - opaque_draw_functions, - alpha_mask_draw_functions, - transmissive_draw_functions, - transparent_draw_functions, - ): ( - Res>, - Res>, - Res>, - Res>, - ), +pub fn specialize_material_meshes( material_pipeline: Res>, mut pipelines: ResMut>>, pipeline_cache: Res, @@ -813,160 +698,42 @@ pub fn queue_material_meshes( render_material_instances: Res>, render_lightmaps: Res, render_visibility_ranges: Res, - (mesh_allocator, material_bind_group_allocator, gpu_preprocessing_support, tx): ( - Res, - Res>, - Res, - ResMut>>, + material_bind_group_allocator: Res>, + ( + mut opaque_render_phases, + mut alpha_mask_render_phases, + mut transmissive_render_phases, + mut transparent_render_phases, + ): ( + ResMut>, + ResMut>, + ResMut>, + ResMut>, ), - mut opaque_render_phases: ResMut>, - mut alpha_mask_render_phases: ResMut>, - mut transmissive_render_phases: ResMut>, - mut transparent_render_phases: ResMut>, - views: Query<( - &MainEntity, - &ExtractedView, - &RenderVisibleEntities, - &Msaa, - Option<&Tonemapping>, - Option<&DebandDither>, - Option<&ShadowFilteringMethod>, - Has, - ( - Has, - Has, - Has, - Has, - ), - Option<&Camera3d>, - Has, - Option<&Projection>, - Has, - ( - Has>, - Has>, - ), - Has, - )>, + views: Query<(&MainEntity, &ExtractedView, &RenderVisibleEntities)>, + view_key_cache: Res>, + mut specialized_piplines: SpecializedPipelines>, ) where M::Data: PartialEq + Eq + Hash + Clone, { - for ( - main_view_entity, - view, - visible_entities, - msaa, - tonemapping, - dither, - shadow_filter_method, - ssao, - (normal_prepass, depth_prepass, motion_vector_prepass, deferred_prepass), - camera_3d, - temporal_jitter, - projection, - distance_fog, - (has_environment_maps, has_irradiance_volumes), - has_oit, - ) in &views - { - let ( - Some(opaque_phase), - Some(alpha_mask_phase), - Some(transmissive_phase), - Some(transparent_phase), - ) = ( - opaque_render_phases.get_mut(&view.retained_view_entity), - alpha_mask_render_phases.get_mut(&view.retained_view_entity), - transmissive_render_phases.get_mut(&view.retained_view_entity), - transparent_render_phases.get_mut(&view.retained_view_entity), - ) - else { + for (view_entity, view, visible_entities) in &views { + if !transparent_render_phases.contains_key(&view.retained_view_entity) + && !opaque_render_phases.contains_key(&view.retained_view_entity) + && !alpha_mask_render_phases.contains_key(&view.retained_view_entity) + && !transmissive_render_phases.contains_key(&view.retained_view_entity) + { 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::>(); - let draw_transparent_pbr = transparent_draw_functions.read().id::>(); - - let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples()) - | MeshPipelineKey::from_hdr(view.hdr); - - if normal_prepass { - view_key |= MeshPipelineKey::NORMAL_PREPASS; - } - - if depth_prepass { - view_key |= MeshPipelineKey::DEPTH_PREPASS; - } - - if motion_vector_prepass { - view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS; - } - - if deferred_prepass { - view_key |= MeshPipelineKey::DEFERRED_PREPASS; - } - - if temporal_jitter { - view_key |= MeshPipelineKey::TEMPORAL_JITTER; - } - - if has_environment_maps { - view_key |= MeshPipelineKey::ENVIRONMENT_MAP; - } - - if has_irradiance_volumes { - view_key |= MeshPipelineKey::IRRADIANCE_VOLUME; - } - - if has_oit { - view_key |= MeshPipelineKey::OIT_ENABLED; - } - - if let Some(projection) = projection { - view_key |= match projection { - Projection::Perspective(_) => MeshPipelineKey::VIEW_PROJECTION_PERSPECTIVE, - Projection::Orthographic(_) => MeshPipelineKey::VIEW_PROJECTION_ORTHOGRAPHIC, - Projection::Custom(_) => MeshPipelineKey::VIEW_PROJECTION_NONSTANDARD, - }; } - match shadow_filter_method.unwrap_or(&ShadowFilteringMethod::default()) { - ShadowFilteringMethod::Hardware2x2 => { - view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_HARDWARE_2X2; - } - ShadowFilteringMethod::Gaussian => { - view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_GAUSSIAN; - } - ShadowFilteringMethod::Temporal => { - view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_TEMPORAL; - } - } + let Some(view_key) = view_key_cache.get(view_entity) else { + continue; + }; - if !view.hdr { - if let Some(tonemapping) = tonemapping { - view_key |= MeshPipelineKey::TONEMAP_IN_SHADER; - view_key |= tonemapping_pipeline_key(*tonemapping); - } - if let Some(DebandDither::Enabled) = dither { - view_key |= MeshPipelineKey::DEBAND_DITHER; + for (_, visible_entity) in visible_entities.iter::() { + if !specialized_piplines.needs_specialization(*view_entity, *visible_entity) { + continue; } - } - if ssao { - view_key |= MeshPipelineKey::SCREEN_SPACE_AMBIENT_OCCLUSION; - } - if distance_fog { - view_key |= MeshPipelineKey::DISTANCE_FOG; - } - if let Some(camera_3d) = camera_3d { - view_key |= screen_space_specular_transmission_pipeline_key( - camera_3d.screen_space_specular_transmission_quality, - ); - } - let rangefinder = view.rangefinder3d(); - for (render_entity, visible_entity) in visible_entities.iter::() { let Some(material_asset_id) = render_material_instances.get(visible_entity) else { continue; }; @@ -989,15 +756,19 @@ pub fn queue_material_meshes( let mut mesh_pipeline_key_bits = material.properties.mesh_pipeline_key_bits; mesh_pipeline_key_bits.insert(alpha_mode_pipeline_key( material.properties.alpha_mode, - msaa, + match view_key.msaa_samples() { + 1 => &Msaa::Off, + 2 => &Msaa::Sample2, + 4 => &Msaa::Sample4, + 8 => &Msaa::Sample8, + _ => unreachable!("Unsupported MSAA sample count"), + }, )); - let mut mesh_key = view_key + let mut mesh_key = *view_key | MeshPipelineKey::from_bits_retain(mesh.key_bits.bits()) | mesh_pipeline_key_bits; - let mut lightmap_slab = None; if let Some(lightmap) = render_lightmaps.render_lightmaps.get(visible_entity) { - lightmap_slab = Some(*lightmap.slab_index); mesh_key |= MeshPipelineKey::LIGHTMAPPED; if lightmap.bicubic_sampling { @@ -1009,7 +780,7 @@ pub fn queue_material_meshes( mesh_key |= MeshPipelineKey::VISIBILITY_RANGE_DITHER; } - if motion_vector_prepass { + if view_key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) { // If the previous frame have skins or morph targets, note that. if mesh_instance .flags @@ -1036,7 +807,6 @@ pub fn queue_material_meshes( }, &mesh.layout, ); - tx.send(visible_entity.entity(), main_view_entity.entity()); let pipeline_id = match pipeline_id { Ok(id) => id, Err(err) => { @@ -1045,90 +815,133 @@ pub fn queue_material_meshes( } }; + specialized_piplines.insert_specialized_material_pipeline( + *view_entity, + *visible_entity, + pipeline_id, + ); + } + } +} + +/// For each view, iterates over all the meshes visible from that view and adds +/// them to [`BinnedRenderPhase`]s or [`SortedRenderPhase`]s as appropriate. +pub fn queue_material_meshes( + render_materials: Res>>, + render_mesh_instances: Res, + render_material_instances: Res>, + mesh_allocator: Res, + gpu_preprocessing_support: Res, + mut opaque_render_phases: ResMut>, + mut alpha_mask_render_phases: ResMut>, + mut transmissive_render_phases: ResMut>, + mut transparent_render_phases: ResMut>, + views: Query<(&MainEntity, &ExtractedView, &RenderVisibleEntities)>, + specialized_pipelines: SpecializedPipelines>, +) where + M::Data: PartialEq + Eq + Hash + Clone, +{ + for (view_entity, view, visible_entities) in &views { + let ( + Some(opaque_phase), + Some(alpha_mask_phase), + Some(transmissive_phase), + Some(transparent_phase), + ) = ( + opaque_render_phases.get_mut(&view.retained_view_entity), + alpha_mask_render_phases.get_mut(&view.retained_view_entity), + transmissive_render_phases.get_mut(&view.retained_view_entity), + transparent_render_phases.get_mut(&view.retained_view_entity), + ) + else { + continue; + }; + + let rangefinder = view.rangefinder3d(); + for (render_entity, visible_entity) in visible_entities.iter::() { + let Some(pipeline_id) = + specialized_pipelines.get_pipeline(*view_entity, *visible_entity) + else { + continue; + }; + let Some(material_asset_id) = render_material_instances.get(visible_entity) else { + continue; + }; + let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity) + else { + continue; + }; + let Some(material) = render_materials.get(*material_asset_id) else { + continue; + }; + // Fetch the slabs that this mesh resides in. let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id); - match mesh_key - .intersection(MeshPipelineKey::BLEND_RESERVED_BITS | MeshPipelineKey::MAY_DISCARD) - { - MeshPipelineKey::BLEND_OPAQUE | MeshPipelineKey::BLEND_ALPHA_TO_COVERAGE => { - if material.properties.reads_view_transmission_texture { - let distance = rangefinder.distance_translation(&mesh_instance.translation) - + material.properties.depth_bias; - transmissive_phase.add(Transmissive3d { - entity: (*render_entity, *visible_entity), - draw_function: draw_transmissive_pbr, - pipeline: pipeline_id, - distance, - batch_range: 0..1, - extra_index: PhaseItemExtraIndex::None, - indexed: index_slab.is_some(), - }); - } else if material.properties.render_method == OpaqueRendererMethod::Forward { - let batch_set_key = Opaque3dBatchSetKey { - pipeline: pipeline_id, - draw_function: draw_opaque_pbr, - material_bind_group_index: Some(material.binding.group.0), - vertex_slab: vertex_slab.unwrap_or_default(), - index_slab, - lightmap_slab, - }; - let bin_key = Opaque3dBinKey { - asset_id: mesh_instance.mesh_asset_id.into(), - }; - opaque_phase.add( - batch_set_key, - bin_key, - (*render_entity, *visible_entity), - BinnedRenderPhaseType::mesh( - mesh_instance.should_batch(), - &gpu_preprocessing_support, - ), - ); - } + match material.properties.render_phase_type { + RenderPhaseType::Transmissive => { + let distance = rangefinder.distance_translation(&mesh_instance.translation) + + material.properties.depth_bias; + transmissive_phase.add(Transmissive3d { + entity: (*render_entity, *visible_entity), + draw_function: material.properties.draw_function_id, + pipeline: pipeline_id, + distance, + batch_range: 0..1, + extra_index: PhaseItemExtraIndex::None, + indexed: index_slab.is_some(), + }); + } + RenderPhaseType::Opaque => { + let batch_set_key = Opaque3dBatchSetKey { + pipeline: pipeline_id, + draw_function: material.properties.draw_function_id, + material_bind_group_index: Some(material.binding.group.0), + vertex_slab: vertex_slab.unwrap_or_default(), + index_slab, + lightmap_slab: mesh_instance.shared.lightmap_slab_index.map(|index| *index), + }; + let bin_key = Opaque3dBinKey { + asset_id: mesh_instance.mesh_asset_id.into(), + }; + opaque_phase.add( + batch_set_key, + bin_key, + (*render_entity, *visible_entity), + BinnedRenderPhaseType::mesh( + mesh_instance.should_batch(), + &gpu_preprocessing_support, + ), + ); } // Alpha mask - MeshPipelineKey::MAY_DISCARD => { - if material.properties.reads_view_transmission_texture { - let distance = rangefinder.distance_translation(&mesh_instance.translation) - + material.properties.depth_bias; - transmissive_phase.add(Transmissive3d { - entity: (*render_entity, *visible_entity), - draw_function: draw_transmissive_pbr, - pipeline: pipeline_id, - distance, - batch_range: 0..1, - extra_index: PhaseItemExtraIndex::None, - indexed: index_slab.is_some(), - }); - } else if material.properties.render_method == OpaqueRendererMethod::Forward { - let batch_set_key = OpaqueNoLightmap3dBatchSetKey { - draw_function: draw_alpha_mask_pbr, - pipeline: pipeline_id, - material_bind_group_index: Some(material.binding.group.0), - vertex_slab: vertex_slab.unwrap_or_default(), - index_slab, - }; - let bin_key = OpaqueNoLightmap3dBinKey { - asset_id: mesh_instance.mesh_asset_id.into(), - }; - alpha_mask_phase.add( - batch_set_key, - bin_key, - (*render_entity, *visible_entity), - BinnedRenderPhaseType::mesh( - mesh_instance.should_batch(), - &gpu_preprocessing_support, - ), - ); - } + RenderPhaseType::AlphaMask => { + let batch_set_key = OpaqueNoLightmap3dBatchSetKey { + draw_function: material.properties.draw_function_id, + pipeline: pipeline_id, + material_bind_group_index: Some(material.binding.group.0), + vertex_slab: vertex_slab.unwrap_or_default(), + index_slab, + }; + let bin_key = OpaqueNoLightmap3dBinKey { + asset_id: mesh_instance.mesh_asset_id.into(), + }; + alpha_mask_phase.add( + batch_set_key, + bin_key, + (*render_entity, *visible_entity), + BinnedRenderPhaseType::mesh( + mesh_instance.should_batch(), + &gpu_preprocessing_support, + ), + ); } - _ => { + RenderPhaseType::Transparent => { let distance = rangefinder.distance_translation(&mesh_instance.translation) + material.properties.depth_bias; transparent_phase.add(Transparent3d { entity: (*render_entity, *visible_entity), - draw_function: draw_transparent_pbr, + draw_function: material.properties.draw_function_id, pipeline: pipeline_id, distance, batch_range: 0..1, @@ -1211,6 +1024,18 @@ pub struct MaterialProperties { /// This allows taking color output from the [`Opaque3d`] pass as an input, (for screen-space transmission) but requires /// rendering to take place in a separate [`Transmissive3d`] pass. pub reads_view_transmission_texture: bool, + pub render_phase_type: RenderPhaseType, + pub draw_function_id: DrawFunctionId, + pub prepass_draw_function_id: Option, + pub deferred_draw_function_id: Option, +} + +#[derive(Clone, Copy)] +pub enum RenderPhaseType { + Opaque, + AlphaMask, + Transmissive, + Transparent, } /// A resource that maps each untyped material ID to its binding. @@ -1237,6 +1062,14 @@ impl RenderAsset for PreparedMaterial { SRes, SResMut>, SResMut, + SRes>, + SRes>, + SRes>, + SRes>, + SRes>, + SRes>, + SRes>, + SRes>, M::Param, ); @@ -1249,6 +1082,14 @@ impl RenderAsset for PreparedMaterial { default_opaque_render_method, ref mut bind_group_allocator, ref mut render_material_bindings, + opaque_draw_functions, + alpha_mask_draw_functions, + transmissive_draw_functions, + transparent_draw_functions, + opaque_prepass_draw_functions, + alpha_mask_prepass_draw_functions, + opaque_deferred_draw_functions, + alpha_mask_deferred_draw_functions, ref mut material_param, ): &mut SystemParamItem, ) -> Result> { @@ -1257,17 +1098,83 @@ impl RenderAsset for PreparedMaterial { .entry(material_id.into()) .or_insert_with(|| bind_group_allocator.allocate()); - let method = match material.opaque_render_method() { + 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::>(); + let draw_transparent_pbr = transparent_draw_functions.read().id::>(); + let draw_opaque_prepass = opaque_prepass_draw_functions.read().id::>(); + let draw_alpha_mask_prepass = alpha_mask_prepass_draw_functions + .read() + .id::>(); + let draw_opaque_deferred = opaque_deferred_draw_functions.read().id::>(); + let draw_alpha_mask_deferred = alpha_mask_deferred_draw_functions + .read() + .id::>(); + + let render_method = match material.opaque_render_method() { OpaqueRendererMethod::Forward => OpaqueRendererMethod::Forward, OpaqueRendererMethod::Deferred => OpaqueRendererMethod::Deferred, OpaqueRendererMethod::Auto => default_opaque_render_method.0, }; + let mut mesh_pipeline_key_bits = MeshPipelineKey::empty(); mesh_pipeline_key_bits.set( MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE, material.reads_view_transmission_texture(), ); + let reads_view_transmission_texture = + mesh_pipeline_key_bits.contains(MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE); + + let forward = match render_method { + OpaqueRendererMethod::Forward => true, + OpaqueRendererMethod::Deferred => false, + OpaqueRendererMethod::Auto => unreachable!(), + }; + + let render_phase_type = match material.alpha_mode() { + AlphaMode::Opaque => { + if reads_view_transmission_texture { + RenderPhaseType::Transmissive + } else if forward { + RenderPhaseType::Opaque + } else { + panic!("Invalid opaque configuration"); + } + } + AlphaMode::Mask(_) => { + if reads_view_transmission_texture { + RenderPhaseType::Transmissive + } else if forward { + RenderPhaseType::AlphaMask + } else { + panic!("Invalid alpha mask configuration"); + } + } + AlphaMode::Blend + | AlphaMode::Premultiplied + | AlphaMode::Add + | AlphaMode::Multiply + | AlphaMode::AlphaToCoverage => RenderPhaseType::Transparent, + }; + + let draw_function_id = match render_phase_type { + RenderPhaseType::Opaque => draw_opaque_pbr, + RenderPhaseType::AlphaMask => draw_alpha_mask_pbr, + RenderPhaseType::Transmissive => draw_transmissive_pbr, + RenderPhaseType::Transparent => draw_transparent_pbr, + }; + let prepass_draw_function_id = match render_phase_type { + RenderPhaseType::Opaque => Some(draw_opaque_prepass), + RenderPhaseType::AlphaMask => Some(draw_alpha_mask_prepass), + _ => None, + }; + let deferred_draw_function_id = match render_phase_type { + RenderPhaseType::Opaque => Some(draw_opaque_deferred), + RenderPhaseType::AlphaMask => Some(draw_alpha_mask_deferred), + _ => None, + }; + match material.unprepared_bind_group( &pipeline.material_layout, render_device, @@ -1284,8 +1191,12 @@ impl RenderAsset for PreparedMaterial { depth_bias: material.depth_bias(), reads_view_transmission_texture: mesh_pipeline_key_bits .contains(MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE), - render_method: method, + render_phase_type, + draw_function_id, + prepass_draw_function_id, + render_method, mesh_pipeline_key_bits, + deferred_draw_function_id, }, phantom: PhantomData, }) @@ -1320,8 +1231,12 @@ impl RenderAsset for PreparedMaterial { depth_bias: material.depth_bias(), reads_view_transmission_texture: mesh_pipeline_key_bits .contains(MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE), - render_method: method, + render_phase_type, + draw_function_id, + prepass_draw_function_id, + render_method, mesh_pipeline_key_bits, + deferred_draw_function_id, }, phantom: PhantomData, }) @@ -1347,7 +1262,7 @@ impl RenderAsset for PreparedMaterial { _, ref mut bind_group_allocator, ref mut render_material_bindings, - _, + .., ): &mut SystemParamItem, ) { let Some(material_binding_id) = render_material_bindings.remove(&source_asset.untyped()) diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 33ccbcc9afb03..a124cc2776671 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -50,6 +50,9 @@ use material_bind_groups::MaterialBindingId; use render::skin::{self, SkinIndex}; use tracing::{error, warn}; +use self::irradiance_volume::IRRADIANCE_VOLUMES_ARE_USABLE; +use crate::environment_map::EnvironmentMapLight; +use crate::irradiance_volume::IrradianceVolume; use crate::{ render::{ morph::{ @@ -60,14 +63,23 @@ use crate::{ }, *, }; +use bevy_asset::prelude::AssetChanged; +use bevy_core_pipeline::core_3d::Camera3d; +use bevy_core_pipeline::oit::OrderIndependentTransparencySettings; +use bevy_core_pipeline::prepass::{DeferredPrepass, DepthPrepass, NormalPrepass}; +use bevy_core_pipeline::tonemapping::{DebandDither, Tonemapping}; +use bevy_ecs::query::QueryItem; +use bevy_render::camera::TemporalJitter; +use bevy_render::prelude::Msaa; +use bevy_render::specialization::view::{SpecializeViewKey, SpecializeViewsPlugin}; +use bevy_render::specialization::NeedsSpecialization; use bevy_render::sync_world::{MainEntity, MainEntityHashMap}; +use bevy_render::view::ExtractedView; use bytemuck::{Pod, Zeroable}; use nonmax::{NonMaxU16, NonMaxU32}; use smallvec::{smallvec, SmallVec}; use static_assertions::const_assert_eq; -use self::irradiance_volume::IRRADIANCE_VOLUMES_ARE_USABLE; - /// Provides support for rendering 3D meshes. #[derive(Default)] pub struct MeshRenderPlugin { @@ -204,6 +216,7 @@ impl Plugin for MeshRenderPlugin { if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app + .add_plugins(SpecializeViewsPlugin::::default()) .init_resource::() .init_resource::(); @@ -283,6 +296,128 @@ impl Plugin for MeshRenderPlugin { } } +impl SpecializeViewKey for MeshPipelineKey { + type QueryData = ( + Read, + Read, + Option>, + Option>, + Option>, + Has, + ( + Has, + Has, + Has, + Has, + ), + Option>, + Has, + Option>, + Has, + ( + Has>, + Has>, + ), + Has, + ); + + fn get_view_key( + ( + view, + msaa, + tonemapping, + dither, + shadow_filter_method, + ssao, + (normal_prepass, depth_prepass, motion_vector_prepass, deferred_prepass), + camera_3d, + temporal_jitter, + projection, + distance_fog, + (has_environment_maps, has_irradiance_volumes), + has_oit, + ): QueryItem, + ) -> Self { + let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples()) + | MeshPipelineKey::from_hdr(view.hdr); + + if normal_prepass { + view_key |= MeshPipelineKey::NORMAL_PREPASS; + } + + if depth_prepass { + view_key |= MeshPipelineKey::DEPTH_PREPASS; + } + + if motion_vector_prepass { + view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS; + } + + if deferred_prepass { + view_key |= MeshPipelineKey::DEFERRED_PREPASS; + } + + if temporal_jitter { + view_key |= MeshPipelineKey::TEMPORAL_JITTER; + } + + if has_environment_maps { + view_key |= MeshPipelineKey::ENVIRONMENT_MAP; + } + + if has_irradiance_volumes { + view_key |= MeshPipelineKey::IRRADIANCE_VOLUME; + } + + if has_oit { + view_key |= MeshPipelineKey::OIT_ENABLED; + } + + if let Some(projection) = projection { + view_key |= match projection { + Projection::Perspective(_) => MeshPipelineKey::VIEW_PROJECTION_PERSPECTIVE, + Projection::Orthographic(_) => MeshPipelineKey::VIEW_PROJECTION_ORTHOGRAPHIC, + Projection::Custom(_) => MeshPipelineKey::VIEW_PROJECTION_NONSTANDARD, + }; + } + + match shadow_filter_method.unwrap_or(&ShadowFilteringMethod::default()) { + ShadowFilteringMethod::Hardware2x2 => { + view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_HARDWARE_2X2; + } + ShadowFilteringMethod::Gaussian => { + view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_GAUSSIAN; + } + ShadowFilteringMethod::Temporal => { + view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_TEMPORAL; + } + } + + if !view.hdr { + if let Some(tonemapping) = tonemapping { + view_key |= MeshPipelineKey::TONEMAP_IN_SHADER; + view_key |= tonemapping_pipeline_key(*tonemapping); + } + if let Some(DebandDither::Enabled) = dither { + view_key |= MeshPipelineKey::DEBAND_DITHER; + } + } + if ssao { + view_key |= MeshPipelineKey::SCREEN_SPACE_AMBIENT_OCCLUSION; + } + if distance_fog { + view_key |= MeshPipelineKey::DISTANCE_FOG; + } + if let Some(camera_3d) = camera_3d { + view_key |= screen_space_specular_transmission_pipeline_key( + camera_3d.screen_space_specular_transmission_quality, + ); + } + + view_key + } +} + #[derive(Component)] pub struct MeshTransforms { pub world_from_local: Affine3, @@ -568,6 +703,9 @@ pub struct RenderMeshInstanceShared { pub material_bindings_index: MaterialBindingId, /// Various flags. pub flags: RenderMeshInstanceFlags, + /// Index of the slab that the lightmap resides in, if a lightmap is + /// present. + pub lightmap_slab_index: Option, } /// Information that is gathered during the parallel portion of mesh extraction @@ -666,6 +804,7 @@ impl RenderMeshInstanceShared { flags: mesh_instance_flags, // This gets filled in later, during `RenderMeshGpuBuilder::update`. material_bindings_index: default(), + lightmap_slab_index: None, } } @@ -974,6 +1113,11 @@ impl RenderMeshInstanceGpuBuilder { Some(render_lightmap) => u16::from(*render_lightmap.slot_index), None => u16::MAX, }; + let lightmap_slab_index = render_lightmaps + .render_lightmaps + .get(&entity) + .map(|lightmap| lightmap.slab_index); + self.shared.lightmap_slab_index = lightmap_slab_index; // Create the mesh input uniform. let mut mesh_input_uniform = MeshInputUniform { diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index 45d02a0e3b5d9..ae6d4824d0a1a 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -3,7 +3,6 @@ reason = "The parent module contains all things viewport-related, while this module handles cameras as a component. However, a rename/refactor which should clear up this lint is being discussed; see #17196." )] use super::{ClearColorConfig, Projection}; -use crate::specialization::{EntitiesToSpecialize, RenderEntitiesToSpecialize}; use crate::sync_world::MainEntity; use crate::{ batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport}, @@ -299,7 +298,6 @@ pub enum ViewportConversionError { Frustum, CameraMainTextureUsages, VisibleEntities, - EntitiesToSpecialize, Transform, Visibility, Msaa, @@ -1056,7 +1054,6 @@ pub fn extract_cameras( &CameraRenderGraph, &GlobalTransform, &VisibleEntities, - &EntitiesToSpecialize, &Frustum, Option<&ColorGrading>, Option<&Exposure>, @@ -1078,7 +1075,6 @@ pub fn extract_cameras( camera_render_graph, transform, visible_entities, - entities_to_specialize, frustum, color_grading, exposure, @@ -1141,17 +1137,6 @@ pub fn extract_cameras( .collect(), }; - let entities_to_specialize = RenderEntitiesToSpecialize { - entities: entities_to_specialize - .entities - .iter() - .map(|(type_id, entities)| { - let entities = entities.iter().copied().map(MainEntity::from).collect(); - (*type_id, entities) - }) - .collect(), - }; - let mut commands = commands.entity(render_entity); commands.insert(( ExtractedCamera { @@ -1186,7 +1171,6 @@ pub fn extract_cameras( color_grading, }, render_visible_entities, - entities_to_specialize, *frustum, )); diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 0cbcd452e7a4c..0a39fb6393dad 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -103,7 +103,6 @@ use bevy_ecs::{prelude::*, schedule::ScheduleLabel}; use core::ops::{Deref, DerefMut}; use std::sync::Mutex; use tracing::debug; -use crate::specialization::SpecializationPlugin; /// Contains the default Bevy rendering backend based on wgpu. /// @@ -270,7 +269,6 @@ impl Plugin for RenderPlugin { /// Initializes the renderer, sets up the [`RenderSet`] and creates the rendering sub-app. fn build(&self, app: &mut App) { app - .add_plugins(SpecializationPlugin) .init_asset::() .init_asset_loader::(); diff --git a/crates/bevy_render/src/specialization/mod.rs b/crates/bevy_render/src/specialization/mod.rs index 185b74682cc36..f8d8dbd424829 100644 --- a/crates/bevy_render/src/specialization/mod.rs +++ b/crates/bevy_render/src/specialization/mod.rs @@ -1,48 +1,24 @@ +pub mod view; + use crate::extract_resource::ExtractResource; -use crate::sync_world::{MainEntity, MainEntityHashSet}; -use crate::view::VisibilitySystems::CheckVisibility; -use crate::view::VisibleEntities; -use crate::RenderApp; -use bevy_app::{App, Plugin, PostUpdate}; -use bevy_derive::{Deref, DerefMut}; +use crate::render_resource::CachedRenderPipelineId; +use crate::specialization::view::{SpecializeViewKey, ViewSpecializationTicks}; +use crate::sync_world::{MainEntity, MainEntityHashMap}; +use crate::{Extract, ExtractSchedule, RenderApp}; +use bevy_app::{App, Plugin}; use bevy_ecs::component::{Component, Tick}; -use bevy_ecs::entity::hash_map::EntityHashMap; -use bevy_ecs::entity::hash_set::EntityHashSet; -use bevy_ecs::entity::{Entity, EntityHash}; +use bevy_ecs::entity::{Entity, EntityBorrow, EntityHash}; use bevy_ecs::prelude::SystemSet; -use bevy_ecs::query::{QueryFilter, QueryItem, ReadOnlyQueryData}; +use bevy_ecs::query::{QueryFilter, ROQueryItem, ReadOnlyQueryData}; use bevy_ecs::resource::Resource; use bevy_ecs::schedule::IntoSystemConfigs; use bevy_ecs::schedule::IntoSystemSetConfigs; -use bevy_ecs::system::{Commands, Local, Query, Res, ResMut, SystemChangeTick}; -use bevy_platform_support::collections::{HashMap, HashSet}; -use bevy_reflect::prelude::ReflectDefault; +use bevy_ecs::system::{Local, Query, Res, ResMut, SystemChangeTick, SystemParam}; +use bevy_platform_support::collections::HashMap; use bevy_reflect::Reflect; -use bevy_utils::{Parallel, TypeIdMap}; +use bevy_utils::Parallel; use core::marker::PhantomData; -use crossbeam_channel::{Receiver, Sender}; -use std::any::TypeId; - -pub struct SpecializationPlugin; - -impl Plugin for SpecializationPlugin { - fn build(&self, app: &mut App) { - use SpecializationSystems::*; - - app.configure_sets( - PostUpdate, - ( - UpdateLastSpecialized.after(CheckVisibility), - CheckSpecialization.after(UpdateLastSpecialized), - MarkEntitiesToSpecialize.after(CheckSpecialization), - ), - ) - .register_type::() - .init_resource::() - .register_type::() - .init_resource::(); - } -} +use std::ops::{Deref, DerefMut}; pub struct CheckSpecializationPlugin(PhantomData); @@ -54,243 +30,158 @@ impl Default for CheckSpecializationPlugin { impl Plugin for CheckSpecializationPlugin where - M: CheckSpecialization, + M: NeedsSpecialization, { - fn build(&self, app: &mut App) { - use SpecializationSystems::*; - app.init_resource::>().add_systems( - PostUpdate, - ( - update_last_specialized::.in_set(UpdateLastSpecialized), - ( - check_entities_needs_specialization::, - check_views_need_specialization::, - ) - .in_set(CheckSpecialization), - mark_entities_for_specialization::.in_set(MarkEntitiesToSpecialize), - ), - ); - } + fn build(&self, _app: &mut App) {} fn finish(&self, app: &mut App) { - let (tx, rx) = crossbeam_channel::unbounded(); - app.insert_resource(EntitySpecializedReceiver::::new(rx)); - if let Some(render_app) = app.get_sub_app_mut(RenderApp) { - render_app.insert_resource(EntitySpecializedSender::::new(tx)); - } - } -} - -#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] -enum SpecializationSystems { - UpdateLastSpecialized, - CheckSpecialization, - MarkEntitiesToSpecialize, -} - -#[derive(Clone, Resource, Default, Debug, Reflect)] -#[reflect(Default)] -pub struct EntitySpecializationTicks { - #[reflect(ignore)] - pub entities: TypeIdMap>, -} - -#[derive(Clone, Resource, Default, Debug, Reflect)] -#[reflect(Default)] -pub struct LastSpecializedTicks { - // (entity, view_entity) -> tick - #[reflect(ignore)] - pub entity_views: TypeIdMap>, -} - -#[derive(Clone, Component, Default, Debug, Reflect)] -#[reflect(Default)] -pub struct EntitiesToSpecialize { - #[reflect(ignore)] - pub entities: TypeIdMap>, -} - -#[derive(Clone, Component, Default, Debug, Reflect)] -#[reflect(Default)] -pub struct RenderEntitiesToSpecialize { - #[reflect(ignore)] - pub entities: TypeIdMap>, -} - -#[derive(Resource)] -pub struct EntitySpecializedSender { - tx: Sender<(Entity, Entity)>, - _marker: PhantomData, -} - -impl EntitySpecializedSender { - pub fn new(tx: Sender<(Entity, Entity)>) -> Self { - Self { - tx, - _marker: Default::default(), + render_app + .add_systems(ExtractSchedule, extract_entities_needs_specialization::) + .init_resource::>() + .init_resource::>(); } } - - pub fn send(&self, entity: Entity, view_entity: Entity) { - self.tx.send((entity, view_entity)).unwrap(); - } } -#[derive(Resource)] -pub struct EntitySpecializedReceiver { - rx: Receiver<(Entity, Entity)>, +#[derive(Clone, Resource, Debug)] +pub struct EntitySpecializationTicks { + pub entities: MainEntityHashMap, _marker: PhantomData, } -impl EntitySpecializedReceiver { - pub fn new(rx: Receiver<(Entity, Entity)>) -> Self { +impl Default for EntitySpecializationTicks { + fn default() -> Self { Self { - rx, + entities: MainEntityHashMap::default(), _marker: Default::default(), } } - - pub fn recv(&self) -> Option<(Entity, Entity)> { - self.rx.try_recv().ok() - } } -pub fn update_last_specialized( - mut last_specialized_ticks: ResMut, - entity_specialized_receiver: Res>, - ticks: SystemChangeTick, -) where - M: CheckSpecialization, +#[derive(SystemParam)] +pub struct SpecializedPipelines<'w, M> +where + M: NeedsSpecialization, { - while let Some((entity, view_entity)) = entity_specialized_receiver.recv() { - let last_specialized_ticks = last_specialized_ticks - .entity_views - .entry(TypeId::of::()) - .or_default(); - last_specialized_ticks.insert((entity, view_entity), ticks.this_run()); - } + entity_specialization_ticks: Res<'w, EntitySpecializationTicks>, + view_specialization_ticks: Res<'w, ViewSpecializationTicks<::ViewKey>>, + specialized_material_pipeline_cache: ResMut<'w, SpecializedMaterialPipelineCache>, + ticks: SystemChangeTick, } -/// Marks entities that need specialization in the render world. -pub fn mark_entities_for_specialization( - entity_specialization_ticks: Res, - last_specialized_ticks: Res, - mut views: Query<(Entity, &VisibleEntities, &mut EntitiesToSpecialize)>, - ticks: SystemChangeTick, -) where - M: CheckSpecialization, +impl SpecializedPipelines<'_, M> +where + M: NeedsSpecialization, { - for (view_entity, visible_entities, mut entities_to_specialize) in views.iter_mut() { - let entities_to_specialize = entities_to_specialize + pub fn needs_specialization(&self, view_entity: MainEntity, entity: MainEntity) -> bool { + let view_tick = self + .view_specialization_ticks + .entities + .get(&view_entity) + .expect("View entity not found in specialization ticks"); + let entity_tick = self + .entity_specialization_ticks .entities - .entry(TypeId::of::()) - .or_default(); - entities_to_specialize.clear(); + .get(&entity) + .expect("Entity not found in specialization ticks"); + let Some((last_specialized_tick, _)) = self + .specialized_material_pipeline_cache + .get(&(view_entity, entity)) + else { + return true; + }; + + view_tick.is_newer_than(*last_specialized_tick, self.ticks.this_run()) + || entity_tick.is_newer_than(*last_specialized_tick, self.ticks.this_run()) + } - for entity in visible_entities.iter(TypeId::of::()) { - let entity_specialization_ticks = entity_specialization_ticks - .entities - .get(&TypeId::of::()) - .unwrap(); - let last_specialized_ticks = last_specialized_ticks - .entity_views - .get(&TypeId::of::()); - let last_specialized_tick = - last_specialized_ticks.and_then(|ticks| ticks.get(&(*entity, view_entity))); - if last_specialized_tick.is_none() - || entity_specialization_ticks - .get(entity) - .unwrap() - .is_newer_than(*last_specialized_tick.unwrap(), ticks.this_run()) - || entity_specialization_ticks - .get(&view_entity) - .unwrap() - .is_newer_than(*last_specialized_tick.unwrap(), ticks.this_run()) - { - println!("Marking entity for specialization: {:?}", entity); - entities_to_specialize.push(*entity); - } - } + pub fn insert_specialized_material_pipeline( + &mut self, + view_entity: MainEntity, + entity: MainEntity, + pipeline_id: CachedRenderPipelineId, + ) { + self.specialized_material_pipeline_cache + .insert((view_entity, entity), (self.ticks.this_run(), pipeline_id)); + } + + pub fn get_pipeline( + &self, + view_entity: MainEntity, + entity: MainEntity, + ) -> Option { + self.specialized_material_pipeline_cache + .get(&(view_entity, entity)) + .map(|(_, pipeline_id)| *pipeline_id) } } -pub fn check_entities_needs_specialization( - mut thread_queues: Local>>, - mut needs_specialization: Query<(Entity,), M::EntityQueryFilter>, - mut entity_specialization_ticks: ResMut, +pub fn extract_entities_needs_specialization( + mut thread_queues: Local>>, + mut needs_specialization: Extract>, + mut entity_specialization_ticks: ResMut>, ticks: SystemChangeTick, ) where - M: CheckSpecialization, + M: NeedsSpecialization, { needs_specialization.par_iter_mut().for_each_init( || thread_queues.borrow_local_mut(), - |queue, query_item| { - let (entity,) = query_item; - queue.push(entity); + |queue, (entity, item)| { + if M::needs_specialization(item) { + println!("Entity {:?} needs specialization", entity); + queue.push(entity.into()); + } }, ); - let mut entity_specialization_ticks = entity_specialization_ticks - .entities - .entry(TypeId::of::()) - .or_default(); let size = thread_queues.iter_mut().map(|queue| queue.len()).sum(); - entity_specialization_ticks.reserve(size); + entity_specialization_ticks.entities.reserve(size); let this_run = ticks.this_run(); for queue in thread_queues.iter_mut() { for entity in queue.drain(..) { // Update the entity's specialization tick with this run's tick - entity_specialization_ticks.insert(entity, this_run); + entity_specialization_ticks.entities.insert(entity, this_run); } } } -#[derive(Resource, Deref, DerefMut, ExtractResource, Clone)] -pub struct ViewKeyCache(EntityHashMap) -where - M: CheckSpecialization; -impl Default for ViewKeyCache -where - M: CheckSpecialization, -{ +pub trait NeedsSpecialization: Component { + type ViewKey: SpecializeViewKey; + type QueryData: ReadOnlyQueryData + 'static; + type QueryFilter: QueryFilter + 'static; + + fn needs_specialization( + item: ROQueryItem<'_, Self::QueryData>, + ) -> bool; +} + +#[derive(Resource)] +pub struct SpecializedMaterialPipelineCache { + map: HashMap<(MainEntity, MainEntity), (Tick, CachedRenderPipelineId), EntityHash>, + marker: PhantomData, +} + +impl Default for SpecializedMaterialPipelineCache { fn default() -> Self { - Self(EntityHashMap::default()) + Self { + map: HashMap::default(), + marker: PhantomData, + } } } -pub trait CheckSpecialization: Component { - type ViewKey: PartialEq + Send + Sync + 'static; - type VisibilityClass: Component; - type ViewKeyQueryData: ReadOnlyQueryData + 'static; - type EntityQueryFilter: QueryFilter + 'static; +impl Deref for SpecializedMaterialPipelineCache { + type Target = HashMap<(MainEntity, MainEntity), (Tick, CachedRenderPipelineId), EntityHash>; - fn get_view_key<'w>(view_query: QueryItem<'w, Self::ViewKeyQueryData>) -> Self::ViewKey; + fn deref(&self) -> &Self::Target { + &self.map + } } -pub fn check_views_need_specialization( - mut view_key_cache: ResMut>, - mut view_specialization_ticks: ResMut, - mut views: Query<(Entity, M::ViewKeyQueryData)>, - ticks: SystemChangeTick, -) where - M: CheckSpecialization, -{ - let view_specialization_ticks = view_specialization_ticks - .entities - .entry(TypeId::of::()) - .or_default(); - for (view_entity, view_query) in views.iter_mut() { - let view_key = M::get_view_key(view_query); - if let Some(current_key) = view_key_cache.get_mut(&view_entity) { - if *current_key != view_key { - view_key_cache.insert(view_entity, view_key); - view_specialization_ticks.insert(view_entity, ticks.this_run()); - } - } else { - view_key_cache.insert(view_entity, view_key); - view_specialization_ticks.insert(view_entity, ticks.this_run()); - } +impl DerefMut for SpecializedMaterialPipelineCache { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.map } -} +} \ No newline at end of file diff --git a/crates/bevy_render/src/specialization/view.rs b/crates/bevy_render/src/specialization/view.rs new file mode 100644 index 0000000000000..b9d6caef59929 --- /dev/null +++ b/crates/bevy_render/src/specialization/view.rs @@ -0,0 +1,100 @@ +use crate::Render; +use crate::RenderSet::PrepareAssets; +use bevy_app::{App, Plugin}; +use bevy_derive::{Deref, DerefMut}; +use bevy_ecs::component::Tick; +use bevy_ecs::entity::hash_map::EntityHashMap; +use bevy_ecs::entity::Entity; +use bevy_ecs::prelude::Resource; +use bevy_ecs::query::{QueryItem, ReadOnlyQueryData}; +use bevy_ecs::schedule::IntoSystemConfigs; +use bevy_ecs::system::{Query, ResMut, SystemChangeTick}; +use bevy_render_macros::ExtractResource; +use core::marker::PhantomData; +use crate::sync_world::{MainEntity, MainEntityHashMap}; + +pub struct SpecializeViewsPlugin(PhantomData); + +impl Plugin for SpecializeViewsPlugin + where VK: SpecializeViewKey +{ + fn build(&self, app: &mut App) {} + + fn finish(&self, app: &mut App) { + app.add_systems( + Render, + check_views_need_specialization::.in_set(PrepareAssets), + ) + .init_resource::>() + .init_resource::>(); + } +} + +impl Default for SpecializeViewsPlugin { + fn default() -> Self { + Self(Default::default()) + } +} + +#[derive(Resource, Deref, DerefMut, ExtractResource, Clone)] +pub struct ViewKeyCache(MainEntityHashMap) +where + VK: SpecializeViewKey; + +impl Default for ViewKeyCache +where + VK: SpecializeViewKey, +{ + fn default() -> Self { + Self(MainEntityHashMap::default()) + } +} + +#[derive(Clone, Resource, Debug)] +pub struct ViewSpecializationTicks { + pub entities: MainEntityHashMap, + _marker: PhantomData, +} + +impl Default for ViewSpecializationTicks { + fn default() -> Self { + Self { + entities: MainEntityHashMap::default(), + _marker: PhantomData, + } + } +} + +pub trait SpecializeViewKey: PartialEq + Send + Sync + 'static { + type QueryData: ReadOnlyQueryData + 'static; + + fn get_view_key<'w>(view_query: QueryItem<'w, Self::QueryData>) -> Self; +} + +pub fn check_views_need_specialization( + mut view_key_cache: ResMut>, + mut view_specialization_ticks: ResMut>, + mut views: Query<(&MainEntity, VK::QueryData)>, + ticks: SystemChangeTick, +) where + VK: SpecializeViewKey, +{ + for (view_entity, view_query) in views.iter_mut() { + let view_key = VK::get_view_key(view_query); + if let Some(current_key) = view_key_cache.get_mut(view_entity) { + if *current_key != view_key { + println!("Specialize view: {:?}", view_entity); + view_key_cache.insert(*view_entity, view_key); + view_specialization_ticks + .entities + .insert(*view_entity, ticks.this_run()); + } + } else { + println!("Specialize new view: {:?}", view_entity); + view_key_cache.insert(*view_entity, view_key); + view_specialization_ticks + .entities + .insert(*view_entity, ticks.this_run()); + } + } +} diff --git a/examples/3d/3d_scene.rs b/examples/3d/3d_scene.rs index 5ea7e20b29662..50eac7323fa9b 100644 --- a/examples/3d/3d_scene.rs +++ b/examples/3d/3d_scene.rs @@ -6,6 +6,7 @@ fn main() { App::new() .add_plugins(DefaultPlugins) .add_systems(Startup, setup) + .add_systems(Update, update_meshes) .run(); } @@ -41,3 +42,10 @@ fn setup( Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y), )); } + +fn update_meshes( + mut meshes: ResMut>, + query: Query<(&Mesh3d)>, +) { + +} \ No newline at end of file From a8331cbfdfc7f10c616ba2c0041745e0e8ae46e1 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Sat, 25 Jan 2025 20:25:30 -0800 Subject: [PATCH 03/35] Forward working. --- crates/bevy_pbr/src/material.rs | 2 +- crates/bevy_pbr/src/prepass/mod.rs | 35 +++----------------- crates/bevy_render/src/specialization/mod.rs | 2 +- 3 files changed, 6 insertions(+), 33 deletions(-) diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index a069fbe5a964b..0c7f57094efd2 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -815,7 +815,7 @@ pub fn specialize_material_meshes( } }; - specialized_piplines.insert_specialized_material_pipeline( + specialized_piplines.insert_pipeline( *view_entity, *visible_entity, pipeline_id, diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 3bc2dc51e3591..86937f11d8942 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -772,17 +772,6 @@ pub fn prepare_prepass_view_bind_group( } pub fn queue_prepass_material_meshes( - ( - 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, @@ -815,22 +804,6 @@ pub fn queue_prepass_material_meshes( ) where M::Data: PartialEq + Eq + Hash + Clone, { - let opaque_draw_prepass = opaque_draw_functions - .read() - .get_id::>() - .unwrap(); - let alpha_mask_draw_prepass = alpha_mask_draw_functions - .read() - .get_id::>() - .unwrap(); - let opaque_draw_deferred = opaque_deferred_draw_functions - .read() - .get_id::>() - .unwrap(); - let alpha_mask_draw_deferred = alpha_mask_deferred_draw_functions - .read() - .get_id::>() - .unwrap(); for ( extracted_view, visible_entities, @@ -985,7 +958,7 @@ pub fn queue_prepass_material_meshes( if deferred { opaque_deferred_phase.as_mut().unwrap().add( OpaqueNoLightmap3dBatchSetKey { - draw_function: opaque_draw_deferred, + draw_function: material.properties.deferred_draw_function_id.unwrap(), pipeline: pipeline_id, material_bind_group_index: Some(material.binding.group.0), vertex_slab: vertex_slab.unwrap_or_default(), @@ -1005,7 +978,7 @@ pub fn queue_prepass_material_meshes( mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id); opaque_phase.add( OpaqueNoLightmap3dBatchSetKey { - draw_function: opaque_draw_prepass, + draw_function: material.properties.prepass_draw_function_id.unwrap(), pipeline: pipeline_id, material_bind_group_index: Some(material.binding.group.0), vertex_slab: vertex_slab.unwrap_or_default(), @@ -1028,7 +1001,7 @@ pub fn queue_prepass_material_meshes( let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id); let batch_set_key = OpaqueNoLightmap3dBatchSetKey { - draw_function: alpha_mask_draw_deferred, + draw_function: material.properties.deferred_draw_function_id.unwrap(), pipeline: pipeline_id, material_bind_group_index: Some(material.binding.group.0), vertex_slab: vertex_slab.unwrap_or_default(), @@ -1050,7 +1023,7 @@ pub fn queue_prepass_material_meshes( let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id); let batch_set_key = OpaqueNoLightmap3dBatchSetKey { - draw_function: alpha_mask_draw_prepass, + draw_function: material.properties.prepass_draw_function_id.unwrap(), pipeline: pipeline_id, material_bind_group_index: Some(material.binding.group.0), vertex_slab: vertex_slab.unwrap_or_default(), diff --git a/crates/bevy_render/src/specialization/mod.rs b/crates/bevy_render/src/specialization/mod.rs index f8d8dbd424829..a1200a6e9a637 100644 --- a/crates/bevy_render/src/specialization/mod.rs +++ b/crates/bevy_render/src/specialization/mod.rs @@ -96,7 +96,7 @@ where || entity_tick.is_newer_than(*last_specialized_tick, self.ticks.this_run()) } - pub fn insert_specialized_material_pipeline( + pub fn insert_pipeline( &mut self, view_entity: MainEntity, entity: MainEntity, From 47aa01d8e29535ce8dac4dcea99f5d1528070a81 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Sun, 26 Jan 2025 13:34:47 -0800 Subject: [PATCH 04/35] Simplify extraction. --- crates/bevy_render/src/specialization/mod.rs | 87 +++++++++++++------- 1 file changed, 57 insertions(+), 30 deletions(-) diff --git a/crates/bevy_render/src/specialization/mod.rs b/crates/bevy_render/src/specialization/mod.rs index a1200a6e9a637..9f02045a012d4 100644 --- a/crates/bevy_render/src/specialization/mod.rs +++ b/crates/bevy_render/src/specialization/mod.rs @@ -4,8 +4,10 @@ use crate::extract_resource::ExtractResource; use crate::render_resource::CachedRenderPipelineId; use crate::specialization::view::{SpecializeViewKey, ViewSpecializationTicks}; use crate::sync_world::{MainEntity, MainEntityHashMap}; +use crate::view::VisibilitySystems; use crate::{Extract, ExtractSchedule, RenderApp}; -use bevy_app::{App, Plugin}; +use bevy_app::{App, Last, Plugin, PostUpdate}; +use bevy_asset::AssetEvents; use bevy_ecs::component::{Component, Tick}; use bevy_ecs::entity::{Entity, EntityBorrow, EntityHash}; use bevy_ecs::prelude::SystemSet; @@ -32,14 +34,55 @@ impl Plugin for CheckSpecializationPlugin where M: NeedsSpecialization, { - fn build(&self, _app: &mut App) {} + fn build(&self, app: &mut App) { + app.add_systems( + Last, + check_entities_needing_specialization::.after(AssetEvents), + ) + .init_resource::>(); + } fn finish(&self, app: &mut App) { if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app .add_systems(ExtractSchedule, extract_entities_needs_specialization::) .init_resource::>() - .init_resource::>(); + .init_resource::>(); + } + } +} + +fn check_entities_needing_specialization( + mut thread_queues: Local>>, + mut needs_specialization: Query<(Entity, M::QueryData), M::QueryFilter>, + mut entities_needing_specialization: ResMut>, +) where + M: NeedsSpecialization, +{ + entities_needing_specialization.entities.clear(); + needs_specialization.par_iter_mut().for_each_init( + || thread_queues.borrow_local_mut(), + |queue, (entity, item)| { + if M::needs_specialization(item) { + queue.push(entity.into()); + } + }, + ); + + thread_queues.drain_into(&mut entities_needing_specialization.entities); +} + +#[derive(Clone, Resource, Debug)] +pub struct EntitiesNeedingSpecialization { + pub entities: Vec, + _marker: PhantomData, +} + +impl Default for EntitiesNeedingSpecialization { + fn default() -> Self { + Self { + entities: Default::default(), + _marker: Default::default(), } } } @@ -50,7 +93,7 @@ pub struct EntitySpecializationTicks { _marker: PhantomData, } -impl Default for EntitySpecializationTicks { +impl Default for EntitySpecializationTicks { fn default() -> Self { Self { entities: MainEntityHashMap::default(), @@ -65,7 +108,8 @@ where M: NeedsSpecialization, { entity_specialization_ticks: Res<'w, EntitySpecializationTicks>, - view_specialization_ticks: Res<'w, ViewSpecializationTicks<::ViewKey>>, + view_specialization_ticks: + Res<'w, ViewSpecializationTicks<::ViewKey>>, specialized_material_pipeline_cache: ResMut<'w, SpecializedMaterialPipelineCache>, ticks: SystemChangeTick, } @@ -118,43 +162,26 @@ where } pub fn extract_entities_needs_specialization( - mut thread_queues: Local>>, - mut needs_specialization: Extract>, + mut entities_needing_specialization: Extract>>, mut entity_specialization_ticks: ResMut>, ticks: SystemChangeTick, ) where M: NeedsSpecialization, { - needs_specialization.par_iter_mut().for_each_init( - || thread_queues.borrow_local_mut(), - |queue, (entity, item)| { - if M::needs_specialization(item) { - println!("Entity {:?} needs specialization", entity); - queue.push(entity.into()); - } - }, - ); - - let size = thread_queues.iter_mut().map(|queue| queue.len()).sum(); - entity_specialization_ticks.entities.reserve(size); - let this_run = ticks.this_run(); - for queue in thread_queues.iter_mut() { - for entity in queue.drain(..) { - // Update the entity's specialization tick with this run's tick - entity_specialization_ticks.entities.insert(entity, this_run); - } + for entity in &entities_needing_specialization.entities { + // Update the entity's specialization tick with this run's tick + entity_specialization_ticks + .entities + .insert((*entity).into(), ticks.this_run()); } } - pub trait NeedsSpecialization: Component { type ViewKey: SpecializeViewKey; type QueryData: ReadOnlyQueryData + 'static; type QueryFilter: QueryFilter + 'static; - fn needs_specialization( - item: ROQueryItem<'_, Self::QueryData>, - ) -> bool; + fn needs_specialization(item: ROQueryItem<'_, Self::QueryData>) -> bool; } #[derive(Resource)] @@ -184,4 +211,4 @@ impl DerefMut for SpecializedMaterialPipelineCache { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.map } -} \ No newline at end of file +} From 14fdd1f7618357c09150df2427f3b820ec3e7a10 Mon Sep 17 00:00:00 2001 From: charlotte Date: Sun, 26 Jan 2025 15:51:32 -0800 Subject: [PATCH 05/35] Move asset events to PostUpdate. --- crates/bevy_asset/src/asset_changed.rs | 6 +++--- crates/bevy_asset/src/lib.rs | 4 ++-- crates/bevy_render/src/specialization/mod.rs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/bevy_asset/src/asset_changed.rs b/crates/bevy_asset/src/asset_changed.rs index 04439d9c5ee36..a9b46a29f9f9e 100644 --- a/crates/bevy_asset/src/asset_changed.rs +++ b/crates/bevy_asset/src/asset_changed.rs @@ -293,7 +293,7 @@ mod tests { use std::println; use crate::{AssetApp, Assets}; - use bevy_app::{App, AppExit, Last, Startup, TaskPoolPlugin, Update}; + use bevy_app::{App, AppExit, PostUpdate, Startup, TaskPoolPlugin, Update}; use bevy_ecs::schedule::IntoSystemConfigs; use bevy_ecs::{ component::Component, @@ -410,7 +410,7 @@ mod tests { .init_asset::() .insert_resource(Counter(vec![0, 0, 0, 0])) .add_systems(Update, add_some) - .add_systems(Last, count_update.after(AssetEvents)); + .add_systems(PostUpdate, count_update.after(AssetEvents)); // First run of the app, `add_systems(Startup…)` runs. app.update(); // run_count == 0 @@ -445,7 +445,7 @@ mod tests { }, ) .add_systems(Update, update_some) - .add_systems(Last, count_update.after(AssetEvents)); + .add_systems(PostUpdate, count_update.after(AssetEvents)); // First run of the app, `add_systems(Startup…)` runs. app.update(); // run_count == 0 diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index 85fe7a87ed132..53e029ebf93eb 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -212,7 +212,7 @@ use alloc::{ sync::Arc, vec::Vec, }; -use bevy_app::{App, Last, Plugin, PreUpdate}; +use bevy_app::{App, Last, Plugin, PostUpdate, PreUpdate}; use bevy_ecs::prelude::Component; use bevy_ecs::{ reflect::AppTypeRegistry, @@ -580,7 +580,7 @@ impl AssetApp for App { .add_event::>() .register_type::>() .add_systems( - Last, + PostUpdate, Assets::::asset_events .run_if(Assets::::asset_events_condition) .in_set(AssetEvents), diff --git a/crates/bevy_render/src/specialization/mod.rs b/crates/bevy_render/src/specialization/mod.rs index 9f02045a012d4..981d6b1f2a8fa 100644 --- a/crates/bevy_render/src/specialization/mod.rs +++ b/crates/bevy_render/src/specialization/mod.rs @@ -36,7 +36,7 @@ where { fn build(&self, app: &mut App) { app.add_systems( - Last, + PostUpdate, check_entities_needing_specialization::.after(AssetEvents), ) .init_resource::>(); From 1e06fd0efa5c656b5215ea270224e2dea99177b5 Mon Sep 17 00:00:00 2001 From: charlotte Date: Sun, 26 Jan 2025 17:14:28 -0800 Subject: [PATCH 06/35] Clean-up. --- crates/bevy_pbr/src/material.rs | 47 ++---- crates/bevy_pbr/src/render/mesh.rs | 4 +- crates/bevy_render/src/specialization/mod.rs | 158 ++++++++++-------- crates/bevy_render/src/specialization/view.rs | 12 +- 4 files changed, 113 insertions(+), 108 deletions(-) diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 0c7f57094efd2..5b63f75ecb13a 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -37,7 +37,7 @@ use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::Reflect; use bevy_render::specialization::view::ViewKeyCache; use bevy_render::specialization::{ - CheckSpecializationPlugin, NeedsSpecialization, SpecializedPipelines, + CheckSpecializationPlugin, NeedsSpecialization, SpecializePipelines, }; use bevy_render::{ batching::gpu_preprocessing::GpuPreprocessingSupport, @@ -609,8 +609,13 @@ pub const fn screen_space_specular_transmission_pipeline_key( } } -impl NeedsSpecialization for MeshMaterial3d { +impl NeedsSpecialization for MeshMaterial3d +where + M: Material, + M::Data: PartialEq + Eq + Hash + Clone, +{ type ViewKey = MeshPipelineKey; + type Pipeline = MaterialPipeline; type QueryData = (); type QueryFilter = Or<( Changed, @@ -689,9 +694,6 @@ fn extract_mesh_materials( /// For each view, iterates over all the meshes visible from that view and adds /// them to [`BinnedRenderPhase`]s or [`SortedRenderPhase`]s as appropriate. pub fn specialize_material_meshes( - material_pipeline: Res>, - mut pipelines: ResMut>>, - pipeline_cache: Res, render_meshes: Res>, render_materials: Res>>, render_mesh_instances: Res, @@ -712,7 +714,7 @@ pub fn specialize_material_meshes( ), views: Query<(&MainEntity, &ExtractedView, &RenderVisibleEntities)>, view_key_cache: Res>, - mut specialized_piplines: SpecializedPipelines>, + mut specialized_pipelines: SpecializePipelines>, ) where M::Data: PartialEq + Eq + Hash + Clone, { @@ -730,7 +732,7 @@ pub fn specialize_material_meshes( }; for (_, visible_entity) in visible_entities.iter::() { - if !specialized_piplines.needs_specialization(*view_entity, *visible_entity) { + if !specialized_pipelines.needs_specialization(*view_entity, *visible_entity) { continue; } @@ -796,30 +798,13 @@ pub fn specialize_material_meshes( } } - let pipeline_id = pipelines.specialize( - &pipeline_cache, - &material_pipeline, - MaterialPipelineKey { - mesh_key, - bind_group_data: material_bind_group - .get_extra_data(material.binding.slot) - .clone(), - }, - &mesh.layout, - ); - let pipeline_id = match pipeline_id { - Ok(id) => id, - Err(err) => { - error!("{}", err); - continue; - } + let key = MaterialPipelineKey { + mesh_key, + bind_group_data: material_bind_group + .get_extra_data(material.binding.slot) + .clone(), }; - - specialized_piplines.insert_pipeline( - *view_entity, - *visible_entity, - pipeline_id, - ); + specialized_pipelines.specialize_pipeline((*view_entity, *visible_entity), key, mesh); } } } @@ -837,7 +822,7 @@ pub fn queue_material_meshes( mut transmissive_render_phases: ResMut>, mut transparent_render_phases: ResMut>, views: Query<(&MainEntity, &ExtractedView, &RenderVisibleEntities)>, - specialized_pipelines: SpecializedPipelines>, + specialized_pipelines: SpecializePipelines>, ) where M::Data: PartialEq + Eq + Hash + Clone, { diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index a124cc2776671..7949eee8e2fbd 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -71,7 +71,7 @@ use bevy_core_pipeline::tonemapping::{DebandDither, Tonemapping}; use bevy_ecs::query::QueryItem; use bevy_render::camera::TemporalJitter; use bevy_render::prelude::Msaa; -use bevy_render::specialization::view::{SpecializeViewKey, SpecializeViewsPlugin}; +use bevy_render::specialization::view::{GetViewKey, SpecializeViewsPlugin}; use bevy_render::specialization::NeedsSpecialization; use bevy_render::sync_world::{MainEntity, MainEntityHashMap}; use bevy_render::view::ExtractedView; @@ -296,7 +296,7 @@ impl Plugin for MeshRenderPlugin { } } -impl SpecializeViewKey for MeshPipelineKey { +impl GetViewKey for MeshPipelineKey { type QueryData = ( Read, Read, diff --git a/crates/bevy_render/src/specialization/mod.rs b/crates/bevy_render/src/specialization/mod.rs index 981d6b1f2a8fa..944a3fb38ad99 100644 --- a/crates/bevy_render/src/specialization/mod.rs +++ b/crates/bevy_render/src/specialization/mod.rs @@ -1,12 +1,14 @@ pub mod view; use crate::extract_resource::ExtractResource; -use crate::render_resource::CachedRenderPipelineId; -use crate::specialization::view::{SpecializeViewKey, ViewSpecializationTicks}; +use crate::mesh::RenderMesh; +use crate::render_resource::{ + CachedRenderPipelineId, PipelineCache, SpecializedMeshPipeline, SpecializedMeshPipelines, +}; +use crate::specialization::view::{GetViewKey, ViewSpecializationTicks}; use crate::sync_world::{MainEntity, MainEntityHashMap}; -use crate::view::VisibilitySystems; use crate::{Extract, ExtractSchedule, RenderApp}; -use bevy_app::{App, Last, Plugin, PostUpdate}; +use bevy_app::{App, Plugin, PostUpdate}; use bevy_asset::AssetEvents; use bevy_ecs::component::{Component, Tick}; use bevy_ecs::entity::{Entity, EntityBorrow, EntityHash}; @@ -21,6 +23,7 @@ use bevy_reflect::Reflect; use bevy_utils::Parallel; use core::marker::PhantomData; use std::ops::{Deref, DerefMut}; +use tracing::error; pub struct CheckSpecializationPlugin(PhantomData); @@ -52,71 +55,36 @@ where } } -fn check_entities_needing_specialization( - mut thread_queues: Local>>, - mut needs_specialization: Query<(Entity, M::QueryData), M::QueryFilter>, - mut entities_needing_specialization: ResMut>, -) where - M: NeedsSpecialization, -{ - entities_needing_specialization.entities.clear(); - needs_specialization.par_iter_mut().for_each_init( - || thread_queues.borrow_local_mut(), - |queue, (entity, item)| { - if M::needs_specialization(item) { - queue.push(entity.into()); - } - }, - ); - - thread_queues.drain_into(&mut entities_needing_specialization.entities); -} - -#[derive(Clone, Resource, Debug)] -pub struct EntitiesNeedingSpecialization { - pub entities: Vec, - _marker: PhantomData, -} - -impl Default for EntitiesNeedingSpecialization { - fn default() -> Self { - Self { - entities: Default::default(), - _marker: Default::default(), - } - } -} +pub trait NeedsSpecialization: Component { + type ViewKey: GetViewKey; + type Pipeline: SpecializedMeshPipeline + Resource + Send + Sync + 'static; + type QueryData: ReadOnlyQueryData + 'static; + type QueryFilter: QueryFilter + 'static; -#[derive(Clone, Resource, Debug)] -pub struct EntitySpecializationTicks { - pub entities: MainEntityHashMap, - _marker: PhantomData, + fn needs_specialization(item: ROQueryItem<'_, Self::QueryData>) -> bool; } -impl Default for EntitySpecializationTicks { - fn default() -> Self { - Self { - entities: MainEntityHashMap::default(), - _marker: Default::default(), - } - } -} #[derive(SystemParam)] -pub struct SpecializedPipelines<'w, M> +pub struct SpecializePipelines<'w, M> where M: NeedsSpecialization, + <::Pipeline as SpecializedMeshPipeline>::Key: Send + Sync + 'static { entity_specialization_ticks: Res<'w, EntitySpecializationTicks>, view_specialization_ticks: Res<'w, ViewSpecializationTicks<::ViewKey>>, specialized_material_pipeline_cache: ResMut<'w, SpecializedMaterialPipelineCache>, + pipelines: ResMut<'w, SpecializedMeshPipelines<::Pipeline>>, + pipeline: Res<'w, ::Pipeline>, + pipeline_cache: Res<'w, PipelineCache>, ticks: SystemChangeTick, } -impl SpecializedPipelines<'_, M> +impl SpecializePipelines<'_, M> where M: NeedsSpecialization, + <::Pipeline as SpecializedMeshPipeline>::Key: Send + Sync + 'static { pub fn needs_specialization(&self, view_entity: MainEntity, entity: MainEntity) -> bool { let view_tick = self @@ -139,17 +107,6 @@ where view_tick.is_newer_than(*last_specialized_tick, self.ticks.this_run()) || entity_tick.is_newer_than(*last_specialized_tick, self.ticks.this_run()) } - - pub fn insert_pipeline( - &mut self, - view_entity: MainEntity, - entity: MainEntity, - pipeline_id: CachedRenderPipelineId, - ) { - self.specialized_material_pipeline_cache - .insert((view_entity, entity), (self.ticks.this_run(), pipeline_id)); - } - pub fn get_pipeline( &self, view_entity: MainEntity, @@ -159,6 +116,49 @@ where .get(&(view_entity, entity)) .map(|(_, pipeline_id)| *pipeline_id) } + + pub fn specialize_pipeline( + &mut self, + (view_entity, visible_entity): (MainEntity, MainEntity), + key: <::Pipeline as SpecializedMeshPipeline>::Key, + mesh: &RenderMesh, + ) { + let pipeline_id = + self.pipelines + .specialize(&self.pipeline_cache, &self.pipeline, key, &mesh.layout); + let pipeline_id = match pipeline_id { + Ok(id) => id, + Err(err) => { + error!("{}", err); + return; + } + }; + + self.specialized_material_pipeline_cache.insert( + (view_entity, visible_entity), + (self.ticks.this_run(), pipeline_id), + ); + } +} + +fn check_entities_needing_specialization( + mut thread_queues: Local>>, + mut needs_specialization: Query<(Entity, M::QueryData), M::QueryFilter>, + mut entities_needing_specialization: ResMut>, +) where + M: NeedsSpecialization, +{ + entities_needing_specialization.entities.clear(); + needs_specialization.par_iter_mut().for_each_init( + || thread_queues.borrow_local_mut(), + |queue, (entity, item)| { + if M::needs_specialization(item) { + queue.push(entity.into()); + } + }, + ); + + thread_queues.drain_into(&mut entities_needing_specialization.entities); } pub fn extract_entities_needs_specialization( @@ -176,12 +176,34 @@ pub fn extract_entities_needs_specialization( } } -pub trait NeedsSpecialization: Component { - type ViewKey: SpecializeViewKey; - type QueryData: ReadOnlyQueryData + 'static; - type QueryFilter: QueryFilter + 'static; +#[derive(Clone, Resource, Debug)] +pub struct EntitiesNeedingSpecialization { + pub entities: Vec, + _marker: PhantomData, +} - fn needs_specialization(item: ROQueryItem<'_, Self::QueryData>) -> bool; +impl Default for EntitiesNeedingSpecialization { + fn default() -> Self { + Self { + entities: Default::default(), + _marker: Default::default(), + } + } +} + +#[derive(Clone, Resource, Debug)] +pub struct EntitySpecializationTicks { + pub entities: MainEntityHashMap, + _marker: PhantomData, +} + +impl Default for EntitySpecializationTicks { + fn default() -> Self { + Self { + entities: MainEntityHashMap::default(), + _marker: Default::default(), + } + } } #[derive(Resource)] diff --git a/crates/bevy_render/src/specialization/view.rs b/crates/bevy_render/src/specialization/view.rs index b9d6caef59929..734f4e0457484 100644 --- a/crates/bevy_render/src/specialization/view.rs +++ b/crates/bevy_render/src/specialization/view.rs @@ -16,7 +16,7 @@ use crate::sync_world::{MainEntity, MainEntityHashMap}; pub struct SpecializeViewsPlugin(PhantomData); impl Plugin for SpecializeViewsPlugin - where VK: SpecializeViewKey + where VK: GetViewKey { fn build(&self, app: &mut App) {} @@ -39,11 +39,11 @@ impl Default for SpecializeViewsPlugin { #[derive(Resource, Deref, DerefMut, ExtractResource, Clone)] pub struct ViewKeyCache(MainEntityHashMap) where - VK: SpecializeViewKey; + VK: GetViewKey; impl Default for ViewKeyCache where - VK: SpecializeViewKey, + VK: GetViewKey, { fn default() -> Self { Self(MainEntityHashMap::default()) @@ -65,7 +65,7 @@ impl Default for ViewSpecializationTicks { } } -pub trait SpecializeViewKey: PartialEq + Send + Sync + 'static { +pub trait GetViewKey: PartialEq + Send + Sync + 'static { type QueryData: ReadOnlyQueryData + 'static; fn get_view_key<'w>(view_query: QueryItem<'w, Self::QueryData>) -> Self; @@ -77,20 +77,18 @@ pub fn check_views_need_specialization( mut views: Query<(&MainEntity, VK::QueryData)>, ticks: SystemChangeTick, ) where - VK: SpecializeViewKey, + VK: GetViewKey, { for (view_entity, view_query) in views.iter_mut() { let view_key = VK::get_view_key(view_query); if let Some(current_key) = view_key_cache.get_mut(view_entity) { if *current_key != view_key { - println!("Specialize view: {:?}", view_entity); view_key_cache.insert(*view_entity, view_key); view_specialization_ticks .entities .insert(*view_entity, ticks.this_run()); } } else { - println!("Specialize new view: {:?}", view_entity); view_key_cache.insert(*view_entity, view_key); view_specialization_ticks .entities From a4010b6995083c4051b08cfbb7e04a8aab1033a6 Mon Sep 17 00:00:00 2001 From: charlotte Date: Sun, 26 Jan 2025 18:12:38 -0800 Subject: [PATCH 07/35] Clean-up. --- crates/bevy_pbr/src/material.rs | 4 ++-- crates/bevy_render/src/specialization/mod.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 5b63f75ecb13a..cb40522a51d3c 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -37,7 +37,7 @@ use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::Reflect; use bevy_render::specialization::view::ViewKeyCache; use bevy_render::specialization::{ - CheckSpecializationPlugin, NeedsSpecialization, SpecializePipelines, + MeshMaterialSpecializationPlugin, NeedsSpecialization, SpecializePipelines, }; use bevy_render::{ batching::gpu_preprocessing::GpuPreprocessingSupport, @@ -282,7 +282,7 @@ where .register_type::>() .add_plugins(( RenderAssetPlugin::>::default(), - CheckSpecializationPlugin::>::default(), + MeshMaterialSpecializationPlugin::>::default(), )) .add_systems( PostUpdate, diff --git a/crates/bevy_render/src/specialization/mod.rs b/crates/bevy_render/src/specialization/mod.rs index 944a3fb38ad99..b29e94d28d97f 100644 --- a/crates/bevy_render/src/specialization/mod.rs +++ b/crates/bevy_render/src/specialization/mod.rs @@ -25,15 +25,15 @@ use core::marker::PhantomData; use std::ops::{Deref, DerefMut}; use tracing::error; -pub struct CheckSpecializationPlugin(PhantomData); +pub struct MeshMaterialSpecializationPlugin(PhantomData); -impl Default for CheckSpecializationPlugin { +impl Default for MeshMaterialSpecializationPlugin { fn default() -> Self { Self(Default::default()) } } -impl Plugin for CheckSpecializationPlugin +impl Plugin for MeshMaterialSpecializationPlugin where M: NeedsSpecialization, { From dae15a864eba01e0f9a5c65d5015947e173b10e4 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Sun, 26 Jan 2025 22:01:48 -0800 Subject: [PATCH 08/35] Start deferred. --- crates/bevy_pbr/src/material.rs | 38 +- crates/bevy_pbr/src/prepass/mod.rs | 351 ++++++++++++++---- crates/bevy_render/src/specialization/mod.rs | 2 +- crates/bevy_render/src/specialization/view.rs | 7 +- 4 files changed, 299 insertions(+), 99 deletions(-) diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index cb40522a51d3c..31d5d0293fcbd 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -625,7 +625,7 @@ where )>; fn needs_specialization(_item: ROQueryItem<'_, Self::QueryData>) -> bool { - // Matching the filter is enough to trigger the specialization. + // Matching the filter is enough to trigger specialization. true } } @@ -735,7 +735,6 @@ pub fn specialize_material_meshes( if !specialized_pipelines.needs_specialization(*view_entity, *visible_entity) { continue; } - let Some(material_asset_id) = render_material_instances.get(visible_entity) else { continue; }; @@ -1087,14 +1086,14 @@ impl RenderAsset for PreparedMaterial { let draw_alpha_mask_pbr = alpha_mask_draw_functions.read().id::>(); let draw_transmissive_pbr = transmissive_draw_functions.read().id::>(); let draw_transparent_pbr = transparent_draw_functions.read().id::>(); - let draw_opaque_prepass = opaque_prepass_draw_functions.read().id::>(); + let draw_opaque_prepass = opaque_prepass_draw_functions.read().get_id::>(); let draw_alpha_mask_prepass = alpha_mask_prepass_draw_functions .read() - .id::>(); - let draw_opaque_deferred = opaque_deferred_draw_functions.read().id::>(); + .get_id::>(); + let draw_opaque_deferred = opaque_deferred_draw_functions.read().get_id::>(); let draw_alpha_mask_deferred = alpha_mask_deferred_draw_functions .read() - .id::>(); + .get_id::>(); let render_method = match material.opaque_render_method() { OpaqueRendererMethod::Forward => OpaqueRendererMethod::Forward, @@ -1121,10 +1120,8 @@ impl RenderAsset for PreparedMaterial { AlphaMode::Opaque => { if reads_view_transmission_texture { RenderPhaseType::Transmissive - } else if forward { - RenderPhaseType::Opaque } else { - panic!("Invalid opaque configuration"); + RenderPhaseType::Opaque } } AlphaMode::Mask(_) => { @@ -1136,11 +1133,18 @@ impl RenderAsset for PreparedMaterial { panic!("Invalid alpha mask configuration"); } } + AlphaMode::AlphaToCoverage => { + if !forward { + RenderPhaseType::Opaque + } else { + RenderPhaseType::Transparent + } + } AlphaMode::Blend | AlphaMode::Premultiplied | AlphaMode::Add | AlphaMode::Multiply - | AlphaMode::AlphaToCoverage => RenderPhaseType::Transparent, + => RenderPhaseType::Transparent, }; let draw_function_id = match render_phase_type { @@ -1150,13 +1154,13 @@ impl RenderAsset for PreparedMaterial { RenderPhaseType::Transparent => draw_transparent_pbr, }; let prepass_draw_function_id = match render_phase_type { - RenderPhaseType::Opaque => Some(draw_opaque_prepass), - RenderPhaseType::AlphaMask => Some(draw_alpha_mask_prepass), + RenderPhaseType::Opaque => draw_opaque_prepass, + RenderPhaseType::AlphaMask => draw_alpha_mask_prepass, _ => None, }; let deferred_draw_function_id = match render_phase_type { - RenderPhaseType::Opaque => Some(draw_opaque_deferred), - RenderPhaseType::AlphaMask => Some(draw_alpha_mask_deferred), + RenderPhaseType::Opaque => draw_opaque_deferred, + RenderPhaseType::AlphaMask => draw_alpha_mask_deferred, _ => None, }; @@ -1174,8 +1178,7 @@ impl RenderAsset for PreparedMaterial { properties: MaterialProperties { alpha_mode: material.alpha_mode(), depth_bias: material.depth_bias(), - reads_view_transmission_texture: mesh_pipeline_key_bits - .contains(MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE), + reads_view_transmission_texture, render_phase_type, draw_function_id, prepass_draw_function_id, @@ -1214,8 +1217,7 @@ impl RenderAsset for PreparedMaterial { properties: MaterialProperties { alpha_mode: material.alpha_mode(), depth_bias: material.depth_bias(), - reads_view_transmission_texture: mesh_pipeline_key_bits - .contains(MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE), + reads_view_transmission_texture, render_phase_type, draw_function_id, prepass_draw_function_id, diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 86937f11d8942..2b772e018e89d 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -22,7 +22,7 @@ use bevy_render::{ }; pub use prepass_bindings::*; -use bevy_asset::{load_internal_asset, AssetServer, Handle}; +use bevy_asset::{load_internal_asset, AsAssetId, AssetId, AssetServer, Handle}; use bevy_core_pipeline::{ core_3d::CORE_3D_DEPTH_FORMAT, deferred::*, prelude::Camera3d, prepass::*, }; @@ -53,8 +53,25 @@ use crate::meshlet::{ MeshletMesh3d, }; +use bevy_asset::prelude::AssetChanged; +use bevy_derive::{Deref, DerefMut}; +use bevy_ecs::component::Tick; +use bevy_ecs::query::{QueryItem, ROQueryItem}; +use bevy_ecs::system::SystemChangeTick; +use bevy_reflect::Reflect; +use bevy_render::extract_resource::ExtractResource; +use bevy_render::specialization::view::{GetViewKey, ViewKeyCache, ViewSpecializationTicks}; +use bevy_render::specialization::{ + EntitySpecializationTicks, +}; +use bevy_render::sync_world::{MainEntity, MainEntityHashMap}; use bevy_render::view::RenderVisibleEntities; +use bevy_render::RenderSet::PrepareAssets; use core::{hash::Hash, marker::PhantomData}; +use std::ops::{Deref, DerefMut}; +use derive_more::From; +use bevy_ecs::entity::EntityHash; +use bevy_platform_support::collections::HashMap; pub const PREPASS_SHADER_HANDLE: Handle = Handle::weak_from_u128(921124473254008983); @@ -184,17 +201,29 @@ where } render_app + .init_resource::() + .init_resource::() + .init_resource::>() + .init_resource::>>() .add_render_command::>() .add_render_command::>() .add_render_command::>() .add_render_command::>() .add_systems( Render, - queue_prepass_material_meshes:: - .in_set(RenderSet::QueueMeshes) - .after(prepare_assets::>) - // queue_material_meshes only writes to `material_bind_group_id`, which `queue_prepass_material_meshes` doesn't read - .ambiguous_with(queue_material_meshes::), + ( + check_prepass_views_need_specialization + .in_set(PrepareAssets), + specialize_prepass_material_meshes:: + .in_set(PrepareAssets) + .after(prepare_assets::>) + .after(prepare_assets::), + queue_prepass_material_meshes:: + .in_set(RenderSet::QueueMeshes) + .after(prepare_assets::>) + // queue_material_meshes only writes to `material_bind_group_id`, which `queue_prepass_material_meshes` doesn't read + .ambiguous_with(queue_material_meshes::), + ), ); #[cfg(feature = "meshlet")] @@ -771,82 +800,165 @@ pub fn prepare_prepass_view_bind_group( } } -pub fn queue_prepass_material_meshes( - prepass_pipeline: Res>, - mut pipelines: ResMut>>, - pipeline_cache: Res, - (render_meshes, render_mesh_instances): ( - Res>, - Res, - ), +#[derive(Resource)] +pub struct SpecializedPrepassMaterialPipelineCache { + map: HashMap<(MainEntity, MainEntity), (Tick, CachedRenderPipelineId), EntityHash>, + marker: PhantomData, +} + +impl Default for SpecializedPrepassMaterialPipelineCache { + fn default() -> Self { + Self { + map: HashMap::default(), + marker: PhantomData, + } + } +} + +impl Deref for SpecializedPrepassMaterialPipelineCache { + type Target = HashMap<(MainEntity, MainEntity), (Tick, CachedRenderPipelineId), EntityHash>; + + fn deref(&self) -> &Self::Target { + &self.map + } +} + +impl DerefMut for SpecializedPrepassMaterialPipelineCache { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.map + } +} + +#[derive(Resource, Deref, DerefMut, Default, Clone)] +pub struct ViewKeyPrepassCache(MainEntityHashMap); + +#[derive(Resource, Deref, DerefMut, Default, Clone)] +pub struct ViewPrepassSpecializationTicks(MainEntityHashMap); + +pub fn check_prepass_views_need_specialization( + mut view_key_cache: ResMut, + mut view_specialization_ticks: ResMut, + mut views: Query<( + &MainEntity, + &Msaa, + Option<&DepthPrepass>, + Option<&NormalPrepass>, + Option<&MotionVectorPrepass>, + )>, + ticks: SystemChangeTick, +) { + for (view_entity, msaa, depth_prepass, normal_prepass, motion_vector_prepass) in + views.iter_mut() + { + let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples()); + if depth_prepass.is_some() { + view_key |= MeshPipelineKey::DEPTH_PREPASS; + } + if normal_prepass.is_some() { + view_key |= MeshPipelineKey::NORMAL_PREPASS; + } + if motion_vector_prepass.is_some() { + view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS; + } + + if let Some(current_key) = view_key_cache.get_mut(view_entity) { + if *current_key != view_key { + view_key_cache.insert(*view_entity, view_key); + view_specialization_ticks.insert(*view_entity, ticks.this_run()); + } + } else { + view_key_cache.insert(*view_entity, view_key); + view_specialization_ticks.insert(*view_entity, ticks.this_run()); + } + } +} + +pub fn specialize_prepass_material_meshes( + render_meshes: Res>, render_materials: Res>>, + render_mesh_instances: Res, render_material_instances: Res>, render_lightmaps: Res, render_visibility_ranges: Res, - (mesh_allocator, material_bind_group_allocator): ( - Res, - Res>, - ), - gpu_preprocessing_support: 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>, + material_bind_group_allocator: Res>, + view_key_cache: Res, views: Query<( + &MainEntity, &ExtractedView, &RenderVisibleEntities, - &Msaa, - Option<&DepthPrepass>, - Option<&NormalPrepass>, Option<&MotionVectorPrepass>, Option<&DeferredPrepass>, )>, + ( + mut opaque_prepass_render_phases, + mut alpha_mask_prepass_render_phases, + mut opaque_deferred_render_phases, + mut alpha_mask_deferred_render_phases, + ): ( + ResMut>, + ResMut>, + ResMut>, + ResMut>, + ), + ( + mut specialized_material_pipeline_cache, + ticks, + prepass_pipeline, + mut pipelines, + pipeline_cache, + view_specialization_ticks, + entity_specialization_ticks, + ): ( + ResMut>, + SystemChangeTick, + Res>, + ResMut>>, + Res, + Res, + Res>>, + ), ) where + M: Material, M::Data: PartialEq + Eq + Hash + Clone, { - for ( - extracted_view, - visible_entities, - msaa, - depth_prepass, - normal_prepass, - motion_vector_prepass, - deferred_prepass, - ) in &views + for (view_entity, extracted_view, visible_entities, motion_vector_prepass, deferred_prepass) in + &views { - let ( - mut opaque_phase, - mut alpha_mask_phase, - mut opaque_deferred_phase, - mut alpha_mask_deferred_phase, - ) = ( - opaque_prepass_render_phases.get_mut(&extracted_view.retained_view_entity), - alpha_mask_prepass_render_phases.get_mut(&extracted_view.retained_view_entity), - opaque_deferred_render_phases.get_mut(&extracted_view.retained_view_entity), - alpha_mask_deferred_render_phases.get_mut(&extracted_view.retained_view_entity), - ); - - // 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() + if !opaque_deferred_render_phases.contains_key(&extracted_view.retained_view_entity) + && !alpha_mask_deferred_render_phases.contains_key(&extracted_view.retained_view_entity) + && !opaque_prepass_render_phases.contains_key(&extracted_view.retained_view_entity) + && !alpha_mask_prepass_render_phases.contains_key(&extracted_view.retained_view_entity) { continue; } - let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples()); - if depth_prepass.is_some() { - view_key |= MeshPipelineKey::DEPTH_PREPASS; - } - if normal_prepass.is_some() { - view_key |= MeshPipelineKey::NORMAL_PREPASS; - } - if motion_vector_prepass.is_some() { - view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS; - } + let Some(view_key) = view_key_cache.get(view_entity) else { + continue; + }; + + for (_, visible_entity) in visible_entities.iter::() { + let view_tick = view_specialization_ticks + .get(view_entity) + .expect("View entity not found in specialization ticks"); + let entity_tick = entity_specialization_ticks + .entities + .get(visible_entity) + .expect("Entity not found in specialization ticks"); + let last_specialized_tick = specialized_material_pipeline_cache + .get(&(*view_entity, *visible_entity)) + .map(|(tick, _)| *tick); + let needs_specialization = + last_specialized_tick.map_or(true, |last_specialized_tick| { + let view_changed = + view_tick.is_newer_than(last_specialized_tick, ticks.this_run()); + let entity_changed = + entity_tick.is_newer_than(last_specialized_tick, ticks.this_run()); + view_changed || entity_changed + }); + if !needs_specialization { + continue; + } - for (render_entity, visible_entity) in visible_entities.iter::() { let Some(material_asset_id) = render_material_instances.get(visible_entity) else { continue; }; @@ -866,12 +978,21 @@ pub fn queue_prepass_material_meshes( continue; }; - let mut mesh_key = view_key | MeshPipelineKey::from_bits_retain(mesh.key_bits.bits()); + let mut mesh_key = *view_key | MeshPipelineKey::from_bits_retain(mesh.key_bits.bits()); let alpha_mode = material.properties.alpha_mode; match alpha_mode { AlphaMode::Opaque | AlphaMode::AlphaToCoverage | AlphaMode::Mask(_) => { - mesh_key |= alpha_mode_pipeline_key(alpha_mode, msaa); + mesh_key |= alpha_mode_pipeline_key( + alpha_mode, + match view_key.msaa_samples() { + 1 => &Msaa::Off, + 2 => &Msaa::Sample2, + 4 => &Msaa::Sample4, + 8 => &Msaa::Sample8, + _ => unreachable!("Unsupported MSAA sample count"), + }, + ); } AlphaMode::Blend | AlphaMode::Premultiplied @@ -949,17 +1070,91 @@ pub fn queue_prepass_material_meshes( } }; + specialized_material_pipeline_cache.insert( + (*view_entity, *visible_entity), + (ticks.this_run(), pipeline_id), + ); + } + } +} + +pub fn queue_prepass_material_meshes( + render_meshes: Res>, + render_mesh_instances: Res, + render_materials: Res>>, + render_material_instances: Res>, + mesh_allocator: Res, + material_bind_group_allocator: Res>, + gpu_preprocessing_support: 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>, + views: Query<(&MainEntity, &ExtractedView, &RenderVisibleEntities)>, + specialized_material_pipeline_cache: Res>, +) where + M::Data: PartialEq + Eq + Hash + Clone, +{ + for (view_entity, extracted_view, visible_entities) in &views { + let ( + mut opaque_phase, + mut alpha_mask_phase, + mut opaque_deferred_phase, + mut alpha_mask_deferred_phase, + ) = ( + opaque_prepass_render_phases.get_mut(&extracted_view.retained_view_entity), + alpha_mask_prepass_render_phases.get_mut(&extracted_view.retained_view_entity), + opaque_deferred_render_phases.get_mut(&extracted_view.retained_view_entity), + alpha_mask_deferred_render_phases.get_mut(&extracted_view.retained_view_entity), + ); + + // 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; + } + + for (render_entity, visible_entity) in visible_entities.iter::() { + let Some((_, pipeline_id)) = + specialized_material_pipeline_cache.get(&(*view_entity, *visible_entity)) + else { + continue; + }; + let Some(material_asset_id) = render_material_instances.get(visible_entity) else { + continue; + }; + let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity) + else { + continue; + }; + let Some(material) = render_materials.get(*material_asset_id) else { + continue; + }; + let Some(material_bind_group) = + material_bind_group_allocator.get(material.binding.group) + else { + continue; + }; + let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else { + continue; + }; let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id); - match mesh_key - .intersection(MeshPipelineKey::BLEND_RESERVED_BITS | MeshPipelineKey::MAY_DISCARD) - { - MeshPipelineKey::BLEND_OPAQUE | MeshPipelineKey::BLEND_ALPHA_TO_COVERAGE => { + let deferred = material.properties.deferred_draw_function_id.is_some(); + + match material.properties.render_phase_type { + RenderPhaseType::Opaque => { if deferred { opaque_deferred_phase.as_mut().unwrap().add( OpaqueNoLightmap3dBatchSetKey { - draw_function: material.properties.deferred_draw_function_id.unwrap(), - pipeline: pipeline_id, + draw_function: material + .properties + .deferred_draw_function_id + .unwrap(), + pipeline: *pipeline_id, material_bind_group_index: Some(material.binding.group.0), vertex_slab: vertex_slab.unwrap_or_default(), index_slab, @@ -978,8 +1173,11 @@ pub fn queue_prepass_material_meshes( mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id); opaque_phase.add( OpaqueNoLightmap3dBatchSetKey { - draw_function: material.properties.prepass_draw_function_id.unwrap(), - pipeline: pipeline_id, + draw_function: material + .properties + .prepass_draw_function_id + .unwrap(), + pipeline: *pipeline_id, material_bind_group_index: Some(material.binding.group.0), vertex_slab: vertex_slab.unwrap_or_default(), index_slab, @@ -995,14 +1193,13 @@ pub fn queue_prepass_material_meshes( ); } } - // Alpha mask - MeshPipelineKey::MAY_DISCARD => { + RenderPhaseType::AlphaMask => { if deferred { let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id); let batch_set_key = OpaqueNoLightmap3dBatchSetKey { draw_function: material.properties.deferred_draw_function_id.unwrap(), - pipeline: pipeline_id, + pipeline: *pipeline_id, material_bind_group_index: Some(material.binding.group.0), vertex_slab: vertex_slab.unwrap_or_default(), index_slab, @@ -1024,7 +1221,7 @@ pub fn queue_prepass_material_meshes( mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id); let batch_set_key = OpaqueNoLightmap3dBatchSetKey { draw_function: material.properties.prepass_draw_function_id.unwrap(), - pipeline: pipeline_id, + pipeline: *pipeline_id, material_bind_group_index: Some(material.binding.group.0), vertex_slab: vertex_slab.unwrap_or_default(), index_slab, diff --git a/crates/bevy_render/src/specialization/mod.rs b/crates/bevy_render/src/specialization/mod.rs index b29e94d28d97f..1ae0f869060c3 100644 --- a/crates/bevy_render/src/specialization/mod.rs +++ b/crates/bevy_render/src/specialization/mod.rs @@ -55,7 +55,7 @@ where } } -pub trait NeedsSpecialization: Component { +pub trait NeedsSpecialization: Sync + Send + 'static { type ViewKey: GetViewKey; type Pipeline: SpecializedMeshPipeline + Resource + Send + Sync + 'static; type QueryData: ReadOnlyQueryData + 'static; diff --git a/crates/bevy_render/src/specialization/view.rs b/crates/bevy_render/src/specialization/view.rs index 734f4e0457484..f038840031dab 100644 --- a/crates/bevy_render/src/specialization/view.rs +++ b/crates/bevy_render/src/specialization/view.rs @@ -1,3 +1,4 @@ +use crate::sync_world::{MainEntity, MainEntityHashMap}; use crate::Render; use crate::RenderSet::PrepareAssets; use bevy_app::{App, Plugin}; @@ -11,12 +12,12 @@ use bevy_ecs::schedule::IntoSystemConfigs; use bevy_ecs::system::{Query, ResMut, SystemChangeTick}; use bevy_render_macros::ExtractResource; use core::marker::PhantomData; -use crate::sync_world::{MainEntity, MainEntityHashMap}; pub struct SpecializeViewsPlugin(PhantomData); impl Plugin for SpecializeViewsPlugin - where VK: GetViewKey +where + VK: GetViewKey, { fn build(&self, app: &mut App) {} @@ -30,7 +31,7 @@ impl Plugin for SpecializeViewsPlugin } } -impl Default for SpecializeViewsPlugin { +impl Default for SpecializeViewsPlugin { fn default() -> Self { Self(Default::default()) } From 54a530cba3986ee36e64971e25ae446f50930bf7 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Sun, 26 Jan 2025 23:40:48 -0800 Subject: [PATCH 09/35] Fix prepass. --- crates/bevy_pbr/src/material.rs | 2 +- crates/bevy_pbr/src/prepass/mod.rs | 60 ++++++++++++++---------------- 2 files changed, 28 insertions(+), 34 deletions(-) diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 31d5d0293fcbd..bb8e9535d2b9c 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -1137,7 +1137,7 @@ impl RenderAsset for PreparedMaterial { if !forward { RenderPhaseType::Opaque } else { - RenderPhaseType::Transparent + RenderPhaseType::AlphaMask } } AlphaMode::Blend diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 2b772e018e89d..ddd21a6f8d8d1 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -56,22 +56,20 @@ use crate::meshlet::{ use bevy_asset::prelude::AssetChanged; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::component::Tick; +use bevy_ecs::entity::EntityHash; use bevy_ecs::query::{QueryItem, ROQueryItem}; use bevy_ecs::system::SystemChangeTick; +use bevy_platform_support::collections::HashMap; use bevy_reflect::Reflect; use bevy_render::extract_resource::ExtractResource; use bevy_render::specialization::view::{GetViewKey, ViewKeyCache, ViewSpecializationTicks}; -use bevy_render::specialization::{ - EntitySpecializationTicks, -}; +use bevy_render::specialization::EntitySpecializationTicks; use bevy_render::sync_world::{MainEntity, MainEntityHashMap}; use bevy_render::view::RenderVisibleEntities; use bevy_render::RenderSet::PrepareAssets; use core::{hash::Hash, marker::PhantomData}; -use std::ops::{Deref, DerefMut}; use derive_more::From; -use bevy_ecs::entity::EntityHash; -use bevy_platform_support::collections::HashMap; +use std::ops::{Deref, DerefMut}; pub const PREPASS_SHADER_HANDLE: Handle = Handle::weak_from_u128(921124473254008983); @@ -212,8 +210,7 @@ where .add_systems( Render, ( - check_prepass_views_need_specialization - .in_set(PrepareAssets), + check_prepass_views_need_specialization.in_set(PrepareAssets), specialize_prepass_material_meshes:: .in_set(PrepareAssets) .after(prepare_assets::>) @@ -886,6 +883,9 @@ pub fn specialize_prepass_material_meshes( &MainEntity, &ExtractedView, &RenderVisibleEntities, + &Msaa, + Option<&DepthPrepass>, + Option<&NormalPrepass>, Option<&MotionVectorPrepass>, Option<&DeferredPrepass>, )>, @@ -921,8 +921,16 @@ pub fn specialize_prepass_material_meshes( M: Material, M::Data: PartialEq + Eq + Hash + Clone, { - for (view_entity, extracted_view, visible_entities, motion_vector_prepass, deferred_prepass) in - &views + for ( + view_entity, + extracted_view, + visible_entities, + msaa, + depth_prepass, + normal_prepass, + motion_vector_prepass, + deferred_prepass, + ) in &views { if !opaque_deferred_render_phases.contains_key(&extracted_view.retained_view_entity) && !alpha_mask_deferred_render_phases.contains_key(&extracted_view.retained_view_entity) @@ -983,16 +991,7 @@ pub fn specialize_prepass_material_meshes( let alpha_mode = material.properties.alpha_mode; match alpha_mode { AlphaMode::Opaque | AlphaMode::AlphaToCoverage | AlphaMode::Mask(_) => { - mesh_key |= alpha_mode_pipeline_key( - alpha_mode, - match view_key.msaa_samples() { - 1 => &Msaa::Off, - 2 => &Msaa::Sample2, - 4 => &Msaa::Sample4, - 8 => &Msaa::Sample8, - _ => unreachable!("Unsupported MSAA sample count"), - }, - ); + mesh_key |= alpha_mode_pipeline_key(alpha_mode, msaa); } AlphaMode::Blend | AlphaMode::Premultiplied @@ -1079,23 +1078,21 @@ pub fn specialize_prepass_material_meshes( } pub fn queue_prepass_material_meshes( - render_meshes: Res>, render_mesh_instances: Res, render_materials: Res>>, render_material_instances: Res>, mesh_allocator: Res, - material_bind_group_allocator: Res>, gpu_preprocessing_support: 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>, - views: Query<(&MainEntity, &ExtractedView, &RenderVisibleEntities)>, + views: Query<(&MainEntity, &ExtractedView, &RenderVisibleEntities, &Msaa, Has)>, specialized_material_pipeline_cache: Res>, ) where M::Data: PartialEq + Eq + Hash + Clone, { - for (view_entity, extracted_view, visible_entities) in &views { + for (view_entity, extracted_view, visible_entities, msaa, has_deferred_prepass) in &views { let ( mut opaque_phase, mut alpha_mask_phase, @@ -1133,17 +1130,14 @@ pub fn queue_prepass_material_meshes( let Some(material) = render_materials.get(*material_asset_id) else { continue; }; - let Some(material_bind_group) = - material_bind_group_allocator.get(material.binding.group) - else { - continue; - }; - let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else { - continue; - }; let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id); - let deferred = material.properties.deferred_draw_function_id.is_some(); + let forward = match material.properties.render_method { + OpaqueRendererMethod::Forward => true, + OpaqueRendererMethod::Deferred => false, + OpaqueRendererMethod::Auto => unreachable!(), + }; + let deferred = has_deferred_prepass && !forward; match material.properties.render_phase_type { RenderPhaseType::Opaque => { From f296a7b43e00e5b50a3ac31328aa2ad9a71c2f80 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Mon, 27 Jan 2025 11:56:05 -0800 Subject: [PATCH 10/35] Start shadows. --- crates/bevy_pbr/src/material.rs | 27 ++-- crates/bevy_pbr/src/prepass/mod.rs | 11 +- crates/bevy_pbr/src/render/light.rs | 226 +++++++++++++++++++++++++--- 3 files changed, 225 insertions(+), 39 deletions(-) diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index bb8e9535d2b9c..d0c9dc50747a7 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -325,11 +325,18 @@ where ); if self.shadows_enabled { - render_app.add_systems( + render_app + .init_resource::() + .init_resource::() + .init_resource::>() + .add_systems( Render, + (specialize_shadows:: + .in_set(RenderSet::PrepareAssets) + .after(prepare_assets::>), queue_shadows:: .in_set(RenderSet::QueueMeshes) - .after(prepare_assets::>), + .after(prepare_assets::>)), ); } @@ -1110,12 +1117,6 @@ impl RenderAsset for PreparedMaterial { let reads_view_transmission_texture = mesh_pipeline_key_bits.contains(MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE); - let forward = match render_method { - OpaqueRendererMethod::Forward => true, - OpaqueRendererMethod::Deferred => false, - OpaqueRendererMethod::Auto => unreachable!(), - }; - let render_phase_type = match material.alpha_mode() { AlphaMode::Opaque => { if reads_view_transmission_texture { @@ -1127,17 +1128,15 @@ impl RenderAsset for PreparedMaterial { AlphaMode::Mask(_) => { if reads_view_transmission_texture { RenderPhaseType::Transmissive - } else if forward { - RenderPhaseType::AlphaMask } else { - panic!("Invalid alpha mask configuration"); + RenderPhaseType::AlphaMask } } AlphaMode::AlphaToCoverage => { - if !forward { - RenderPhaseType::Opaque + if reads_view_transmission_texture { + RenderPhaseType::Transmissive } else { - RenderPhaseType::AlphaMask + RenderPhaseType::Opaque } } AlphaMode::Blend diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index ddd21a6f8d8d1..1232e66bf5f75 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -1087,12 +1087,12 @@ pub fn queue_prepass_material_meshes( mut alpha_mask_prepass_render_phases: ResMut>, mut opaque_deferred_render_phases: ResMut>, mut alpha_mask_deferred_render_phases: ResMut>, - views: Query<(&MainEntity, &ExtractedView, &RenderVisibleEntities, &Msaa, Has)>, + views: Query<(&MainEntity, &ExtractedView, &RenderVisibleEntities)>, specialized_material_pipeline_cache: Res>, ) where M::Data: PartialEq + Eq + Hash + Clone, { - for (view_entity, extracted_view, visible_entities, msaa, has_deferred_prepass) in &views { + for (view_entity, extracted_view, visible_entities) in &views { let ( mut opaque_phase, mut alpha_mask_phase, @@ -1132,12 +1132,11 @@ pub fn queue_prepass_material_meshes( }; let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id); - let forward = match material.properties.render_method { - OpaqueRendererMethod::Forward => true, - OpaqueRendererMethod::Deferred => false, + let deferred = match material.properties.render_method { + OpaqueRendererMethod::Forward => false, + OpaqueRendererMethod::Deferred => true, OpaqueRendererMethod::Auto => unreachable!(), }; - let deferred = has_deferred_prepass && !forward; match material.properties.render_phase_type { RenderPhaseType::Opaque => { diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index ff4c0b0ac8d48..7548695ad3be8 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -5,6 +5,9 @@ use bevy_asset::UntypedAssetId; use bevy_color::ColorToComponents; use bevy_core_pipeline::core_3d::{Camera3d, CORE_3D_DEPTH_FORMAT}; use bevy_derive::{Deref, DerefMut}; +use bevy_ecs::component::Tick; +use bevy_ecs::entity::EntityHash; +use bevy_ecs::system::SystemChangeTick; use bevy_ecs::{ entity::{hash_map::EntityHashMap, hash_set::EntityHashSet}, prelude::*, @@ -12,6 +15,12 @@ use bevy_ecs::{ }; use bevy_math::{ops, Mat4, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles}; use bevy_platform_support::collections::{HashMap, HashSet}; +use bevy_render::extract_resource::ExtractResource; +use bevy_render::specialization::view::{GetViewKey, ViewKeyCache, ViewSpecializationTicks}; +use bevy_render::specialization::{ + EntitiesNeedingSpecialization, EntitySpecializationTicks, NeedsSpecialization, +}; +use bevy_render::sync_world::MainEntityHashMap; use bevy_render::{ batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport}, camera::SortedCameras, @@ -36,8 +45,10 @@ use bevy_render::{ sync_world::{MainEntity, RenderEntity}, }; use bevy_transform::{components::GlobalTransform, prelude::Transform}; -use bevy_utils::default; +use bevy_utils::{default, Parallel}; use core::{hash::Hash, ops::Range}; +use std::marker::PhantomData; +use std::ops::{Deref, DerefMut}; #[cfg(feature = "trace")] use tracing::info_span; use tracing::{error, warn}; @@ -1580,11 +1591,104 @@ fn despawn_entities(commands: &mut Commands, entities: Vec) { }); } -/// For each shadow cascade, iterates over all the meshes "visible" from it and -/// adds them to [`BinnedRenderPhase`]s or [`SortedRenderPhase`]s as -/// appropriate. -pub fn queue_shadows( - shadow_draw_functions: Res>, +/// These will be extracted in the material extraction +fn check_entities_needing_specialization( + mut thread_queues: Local>>, + mut needs_specialization: Query>, Changed)>, + mut entities_needing_specialization: ResMut>, + mut removed_components: RemovedComponents, +) { + entities_needing_specialization.entities.clear(); + needs_specialization.par_iter_mut().for_each_init( + || thread_queues.borrow_local_mut(), + |queue, entity| { + queue.push(entity.into()); + }, + ); + + for removed in removed_components.read() { + entities_needing_specialization + .entities + .push(removed.into()); + } + + thread_queues.drain_into(&mut entities_needing_specialization.entities); +} + +//ODO: Are these render entities or main entities?These are ex +#[derive(Resource, Deref, DerefMut, Default, Debug, Clone)] +pub struct LightKeyCache(EntityHashMap); + +#[derive(Resource, Deref, DerefMut, Default, Debug, Clone)] +pub struct LightSpecializationTicks(EntityHashMap); + +#[derive(Resource)] +pub struct SpecializedShadowMaterialPipelineCache { + map: HashMap<(Entity, MainEntity), (Tick, CachedRenderPipelineId), EntityHash>, + marker: PhantomData, +} + +impl Default for SpecializedShadowMaterialPipelineCache { + fn default() -> Self { + Self { + map: HashMap::default(), + marker: PhantomData, + } + } +} + +impl Deref for SpecializedShadowMaterialPipelineCache { + type Target = HashMap<(Entity, MainEntity), (Tick, CachedRenderPipelineId), EntityHash>; + + fn deref(&self) -> &Self::Target { + &self.map + } +} + +impl DerefMut for SpecializedShadowMaterialPipelineCache { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.map + } +} + +pub fn check_views_lights_need_specialization( + view_lights: Query<(Entity, &ViewLightEntities), With>, + view_light_entities: Query<(&LightEntity, &ExtractedView)>, + shadow_render_phases: Res>, + mut light_key_cache: ResMut, + mut light_specialization_ticks: ResMut, + ticks: SystemChangeTick, +) where + VK: GetViewKey, +{ + for (entity, view_lights) in &view_lights { + for view_light_entity in view_lights.lights.iter().copied() { + let Ok((light_entity, extracted_view_light)) = + view_light_entities.get(view_light_entity) + else { + continue; + }; + if !shadow_render_phases.contains_key(&extracted_view_light.retained_view_entity) { + continue; + } + + let is_directional_light = matches!(light_entity, LightEntity::Directional { .. }); + let mut light_key = MeshPipelineKey::DEPTH_PREPASS; + light_key.set(MeshPipelineKey::UNCLIPPED_DEPTH_ORTHO, is_directional_light); + if let Some(current_key) = light_key_cache.get_mut(&entity) { + if *current_key != light_key { + light_key_cache.insert(view_light_entity, light_key); + light_specialization_ticks.insert(view_light_entity, ticks.this_run()); + } + } else { + light_key_cache.insert(view_light_entity, light_key); + light_specialization_ticks.insert(view_light_entity, ticks.this_run()); + } + } + } +} + +pub fn specialize_shadows( prepass_pipeline: Res>, (render_meshes, render_mesh_instances, render_materials, render_material_instances): ( Res>, @@ -1593,12 +1697,10 @@ pub fn queue_shadows( Res>, ), material_bind_group_allocator: Res>, - mut shadow_render_phases: ResMut>, + shadow_render_phases: Res>, mut pipelines: ResMut>>, pipeline_cache: Res, render_lightmaps: Res, - gpu_preprocessing_support: Res, - mesh_allocator: Res, view_lights: Query<(Entity, &ViewLightEntities), With>, view_light_entities: Query<(&LightEntity, &ExtractedView)>, point_light_entities: Query<&RenderCubemapVisibleEntities, With>, @@ -1607,24 +1709,27 @@ pub fn queue_shadows( With, >, spot_light_entities: Query<&RenderVisibleMeshEntities, With>, + light_key_cache: Res, + mut specialized_material_pipeline_cache: ResMut>, + ticks: SystemChangeTick, ) where M::Data: PartialEq + Eq + Hash + Clone, { 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 Ok((light_entity, extracted_view_light)) = view_light_entities.get(view_light_entity) else { continue; }; - let Some(shadow_phase) = - shadow_render_phases.get_mut(&extracted_view_light.retained_view_entity) - else { + if !shadow_render_phases.contains_key(&extracted_view_light.retained_view_entity) { + continue; + } + + let Some(light_key) = light_key_cache.get(&entity) else { continue; }; - let is_directional_light = matches!(light_entity, LightEntity::Directional { .. }); let visible_entities = match light_entity { LightEntity::Directional { light_entity, @@ -1648,13 +1753,11 @@ pub fn queue_shadows( .get(*light_entity) .expect("Failed to get spot light visible entities"), }; - let mut light_key = MeshPipelineKey::DEPTH_PREPASS; - light_key.set(MeshPipelineKey::UNCLIPPED_DEPTH_ORTHO, is_directional_light); // NOTE: Lights with shadow mapping disabled will have no visible entities // so no meshes will be queued - for (entity, main_entity) in visible_entities.iter().copied() { + for (_, main_entity) in visible_entities.iter().copied() { let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(main_entity) else { continue; @@ -1681,7 +1784,7 @@ pub fn queue_shadows( }; let mut mesh_key = - light_key | MeshPipelineKey::from_bits_retain(mesh.key_bits.bits()); + *light_key | MeshPipelineKey::from_bits_retain(mesh.key_bits.bits()); // Even though we don't use the lightmap in the shadow map, the // `SetMeshBindGroup` render command will bind the data for it. So @@ -1720,11 +1823,96 @@ pub fn queue_shadows( } }; + specialized_material_pipeline_cache.insert( + (view_light_entity, main_entity), + (ticks.this_run(), pipeline_id), + ); + } + } + } +} + +/// For each shadow cascade, iterates over all the meshes "visible" from it and +/// adds them to [`BinnedRenderPhase`]s or [`SortedRenderPhase`]s as +/// appropriate. +pub fn queue_shadows( + shadow_draw_functions: Res>, + render_mesh_instances: Res, + mut shadow_render_phases: ResMut>, + gpu_preprocessing_support: Res, + mesh_allocator: Res, + view_lights: Query<(Entity, &ViewLightEntities), With>, + view_light_entities: Query<(&LightEntity, &ExtractedView)>, + point_light_entities: Query<&RenderCubemapVisibleEntities, With>, + directional_light_entities: Query< + &RenderCascadesVisibleEntities, + With, + >, + spot_light_entities: Query<&RenderVisibleMeshEntities, With>, + specialized_material_pipeline_cache: Res>, +) where + M::Data: PartialEq + Eq + Hash + Clone, +{ + let draw_shadow_mesh = shadow_draw_functions.read().id::>(); + for (entity, view_lights) in &view_lights { + for view_light_entity in view_lights.lights.iter().copied() { + let Ok((light_entity, extracted_view_light)) = + view_light_entities.get(view_light_entity) + else { + continue; + }; + let Some(shadow_phase) = + shadow_render_phases.get_mut(&extracted_view_light.retained_view_entity) + else { + continue; + }; + + let visible_entities = match light_entity { + LightEntity::Directional { + light_entity, + cascade_index, + } => directional_light_entities + .get(*light_entity) + .expect("Failed to get directional light visible entities") + .entities + .get(&entity) + .expect("Failed to get directional light visible entities for view") + .get(*cascade_index) + .expect("Failed to get directional light visible entities for cascade"), + LightEntity::Point { + light_entity, + face_index, + } => point_light_entities + .get(*light_entity) + .expect("Failed to get point light visible entities") + .get(*face_index), + LightEntity::Spot { light_entity } => spot_light_entities + .get(*light_entity) + .expect("Failed to get spot light visible entities"), + }; + + for (entity, main_entity) in visible_entities.iter().copied() { + let Some((_, pipeline_id)) = + specialized_material_pipeline_cache.get(&(view_light_entity, main_entity)) + else { + continue; + }; + let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(main_entity) + else { + continue; + }; + if !mesh_instance + .flags + .contains(RenderMeshInstanceFlags::SHADOW_CASTER) + { + continue; + } + let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id); let batch_set_key = ShadowBatchSetKey { - pipeline: pipeline_id, + pipeline: *pipeline_id, draw_function: draw_shadow_mesh, vertex_slab: vertex_slab.unwrap_or_default(), index_slab, From c251c043a970db7339c6aa175e8e6252c1fe29fe Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Mon, 27 Jan 2025 12:07:39 -0800 Subject: [PATCH 11/35] Shadows. --- crates/bevy_pbr/src/material.rs | 9 ++++- crates/bevy_pbr/src/render/light.rs | 52 +++++++++++++++++++++++------ 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index d0c9dc50747a7..cb46bbd4f844e 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -7,7 +7,7 @@ use crate::meshlet::{ }; use crate::*; use bevy_asset::prelude::AssetChanged; -use bevy_asset::{Asset, AssetId, AssetServer, UntypedAssetId}; +use bevy_asset::{Asset, AssetEvents, AssetId, AssetServer, UntypedAssetId}; use bevy_core_pipeline::deferred::{AlphaMask3dDeferred, Opaque3dDeferred}; use bevy_core_pipeline::prepass::{AlphaMask3dPrepass, Opaque3dPrepass}; use bevy_core_pipeline::{ @@ -291,6 +291,13 @@ where .after(mesh::mark_3d_meshes_as_changed_if_their_assets_changed), ); + if self.shadows_enabled { + app.add_systems( + PostUpdate, + check_entities_needing_specialization::.after(AssetEvents), + ); + } + if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app .init_resource::>() diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 7548695ad3be8..962dc29aa0249 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -1592,10 +1592,10 @@ fn despawn_entities(commands: &mut Commands, entities: Vec) { } /// These will be extracted in the material extraction -fn check_entities_needing_specialization( +pub fn check_entities_needing_specialization( mut thread_queues: Local>>, mut needs_specialization: Query>, Changed)>, - mut entities_needing_specialization: ResMut>, + mut entities_needing_specialization: ResMut>>, mut removed_components: RemovedComponents, ) { entities_needing_specialization.entities.clear(); @@ -1690,13 +1690,19 @@ pub fn check_views_lights_need_specialization( pub fn specialize_shadows( prepass_pipeline: Res>, - (render_meshes, render_mesh_instances, render_materials, render_material_instances): ( + ( + render_meshes, + render_mesh_instances, + render_materials, + render_material_instances, + material_bind_group_allocator, + ): ( Res>, Res, Res>>, Res>, + Res>, ), - material_bind_group_allocator: Res>, shadow_render_phases: Res>, mut pipelines: ResMut>>, pipeline_cache: Res, @@ -1711,6 +1717,8 @@ pub fn specialize_shadows( spot_light_entities: Query<&RenderVisibleMeshEntities, With>, light_key_cache: Res, mut specialized_material_pipeline_cache: ResMut>, + light_specialization_ticks: Res, + entity_specialization_ticks: Res>>, ticks: SystemChangeTick, ) where M::Data: PartialEq + Eq + Hash + Clone, @@ -1725,7 +1733,6 @@ pub fn specialize_shadows( if !shadow_render_phases.contains_key(&extracted_view_light.retained_view_entity) { continue; } - let Some(light_key) = light_key_cache.get(&entity) else { continue; }; @@ -1757,8 +1764,30 @@ pub fn specialize_shadows( // NOTE: Lights with shadow mapping disabled will have no visible entities // so no meshes will be queued - for (_, main_entity) in visible_entities.iter().copied() { - let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(main_entity) + for (_, visible_entity) in visible_entities.iter().copied() { + let view_tick = light_specialization_ticks + .get(&view_light_entity) + .expect("View entity not found in specialization ticks"); + let entity_tick = entity_specialization_ticks + .entities + .get(&visible_entity) + .expect("Entity not found in specialization ticks"); + let last_specialized_tick = specialized_material_pipeline_cache + .get(&(view_light_entity, visible_entity)) + .map(|(tick, _)| *tick); + let needs_specialization = + last_specialized_tick.map_or(true, |last_specialized_tick| { + let view_changed = + view_tick.is_newer_than(last_specialized_tick, ticks.this_run()); + let entity_changed = + entity_tick.is_newer_than(last_specialized_tick, ticks.this_run()); + view_changed || entity_changed + }); + if !needs_specialization { + continue; + } + let Some(mesh_instance) = + render_mesh_instances.render_mesh_queue_data(visible_entity) else { continue; }; @@ -1768,7 +1797,7 @@ pub fn specialize_shadows( { continue; } - let Some(material_asset_id) = render_material_instances.get(&main_entity) else { + let Some(material_asset_id) = render_material_instances.get(&visible_entity) else { continue; }; let Some(material) = render_materials.get(*material_asset_id) else { @@ -1791,7 +1820,10 @@ pub fn specialize_shadows( // we need to include the appropriate flag in the mesh pipeline key // to ensure that the necessary bind group layout entries are // present. - if render_lightmaps.render_lightmaps.contains_key(&main_entity) { + if render_lightmaps + .render_lightmaps + .contains_key(&visible_entity) + { mesh_key |= MeshPipelineKey::LIGHTMAPPED; } @@ -1824,7 +1856,7 @@ pub fn specialize_shadows( }; specialized_material_pipeline_cache.insert( - (view_light_entity, main_entity), + (view_light_entity, visible_entity), (ticks.this_run(), pipeline_id), ); } From 6757df746bc374b5fdd5a715577746c9bf05a1d7 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Mon, 27 Jan 2025 12:14:22 -0800 Subject: [PATCH 12/35] Fix shadows. --- crates/bevy_pbr/src/material.rs | 36 +++++++++++++++++------------ crates/bevy_pbr/src/render/light.rs | 8 +++---- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index cb46bbd4f844e..aef7c39b5f154 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -39,6 +39,7 @@ use bevy_render::specialization::view::ViewKeyCache; use bevy_render::specialization::{ MeshMaterialSpecializationPlugin, NeedsSpecialization, SpecializePipelines, }; +use bevy_render::RenderSet::PrepareAssets; use bevy_render::{ batching::gpu_preprocessing::GpuPreprocessingSupport, camera::TemporalJitter, @@ -337,14 +338,17 @@ where .init_resource::() .init_resource::>() .add_systems( - Render, - (specialize_shadows:: - .in_set(RenderSet::PrepareAssets) - .after(prepare_assets::>), - queue_shadows:: - .in_set(RenderSet::QueueMeshes) - .after(prepare_assets::>)), - ); + Render, + ( + check_views_lights_need_specialization.in_set(PrepareAssets), + specialize_shadows:: + .in_set(RenderSet::PrepareAssets) + .after(prepare_assets::>), + queue_shadows:: + .in_set(RenderSet::QueueMeshes) + .after(prepare_assets::>), + ), + ); } #[cfg(feature = "meshlet")] @@ -1100,11 +1104,15 @@ impl RenderAsset for PreparedMaterial { let draw_alpha_mask_pbr = alpha_mask_draw_functions.read().id::>(); let draw_transmissive_pbr = transmissive_draw_functions.read().id::>(); let draw_transparent_pbr = transparent_draw_functions.read().id::>(); - let draw_opaque_prepass = opaque_prepass_draw_functions.read().get_id::>(); + let draw_opaque_prepass = opaque_prepass_draw_functions + .read() + .get_id::>(); let draw_alpha_mask_prepass = alpha_mask_prepass_draw_functions .read() .get_id::>(); - let draw_opaque_deferred = opaque_deferred_draw_functions.read().get_id::>(); + let draw_opaque_deferred = opaque_deferred_draw_functions + .read() + .get_id::>(); let draw_alpha_mask_deferred = alpha_mask_deferred_draw_functions .read() .get_id::>(); @@ -1146,11 +1154,9 @@ impl RenderAsset for PreparedMaterial { RenderPhaseType::Opaque } } - AlphaMode::Blend - | AlphaMode::Premultiplied - | AlphaMode::Add - | AlphaMode::Multiply - => RenderPhaseType::Transparent, + AlphaMode::Blend | AlphaMode::Premultiplied | AlphaMode::Add | AlphaMode::Multiply => { + RenderPhaseType::Transparent + } }; let draw_function_id = match render_phase_type { diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 962dc29aa0249..fd5b28b8ec7f5 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -1651,16 +1651,14 @@ impl DerefMut for SpecializedShadowMaterialPipelineCache { } } -pub fn check_views_lights_need_specialization( +pub fn check_views_lights_need_specialization( view_lights: Query<(Entity, &ViewLightEntities), With>, view_light_entities: Query<(&LightEntity, &ExtractedView)>, shadow_render_phases: Res>, mut light_key_cache: ResMut, mut light_specialization_ticks: ResMut, ticks: SystemChangeTick, -) where - VK: GetViewKey, -{ +) { for (entity, view_lights) in &view_lights { for view_light_entity in view_lights.lights.iter().copied() { let Ok((light_entity, extracted_view_light)) = @@ -1733,7 +1731,7 @@ pub fn specialize_shadows( if !shadow_render_phases.contains_key(&extracted_view_light.retained_view_entity) { continue; } - let Some(light_key) = light_key_cache.get(&entity) else { + let Some(light_key) = light_key_cache.get(&view_light_entity) else { continue; }; From 7055630ba6b2e6560946181456cfb3768b39af80 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Mon, 27 Jan 2025 12:41:21 -0800 Subject: [PATCH 13/35] Remove plugin. --- crates/bevy_pbr/src/material.rs | 204 ++++++++++++--- crates/bevy_pbr/src/prepass/mod.rs | 21 +- crates/bevy_pbr/src/render/light.rs | 8 +- crates/bevy_pbr/src/render/mesh.rs | 96 ++++--- crates/bevy_render/src/lib.rs | 1 - crates/bevy_render/src/specialization/mod.rs | 236 ------------------ crates/bevy_render/src/specialization/view.rs | 99 -------- 7 files changed, 243 insertions(+), 422 deletions(-) delete mode 100644 crates/bevy_render/src/specialization/mod.rs delete mode 100644 crates/bevy_render/src/specialization/view.rs diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index aef7c39b5f154..be60027d8fc5d 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -23,8 +23,11 @@ use bevy_core_pipeline::{ tonemapping::{DebandDither, Tonemapping}, }; use bevy_derive::{Deref, DerefMut}; +use bevy_ecs::component::Tick; +use bevy_ecs::entity::EntityHash; use bevy_ecs::query::{QueryItem, ROQueryItem}; use bevy_ecs::system::lifetimeless::Read; +use bevy_ecs::system::SystemChangeTick; use bevy_ecs::{ prelude::*, system::{ @@ -35,10 +38,6 @@ use bevy_ecs::{ use bevy_platform_support::collections::HashMap; use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::Reflect; -use bevy_render::specialization::view::ViewKeyCache; -use bevy_render::specialization::{ - MeshMaterialSpecializationPlugin, NeedsSpecialization, SpecializePipelines, -}; use bevy_render::RenderSet::PrepareAssets; use bevy_render::{ batching::gpu_preprocessing::GpuPreprocessingSupport, @@ -55,7 +54,9 @@ use bevy_render::{ }; use bevy_render::{mesh::allocator::MeshAllocator, sync_world::MainEntityHashMap}; use bevy_render::{texture::FallbackImage, view::RenderVisibleEntities}; +use bevy_utils::Parallel; use core::{hash::Hash, marker::PhantomData}; +use std::ops::{Deref, DerefMut}; use tracing::error; /// Materials are used alongside [`MaterialPlugin`], [`Mesh3d`], and [`MeshMaterial3d`] @@ -281,10 +282,12 @@ where fn build(&self, app: &mut App) { app.init_asset::() .register_type::>() - .add_plugins(( - RenderAssetPlugin::>::default(), - MeshMaterialSpecializationPlugin::>::default(), - )) + .init_resource::>() + .add_systems( + PostUpdate, + check_entities_needing_specialization::.after(AssetEvents), + ) + .add_plugins((RenderAssetPlugin::>::default(),)) .add_systems( PostUpdate, mark_meshes_as_changed_if_their_materials_changed:: @@ -301,6 +304,8 @@ where if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app + .init_resource::>() + .init_resource::>() .init_resource::>() .init_resource::>() .add_render_command::>() @@ -311,7 +316,10 @@ where .init_resource::>>() .add_systems( ExtractSchedule, - extract_mesh_materials::.before(ExtractMeshesSet), + ( + extract_mesh_materials::.before(ExtractMeshesSet), + extract_entities_needs_specialization::, + ), ) .add_systems( Render, @@ -627,27 +635,6 @@ pub const fn screen_space_specular_transmission_pipeline_key( } } -impl NeedsSpecialization for MeshMaterial3d -where - M: Material, - M::Data: PartialEq + Eq + Hash + Clone, -{ - type ViewKey = MeshPipelineKey; - type Pipeline = MaterialPipeline; - type QueryData = (); - type QueryFilter = Or<( - Changed, - AssetChanged, - Changed>, - AssetChanged>, - )>; - - fn needs_specialization(_item: ROQueryItem<'_, Self::QueryData>) -> bool { - // Matching the filter is enough to trigger specialization. - true - } -} - /// A system that ensures that /// [`crate::render::mesh::extract_meshes_for_gpu_building`] re-extracts meshes /// whose materials changed. @@ -709,8 +696,107 @@ fn extract_mesh_materials( } } } -/// For each view, iterates over all the meshes visible from that view and adds -/// them to [`BinnedRenderPhase`]s or [`SortedRenderPhase`]s as appropriate. + +pub fn extract_entities_needs_specialization( + mut entities_needing_specialization: Extract>>, + mut entity_specialization_ticks: ResMut>, + ticks: SystemChangeTick, +) where + M: Material, +{ + for entity in &entities_needing_specialization.entities { + // Update the entity's specialization tick with this run's tick + entity_specialization_ticks + .entities + .insert((*entity).into(), ticks.this_run()); + } +} + +#[derive(Clone, Resource, Debug)] +pub struct EntitiesNeedingSpecialization { + pub entities: Vec, + _marker: PhantomData, +} + +impl Default for EntitiesNeedingSpecialization { + fn default() -> Self { + Self { + entities: Default::default(), + _marker: Default::default(), + } + } +} + +#[derive(Clone, Resource, Debug)] +pub struct EntitySpecializationTicks { + pub entities: MainEntityHashMap, + _marker: PhantomData, +} + +impl Default for EntitySpecializationTicks { + fn default() -> Self { + Self { + entities: MainEntityHashMap::default(), + _marker: Default::default(), + } + } +} + +#[derive(Resource)] +pub struct SpecializedMaterialPipelineCache { + map: HashMap<(MainEntity, MainEntity), (Tick, CachedRenderPipelineId), EntityHash>, + marker: PhantomData, +} + +impl Default for SpecializedMaterialPipelineCache { + fn default() -> Self { + Self { + map: HashMap::default(), + marker: PhantomData, + } + } +} + +impl Deref for SpecializedMaterialPipelineCache { + type Target = HashMap<(MainEntity, MainEntity), (Tick, CachedRenderPipelineId), EntityHash>; + + fn deref(&self) -> &Self::Target { + &self.map + } +} + +impl DerefMut for SpecializedMaterialPipelineCache { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.map + } +} + +fn check_entities_needing_specialization( + mut thread_queues: Local>>, + mut needs_specialization: Query< + Entity, + Or<( + Changed, + AssetChanged, + Changed>, + AssetChanged>, + )>, + >, + mut entities_needing_specialization: ResMut>, +) where + M: Material, +{ + entities_needing_specialization.entities.clear(); + needs_specialization.par_iter_mut().for_each_init( + || thread_queues.borrow_local_mut(), + |queue, entity| { + queue.push(entity.into()); + }, + ); + + thread_queues.drain_into(&mut entities_needing_specialization.entities); +} + pub fn specialize_material_meshes( render_meshes: Res>, render_materials: Res>>, @@ -718,21 +804,28 @@ pub fn specialize_material_meshes( render_material_instances: Res>, render_lightmaps: Res, render_visibility_ranges: Res, - material_bind_group_allocator: Res>, ( + material_bind_group_allocator, mut opaque_render_phases, mut alpha_mask_render_phases, mut transmissive_render_phases, mut transparent_render_phases, ): ( + Res>, ResMut>, ResMut>, ResMut>, ResMut>, ), views: Query<(&MainEntity, &ExtractedView, &RenderVisibleEntities)>, - view_key_cache: Res>, - mut specialized_pipelines: SpecializePipelines>, + view_key_cache: Res, + entity_specialization_ticks: Res>, + view_specialization_ticks: Res, + mut specialized_material_pipeline_cache: ResMut>, + mut pipelines: ResMut>>, + pipeline: Res>, + pipeline_cache: Res, + ticks: SystemChangeTick, ) where M::Data: PartialEq + Eq + Hash + Clone, { @@ -750,7 +843,25 @@ pub fn specialize_material_meshes( }; for (_, visible_entity) in visible_entities.iter::() { - if !specialized_pipelines.needs_specialization(*view_entity, *visible_entity) { + let view_tick = view_specialization_ticks + .get(view_entity) + .expect("View entity not found in specialization ticks"); + let entity_tick = entity_specialization_ticks + .entities + .get(visible_entity) + .expect("Entity not found in specialization ticks"); + let last_specialized_tick = specialized_material_pipeline_cache + .get(&(*view_entity, *visible_entity)) + .map(|(tick, _)| *tick); + let needs_specialization = + last_specialized_tick.map_or(true, |last_specialized_tick| { + let view_changed = + view_tick.is_newer_than(last_specialized_tick, ticks.this_run()); + let entity_changed = + entity_tick.is_newer_than(last_specialized_tick, ticks.this_run()); + view_changed || entity_changed + }); + if !needs_specialization { continue; } let Some(material_asset_id) = render_material_instances.get(visible_entity) else { @@ -821,7 +932,19 @@ pub fn specialize_material_meshes( .get_extra_data(material.binding.slot) .clone(), }; - specialized_pipelines.specialize_pipeline((*view_entity, *visible_entity), key, mesh); + let pipeline_id = pipelines.specialize(&pipeline_cache, &pipeline, key, &mesh.layout); + let pipeline_id = match pipeline_id { + Ok(id) => id, + Err(err) => { + error!("{}", err); + return; + } + }; + + specialized_material_pipeline_cache.insert( + (*view_entity, *visible_entity), + (ticks.this_run(), pipeline_id), + ); } } } @@ -839,7 +962,7 @@ pub fn queue_material_meshes( mut transmissive_render_phases: ResMut>, mut transparent_render_phases: ResMut>, views: Query<(&MainEntity, &ExtractedView, &RenderVisibleEntities)>, - specialized_pipelines: SpecializePipelines>, + specialized_material_pipeline_cache: ResMut>, ) where M::Data: PartialEq + Eq + Hash + Clone, { @@ -861,8 +984,9 @@ pub fn queue_material_meshes( let rangefinder = view.rangefinder3d(); for (render_entity, visible_entity) in visible_entities.iter::() { - let Some(pipeline_id) = - specialized_pipelines.get_pipeline(*view_entity, *visible_entity) + let Some(pipeline_id) = specialized_material_pipeline_cache + .get(&(*view_entity, *visible_entity)) + .map(|(_, pipeline_id)| *pipeline_id) else { continue; }; diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 1232e66bf5f75..483638657d024 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -62,8 +62,6 @@ use bevy_ecs::system::SystemChangeTick; use bevy_platform_support::collections::HashMap; use bevy_reflect::Reflect; use bevy_render::extract_resource::ExtractResource; -use bevy_render::specialization::view::{GetViewKey, ViewKeyCache, ViewSpecializationTicks}; -use bevy_render::specialization::EntitySpecializationTicks; use bevy_render::sync_world::{MainEntity, MainEntityHashMap}; use bevy_render::view::RenderVisibleEntities; use bevy_render::RenderSet::PrepareAssets; @@ -202,7 +200,7 @@ where .init_resource::() .init_resource::() .init_resource::>() - .init_resource::>>() + .init_resource::>() .add_render_command::>() .add_render_command::>() .add_render_command::>() @@ -797,6 +795,21 @@ pub fn prepare_prepass_view_bind_group( } } +#[derive(Clone, Resource, Debug)] +pub struct EntitySpecializationTicks { + pub entities: MainEntityHashMap, + _marker: PhantomData, +} + +impl Default for EntitySpecializationTicks { + fn default() -> Self { + Self { + entities: MainEntityHashMap::default(), + _marker: Default::default(), + } + } +} + #[derive(Resource)] pub struct SpecializedPrepassMaterialPipelineCache { map: HashMap<(MainEntity, MainEntity), (Tick, CachedRenderPipelineId), EntityHash>, @@ -915,7 +928,7 @@ pub fn specialize_prepass_material_meshes( ResMut>>, Res, Res, - Res>>, + Res>, ), ) where M: Material, diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index fd5b28b8ec7f5..1361768546854 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -16,10 +16,6 @@ use bevy_ecs::{ use bevy_math::{ops, Mat4, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles}; use bevy_platform_support::collections::{HashMap, HashSet}; use bevy_render::extract_resource::ExtractResource; -use bevy_render::specialization::view::{GetViewKey, ViewKeyCache, ViewSpecializationTicks}; -use bevy_render::specialization::{ - EntitiesNeedingSpecialization, EntitySpecializationTicks, NeedsSpecialization, -}; use bevy_render::sync_world::MainEntityHashMap; use bevy_render::{ batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport}, @@ -1595,7 +1591,7 @@ fn despawn_entities(commands: &mut Commands, entities: Vec) { pub fn check_entities_needing_specialization( mut thread_queues: Local>>, mut needs_specialization: Query>, Changed)>, - mut entities_needing_specialization: ResMut>>, + mut entities_needing_specialization: ResMut>, mut removed_components: RemovedComponents, ) { entities_needing_specialization.entities.clear(); @@ -1716,7 +1712,7 @@ pub fn specialize_shadows( light_key_cache: Res, mut specialized_material_pipeline_cache: ResMut>, light_specialization_ticks: Res, - entity_specialization_ticks: Res>>, + entity_specialization_ticks: Res>, ticks: SystemChangeTick, ) where M::Data: PartialEq + Eq + Hash + Clone, diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 7949eee8e2fbd..2ea9e21a79f50 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -1,5 +1,3 @@ -use core::mem::size_of; - use crate::material_bind_groups::{MaterialBindGroupIndex, MaterialBindGroupSlot}; use allocator::MeshAllocator; use bevy_asset::{load_internal_asset, AssetId, UntypedAssetId}; @@ -46,8 +44,10 @@ use bevy_render::{ }; use bevy_transform::components::GlobalTransform; use bevy_utils::{default, Parallel}; +use core::mem::size_of; use material_bind_groups::MaterialBindingId; use render::skin::{self, SkinIndex}; +use std::marker::PhantomData; use tracing::{error, warn}; use self::irradiance_volume::IRRADIANCE_VOLUMES_ARE_USABLE; @@ -68,13 +68,15 @@ use bevy_core_pipeline::core_3d::Camera3d; use bevy_core_pipeline::oit::OrderIndependentTransparencySettings; use bevy_core_pipeline::prepass::{DeferredPrepass, DepthPrepass, NormalPrepass}; use bevy_core_pipeline::tonemapping::{DebandDither, Tonemapping}; +use bevy_ecs::component::Tick; use bevy_ecs::query::QueryItem; +use bevy_ecs::system::SystemChangeTick; use bevy_render::camera::TemporalJitter; +use bevy_render::extract_resource::ExtractResource; use bevy_render::prelude::Msaa; -use bevy_render::specialization::view::{GetViewKey, SpecializeViewsPlugin}; -use bevy_render::specialization::NeedsSpecialization; use bevy_render::sync_world::{MainEntity, MainEntityHashMap}; use bevy_render::view::ExtractedView; +use bevy_render::RenderSet::PrepareAssets; use bytemuck::{Pod, Zeroable}; use nonmax::{NonMaxU16, NonMaxU32}; use smallvec::{smallvec, SmallVec}; @@ -216,9 +218,14 @@ impl Plugin for MeshRenderPlugin { if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app - .add_plugins(SpecializeViewsPlugin::::default()) + .init_resource::() + .init_resource::() .init_resource::() - .init_resource::(); + .init_resource::() + .add_systems( + Render, + check_views_need_specialization.in_set(PrepareAssets), + ); let gpu_preprocessing_support = render_app.world().resource::(); @@ -296,13 +303,22 @@ impl Plugin for MeshRenderPlugin { } } -impl GetViewKey for MeshPipelineKey { - type QueryData = ( - Read, - Read, - Option>, - Option>, - Option>, +#[derive(Resource, Deref, DerefMut, Default, Debug, Clone)] +pub struct ViewKeyCache(MainEntityHashMap); + +#[derive(Resource, Deref, DerefMut, Default, Debug, Clone)] +pub struct ViewSpecializationTicks(MainEntityHashMap); + +pub fn check_views_need_specialization( + mut view_key_cache: ResMut, + mut view_specialization_ticks: ResMut, + mut views: Query<( + &MainEntity, + &ExtractedView, + &Msaa, + Option<&Tonemapping>, + Option<&DebandDither>, + Option<&ShadowFilteringMethod>, Has, ( Has, @@ -310,34 +326,35 @@ impl GetViewKey for MeshPipelineKey { Has, Has, ), - Option>, + Option<&Camera3d>, Has, - Option>, + Option<&Projection>, Has, ( Has>, Has>, ), Has, - ); - - fn get_view_key( - ( - view, - msaa, - tonemapping, - dither, - shadow_filter_method, - ssao, - (normal_prepass, depth_prepass, motion_vector_prepass, deferred_prepass), - camera_3d, - temporal_jitter, - projection, - distance_fog, - (has_environment_maps, has_irradiance_volumes), - has_oit, - ): QueryItem, - ) -> Self { + )>, + ticks: SystemChangeTick, +) { + for ( + view_entity, + view, + msaa, + tonemapping, + dither, + shadow_filter_method, + ssao, + (normal_prepass, depth_prepass, motion_vector_prepass, deferred_prepass), + camera_3d, + temporal_jitter, + projection, + distance_fog, + (has_environment_maps, has_irradiance_volumes), + has_oit, + ) in views.iter_mut() + { let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples()) | MeshPipelineKey::from_hdr(view.hdr); @@ -413,8 +430,15 @@ impl GetViewKey for MeshPipelineKey { camera_3d.screen_space_specular_transmission_quality, ); } - - view_key + if let Some(current_key) = view_key_cache.get_mut(view_entity) { + if *current_key != view_key { + view_key_cache.insert(*view_entity, view_key); + view_specialization_ticks.insert(*view_entity, ticks.this_run()); + } + } else { + view_key_cache.insert(*view_entity, view_key); + view_specialization_ticks.insert(*view_entity, ticks.this_run()); + } } } diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 0a39fb6393dad..fb4a30e97648e 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -46,7 +46,6 @@ pub mod sync_component; pub mod sync_world; pub mod texture; pub mod view; -pub mod specialization; /// The render prelude. /// diff --git a/crates/bevy_render/src/specialization/mod.rs b/crates/bevy_render/src/specialization/mod.rs deleted file mode 100644 index 1ae0f869060c3..0000000000000 --- a/crates/bevy_render/src/specialization/mod.rs +++ /dev/null @@ -1,236 +0,0 @@ -pub mod view; - -use crate::extract_resource::ExtractResource; -use crate::mesh::RenderMesh; -use crate::render_resource::{ - CachedRenderPipelineId, PipelineCache, SpecializedMeshPipeline, SpecializedMeshPipelines, -}; -use crate::specialization::view::{GetViewKey, ViewSpecializationTicks}; -use crate::sync_world::{MainEntity, MainEntityHashMap}; -use crate::{Extract, ExtractSchedule, RenderApp}; -use bevy_app::{App, Plugin, PostUpdate}; -use bevy_asset::AssetEvents; -use bevy_ecs::component::{Component, Tick}; -use bevy_ecs::entity::{Entity, EntityBorrow, EntityHash}; -use bevy_ecs::prelude::SystemSet; -use bevy_ecs::query::{QueryFilter, ROQueryItem, ReadOnlyQueryData}; -use bevy_ecs::resource::Resource; -use bevy_ecs::schedule::IntoSystemConfigs; -use bevy_ecs::schedule::IntoSystemSetConfigs; -use bevy_ecs::system::{Local, Query, Res, ResMut, SystemChangeTick, SystemParam}; -use bevy_platform_support::collections::HashMap; -use bevy_reflect::Reflect; -use bevy_utils::Parallel; -use core::marker::PhantomData; -use std::ops::{Deref, DerefMut}; -use tracing::error; - -pub struct MeshMaterialSpecializationPlugin(PhantomData); - -impl Default for MeshMaterialSpecializationPlugin { - fn default() -> Self { - Self(Default::default()) - } -} - -impl Plugin for MeshMaterialSpecializationPlugin -where - M: NeedsSpecialization, -{ - fn build(&self, app: &mut App) { - app.add_systems( - PostUpdate, - check_entities_needing_specialization::.after(AssetEvents), - ) - .init_resource::>(); - } - - fn finish(&self, app: &mut App) { - if let Some(render_app) = app.get_sub_app_mut(RenderApp) { - render_app - .add_systems(ExtractSchedule, extract_entities_needs_specialization::) - .init_resource::>() - .init_resource::>(); - } - } -} - -pub trait NeedsSpecialization: Sync + Send + 'static { - type ViewKey: GetViewKey; - type Pipeline: SpecializedMeshPipeline + Resource + Send + Sync + 'static; - type QueryData: ReadOnlyQueryData + 'static; - type QueryFilter: QueryFilter + 'static; - - fn needs_specialization(item: ROQueryItem<'_, Self::QueryData>) -> bool; -} - - -#[derive(SystemParam)] -pub struct SpecializePipelines<'w, M> -where - M: NeedsSpecialization, - <::Pipeline as SpecializedMeshPipeline>::Key: Send + Sync + 'static -{ - entity_specialization_ticks: Res<'w, EntitySpecializationTicks>, - view_specialization_ticks: - Res<'w, ViewSpecializationTicks<::ViewKey>>, - specialized_material_pipeline_cache: ResMut<'w, SpecializedMaterialPipelineCache>, - pipelines: ResMut<'w, SpecializedMeshPipelines<::Pipeline>>, - pipeline: Res<'w, ::Pipeline>, - pipeline_cache: Res<'w, PipelineCache>, - ticks: SystemChangeTick, -} - -impl SpecializePipelines<'_, M> -where - M: NeedsSpecialization, - <::Pipeline as SpecializedMeshPipeline>::Key: Send + Sync + 'static -{ - pub fn needs_specialization(&self, view_entity: MainEntity, entity: MainEntity) -> bool { - let view_tick = self - .view_specialization_ticks - .entities - .get(&view_entity) - .expect("View entity not found in specialization ticks"); - let entity_tick = self - .entity_specialization_ticks - .entities - .get(&entity) - .expect("Entity not found in specialization ticks"); - let Some((last_specialized_tick, _)) = self - .specialized_material_pipeline_cache - .get(&(view_entity, entity)) - else { - return true; - }; - - view_tick.is_newer_than(*last_specialized_tick, self.ticks.this_run()) - || entity_tick.is_newer_than(*last_specialized_tick, self.ticks.this_run()) - } - pub fn get_pipeline( - &self, - view_entity: MainEntity, - entity: MainEntity, - ) -> Option { - self.specialized_material_pipeline_cache - .get(&(view_entity, entity)) - .map(|(_, pipeline_id)| *pipeline_id) - } - - pub fn specialize_pipeline( - &mut self, - (view_entity, visible_entity): (MainEntity, MainEntity), - key: <::Pipeline as SpecializedMeshPipeline>::Key, - mesh: &RenderMesh, - ) { - let pipeline_id = - self.pipelines - .specialize(&self.pipeline_cache, &self.pipeline, key, &mesh.layout); - let pipeline_id = match pipeline_id { - Ok(id) => id, - Err(err) => { - error!("{}", err); - return; - } - }; - - self.specialized_material_pipeline_cache.insert( - (view_entity, visible_entity), - (self.ticks.this_run(), pipeline_id), - ); - } -} - -fn check_entities_needing_specialization( - mut thread_queues: Local>>, - mut needs_specialization: Query<(Entity, M::QueryData), M::QueryFilter>, - mut entities_needing_specialization: ResMut>, -) where - M: NeedsSpecialization, -{ - entities_needing_specialization.entities.clear(); - needs_specialization.par_iter_mut().for_each_init( - || thread_queues.borrow_local_mut(), - |queue, (entity, item)| { - if M::needs_specialization(item) { - queue.push(entity.into()); - } - }, - ); - - thread_queues.drain_into(&mut entities_needing_specialization.entities); -} - -pub fn extract_entities_needs_specialization( - mut entities_needing_specialization: Extract>>, - mut entity_specialization_ticks: ResMut>, - ticks: SystemChangeTick, -) where - M: NeedsSpecialization, -{ - for entity in &entities_needing_specialization.entities { - // Update the entity's specialization tick with this run's tick - entity_specialization_ticks - .entities - .insert((*entity).into(), ticks.this_run()); - } -} - -#[derive(Clone, Resource, Debug)] -pub struct EntitiesNeedingSpecialization { - pub entities: Vec, - _marker: PhantomData, -} - -impl Default for EntitiesNeedingSpecialization { - fn default() -> Self { - Self { - entities: Default::default(), - _marker: Default::default(), - } - } -} - -#[derive(Clone, Resource, Debug)] -pub struct EntitySpecializationTicks { - pub entities: MainEntityHashMap, - _marker: PhantomData, -} - -impl Default for EntitySpecializationTicks { - fn default() -> Self { - Self { - entities: MainEntityHashMap::default(), - _marker: Default::default(), - } - } -} - -#[derive(Resource)] -pub struct SpecializedMaterialPipelineCache { - map: HashMap<(MainEntity, MainEntity), (Tick, CachedRenderPipelineId), EntityHash>, - marker: PhantomData, -} - -impl Default for SpecializedMaterialPipelineCache { - fn default() -> Self { - Self { - map: HashMap::default(), - marker: PhantomData, - } - } -} - -impl Deref for SpecializedMaterialPipelineCache { - type Target = HashMap<(MainEntity, MainEntity), (Tick, CachedRenderPipelineId), EntityHash>; - - fn deref(&self) -> &Self::Target { - &self.map - } -} - -impl DerefMut for SpecializedMaterialPipelineCache { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.map - } -} diff --git a/crates/bevy_render/src/specialization/view.rs b/crates/bevy_render/src/specialization/view.rs deleted file mode 100644 index f038840031dab..0000000000000 --- a/crates/bevy_render/src/specialization/view.rs +++ /dev/null @@ -1,99 +0,0 @@ -use crate::sync_world::{MainEntity, MainEntityHashMap}; -use crate::Render; -use crate::RenderSet::PrepareAssets; -use bevy_app::{App, Plugin}; -use bevy_derive::{Deref, DerefMut}; -use bevy_ecs::component::Tick; -use bevy_ecs::entity::hash_map::EntityHashMap; -use bevy_ecs::entity::Entity; -use bevy_ecs::prelude::Resource; -use bevy_ecs::query::{QueryItem, ReadOnlyQueryData}; -use bevy_ecs::schedule::IntoSystemConfigs; -use bevy_ecs::system::{Query, ResMut, SystemChangeTick}; -use bevy_render_macros::ExtractResource; -use core::marker::PhantomData; - -pub struct SpecializeViewsPlugin(PhantomData); - -impl Plugin for SpecializeViewsPlugin -where - VK: GetViewKey, -{ - fn build(&self, app: &mut App) {} - - fn finish(&self, app: &mut App) { - app.add_systems( - Render, - check_views_need_specialization::.in_set(PrepareAssets), - ) - .init_resource::>() - .init_resource::>(); - } -} - -impl Default for SpecializeViewsPlugin { - fn default() -> Self { - Self(Default::default()) - } -} - -#[derive(Resource, Deref, DerefMut, ExtractResource, Clone)] -pub struct ViewKeyCache(MainEntityHashMap) -where - VK: GetViewKey; - -impl Default for ViewKeyCache -where - VK: GetViewKey, -{ - fn default() -> Self { - Self(MainEntityHashMap::default()) - } -} - -#[derive(Clone, Resource, Debug)] -pub struct ViewSpecializationTicks { - pub entities: MainEntityHashMap, - _marker: PhantomData, -} - -impl Default for ViewSpecializationTicks { - fn default() -> Self { - Self { - entities: MainEntityHashMap::default(), - _marker: PhantomData, - } - } -} - -pub trait GetViewKey: PartialEq + Send + Sync + 'static { - type QueryData: ReadOnlyQueryData + 'static; - - fn get_view_key<'w>(view_query: QueryItem<'w, Self::QueryData>) -> Self; -} - -pub fn check_views_need_specialization( - mut view_key_cache: ResMut>, - mut view_specialization_ticks: ResMut>, - mut views: Query<(&MainEntity, VK::QueryData)>, - ticks: SystemChangeTick, -) where - VK: GetViewKey, -{ - for (view_entity, view_query) in views.iter_mut() { - let view_key = VK::get_view_key(view_query); - if let Some(current_key) = view_key_cache.get_mut(view_entity) { - if *current_key != view_key { - view_key_cache.insert(*view_entity, view_key); - view_specialization_ticks - .entities - .insert(*view_entity, ticks.this_run()); - } - } else { - view_key_cache.insert(*view_entity, view_key); - view_specialization_ticks - .entities - .insert(*view_entity, ticks.this_run()); - } - } -} From f83ee1522f9473fa1e7f169ad1e40ac8919d5d88 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Mon, 27 Jan 2025 12:41:32 -0800 Subject: [PATCH 14/35] Cargo fmt. --- crates/bevy_pbr/src/mesh_material.rs | 4 ++-- crates/bevy_render/src/lib.rs | 3 +-- examples/3d/3d_scene.rs | 7 +------ 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/crates/bevy_pbr/src/mesh_material.rs b/crates/bevy_pbr/src/mesh_material.rs index e8d7dd6c5391c..c3f81943ecbcf 100644 --- a/crates/bevy_pbr/src/mesh_material.rs +++ b/crates/bevy_pbr/src/mesh_material.rs @@ -58,10 +58,10 @@ impl From<&MeshMaterial3d> for AssetId { } } -impl AsAssetId for MeshMaterial3d { +impl AsAssetId for MeshMaterial3d { type Asset = M; fn as_asset_id(&self) -> AssetId { self.id() } -} \ No newline at end of file +} diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index fb4a30e97648e..0f9c504edeeb9 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -267,8 +267,7 @@ pub const COLOR_OPERATIONS_SHADER_HANDLE: Handle = impl Plugin for RenderPlugin { /// Initializes the renderer, sets up the [`RenderSet`] and creates the rendering sub-app. fn build(&self, app: &mut App) { - app - .init_asset::() + app.init_asset::() .init_asset_loader::(); match &self.render_creation { diff --git a/examples/3d/3d_scene.rs b/examples/3d/3d_scene.rs index 50eac7323fa9b..21371e6a29e4a 100644 --- a/examples/3d/3d_scene.rs +++ b/examples/3d/3d_scene.rs @@ -43,9 +43,4 @@ fn setup( )); } -fn update_meshes( - mut meshes: ResMut>, - query: Query<(&Mesh3d)>, -) { - -} \ No newline at end of file +fn update_meshes(mut meshes: ResMut>, query: Query<(&Mesh3d)>) {} From 9da87b8dfb95920b3ae2a47f5154cd4674ecf08e Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Mon, 27 Jan 2025 12:49:47 -0800 Subject: [PATCH 15/35] Fix prepass. --- crates/bevy_pbr/src/prepass/mod.rs | 16 --- crates/bevy_render/src/specialization/view.rs | 99 +++++++++++++++++++ 2 files changed, 99 insertions(+), 16 deletions(-) create mode 100644 crates/bevy_render/src/specialization/view.rs diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 483638657d024..55b10c3f8dc1c 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -200,7 +200,6 @@ where .init_resource::() .init_resource::() .init_resource::>() - .init_resource::>() .add_render_command::>() .add_render_command::>() .add_render_command::>() @@ -795,21 +794,6 @@ pub fn prepare_prepass_view_bind_group( } } -#[derive(Clone, Resource, Debug)] -pub struct EntitySpecializationTicks { - pub entities: MainEntityHashMap, - _marker: PhantomData, -} - -impl Default for EntitySpecializationTicks { - fn default() -> Self { - Self { - entities: MainEntityHashMap::default(), - _marker: Default::default(), - } - } -} - #[derive(Resource)] pub struct SpecializedPrepassMaterialPipelineCache { map: HashMap<(MainEntity, MainEntity), (Tick, CachedRenderPipelineId), EntityHash>, diff --git a/crates/bevy_render/src/specialization/view.rs b/crates/bevy_render/src/specialization/view.rs new file mode 100644 index 0000000000000..f038840031dab --- /dev/null +++ b/crates/bevy_render/src/specialization/view.rs @@ -0,0 +1,99 @@ +use crate::sync_world::{MainEntity, MainEntityHashMap}; +use crate::Render; +use crate::RenderSet::PrepareAssets; +use bevy_app::{App, Plugin}; +use bevy_derive::{Deref, DerefMut}; +use bevy_ecs::component::Tick; +use bevy_ecs::entity::hash_map::EntityHashMap; +use bevy_ecs::entity::Entity; +use bevy_ecs::prelude::Resource; +use bevy_ecs::query::{QueryItem, ReadOnlyQueryData}; +use bevy_ecs::schedule::IntoSystemConfigs; +use bevy_ecs::system::{Query, ResMut, SystemChangeTick}; +use bevy_render_macros::ExtractResource; +use core::marker::PhantomData; + +pub struct SpecializeViewsPlugin(PhantomData); + +impl Plugin for SpecializeViewsPlugin +where + VK: GetViewKey, +{ + fn build(&self, app: &mut App) {} + + fn finish(&self, app: &mut App) { + app.add_systems( + Render, + check_views_need_specialization::.in_set(PrepareAssets), + ) + .init_resource::>() + .init_resource::>(); + } +} + +impl Default for SpecializeViewsPlugin { + fn default() -> Self { + Self(Default::default()) + } +} + +#[derive(Resource, Deref, DerefMut, ExtractResource, Clone)] +pub struct ViewKeyCache(MainEntityHashMap) +where + VK: GetViewKey; + +impl Default for ViewKeyCache +where + VK: GetViewKey, +{ + fn default() -> Self { + Self(MainEntityHashMap::default()) + } +} + +#[derive(Clone, Resource, Debug)] +pub struct ViewSpecializationTicks { + pub entities: MainEntityHashMap, + _marker: PhantomData, +} + +impl Default for ViewSpecializationTicks { + fn default() -> Self { + Self { + entities: MainEntityHashMap::default(), + _marker: PhantomData, + } + } +} + +pub trait GetViewKey: PartialEq + Send + Sync + 'static { + type QueryData: ReadOnlyQueryData + 'static; + + fn get_view_key<'w>(view_query: QueryItem<'w, Self::QueryData>) -> Self; +} + +pub fn check_views_need_specialization( + mut view_key_cache: ResMut>, + mut view_specialization_ticks: ResMut>, + mut views: Query<(&MainEntity, VK::QueryData)>, + ticks: SystemChangeTick, +) where + VK: GetViewKey, +{ + for (view_entity, view_query) in views.iter_mut() { + let view_key = VK::get_view_key(view_query); + if let Some(current_key) = view_key_cache.get_mut(view_entity) { + if *current_key != view_key { + view_key_cache.insert(*view_entity, view_key); + view_specialization_ticks + .entities + .insert(*view_entity, ticks.this_run()); + } + } else { + view_key_cache.insert(*view_entity, view_key); + view_specialization_ticks + .entities + .insert(*view_entity, ticks.this_run()); + } + } +} From 68fa3f01a136b8d5706f04b8fdd36c5ff4910c40 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Mon, 27 Jan 2025 13:09:46 -0800 Subject: [PATCH 16/35] 2d. --- crates/bevy_pbr/src/material.rs | 3 +- crates/bevy_render/src/specialization/view.rs | 99 ------ crates/bevy_sprite/src/mesh2d/material.rs | 319 +++++++++++++++--- crates/bevy_sprite/src/mesh2d/mesh.rs | 61 +++- 4 files changed, 336 insertions(+), 146 deletions(-) delete mode 100644 crates/bevy_render/src/specialization/view.rs diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index be60027d8fc5d..e6ecc974551a6 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -38,7 +38,6 @@ use bevy_ecs::{ use bevy_platform_support::collections::HashMap; use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::Reflect; -use bevy_render::RenderSet::PrepareAssets; use bevy_render::{ batching::gpu_preprocessing::GpuPreprocessingSupport, camera::TemporalJitter, @@ -348,7 +347,7 @@ where .add_systems( Render, ( - check_views_lights_need_specialization.in_set(PrepareAssets), + check_views_lights_need_specialization.in_set(RenderSet::PrepareAssets), specialize_shadows:: .in_set(RenderSet::PrepareAssets) .after(prepare_assets::>), diff --git a/crates/bevy_render/src/specialization/view.rs b/crates/bevy_render/src/specialization/view.rs deleted file mode 100644 index f038840031dab..0000000000000 --- a/crates/bevy_render/src/specialization/view.rs +++ /dev/null @@ -1,99 +0,0 @@ -use crate::sync_world::{MainEntity, MainEntityHashMap}; -use crate::Render; -use crate::RenderSet::PrepareAssets; -use bevy_app::{App, Plugin}; -use bevy_derive::{Deref, DerefMut}; -use bevy_ecs::component::Tick; -use bevy_ecs::entity::hash_map::EntityHashMap; -use bevy_ecs::entity::Entity; -use bevy_ecs::prelude::Resource; -use bevy_ecs::query::{QueryItem, ReadOnlyQueryData}; -use bevy_ecs::schedule::IntoSystemConfigs; -use bevy_ecs::system::{Query, ResMut, SystemChangeTick}; -use bevy_render_macros::ExtractResource; -use core::marker::PhantomData; - -pub struct SpecializeViewsPlugin(PhantomData); - -impl Plugin for SpecializeViewsPlugin -where - VK: GetViewKey, -{ - fn build(&self, app: &mut App) {} - - fn finish(&self, app: &mut App) { - app.add_systems( - Render, - check_views_need_specialization::.in_set(PrepareAssets), - ) - .init_resource::>() - .init_resource::>(); - } -} - -impl Default for SpecializeViewsPlugin { - fn default() -> Self { - Self(Default::default()) - } -} - -#[derive(Resource, Deref, DerefMut, ExtractResource, Clone)] -pub struct ViewKeyCache(MainEntityHashMap) -where - VK: GetViewKey; - -impl Default for ViewKeyCache -where - VK: GetViewKey, -{ - fn default() -> Self { - Self(MainEntityHashMap::default()) - } -} - -#[derive(Clone, Resource, Debug)] -pub struct ViewSpecializationTicks { - pub entities: MainEntityHashMap, - _marker: PhantomData, -} - -impl Default for ViewSpecializationTicks { - fn default() -> Self { - Self { - entities: MainEntityHashMap::default(), - _marker: PhantomData, - } - } -} - -pub trait GetViewKey: PartialEq + Send + Sync + 'static { - type QueryData: ReadOnlyQueryData + 'static; - - fn get_view_key<'w>(view_query: QueryItem<'w, Self::QueryData>) -> Self; -} - -pub fn check_views_need_specialization( - mut view_key_cache: ResMut>, - mut view_specialization_ticks: ResMut>, - mut views: Query<(&MainEntity, VK::QueryData)>, - ticks: SystemChangeTick, -) where - VK: GetViewKey, -{ - for (view_entity, view_query) in views.iter_mut() { - let view_key = VK::get_view_key(view_query); - if let Some(current_key) = view_key_cache.get_mut(view_entity) { - if *current_key != view_key { - view_key_cache.insert(*view_entity, view_key); - view_specialization_ticks - .entities - .insert(*view_entity, ticks.this_run()); - } - } else { - view_key_cache.insert(*view_entity, view_key); - view_specialization_ticks - .entities - .insert(*view_entity, ticks.this_run()); - } - } -} diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index b2e8af7a0c300..c258b537d3f5e 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -1,9 +1,10 @@ use crate::{ DrawMesh2d, Mesh2d, Mesh2dPipeline, Mesh2dPipelineKey, RenderMesh2dInstances, - SetMesh2dBindGroup, SetMesh2dViewBindGroup, + SetMesh2dBindGroup, SetMesh2dViewBindGroup, ViewKeyCache, ViewSpecializationTicks, }; -use bevy_app::{App, Plugin}; -use bevy_asset::{AsAssetId, Asset, AssetApp, AssetId, AssetServer, Handle}; +use bevy_app::{App, Plugin, PostUpdate}; +use bevy_asset::prelude::AssetChanged; +use bevy_asset::{AsAssetId, Asset, AssetApp, AssetEvents, AssetId, AssetServer, Handle}; use bevy_core_pipeline::{ core_2d::{ AlphaMask2d, AlphaMask2dBinKey, BatchSetKey2d, Opaque2d, Opaque2dBinKey, Transparent2d, @@ -11,13 +12,20 @@ use bevy_core_pipeline::{ tonemapping::{DebandDither, Tonemapping}, }; use bevy_derive::{Deref, DerefMut}; +use bevy_ecs::component::Tick; +use bevy_ecs::entity::EntityHash; +use bevy_ecs::system::SystemChangeTick; use bevy_ecs::{ prelude::*, system::{lifetimeless::SRes, SystemParamItem}, }; use bevy_math::FloatOrd; +use bevy_platform_support::collections::HashMap; use bevy_reflect::{prelude::ReflectDefault, Reflect}; use bevy_render::view::RenderVisibleEntities; +use bevy_render::mesh::Mesh3d; +use bevy_render::render_phase::DrawFunctionId; +use bevy_render::render_resource::CachedRenderPipelineId; use bevy_render::{ mesh::{MeshVertexBufferLayoutRef, RenderMesh}, render_asset::{ @@ -38,8 +46,10 @@ use bevy_render::{ view::{ExtractedView, Msaa, ViewVisibility}, Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; +use bevy_utils::Parallel; use core::{hash::Hash, marker::PhantomData}; use derive_more::derive::From; +use std::ops::{Deref, DerefMut}; use tracing::error; /// Materials are used alongside [`Material2dPlugin`], [`Mesh2d`], and [`MeshMaterial2d`] @@ -252,22 +262,41 @@ where { fn build(&self, app: &mut App) { app.init_asset::() + .init_resource::>() .register_type::>() - .add_plugins(RenderAssetPlugin::>::default()); + .add_plugins(RenderAssetPlugin::>::default()) + .add_systems( + PostUpdate, + check_entities_needing_specialization::.after(AssetEvents), + ); if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app + .init_resource::>() + .init_resource::>() .add_render_command::>() .add_render_command::>() .add_render_command::>() .init_resource::>() .init_resource::>>() - .add_systems(ExtractSchedule, extract_mesh_materials_2d::) + .add_systems( + ExtractSchedule, + ( + extract_entities_needs_specialization::, + extract_mesh_materials_2d::, + ), + ) .add_systems( Render, - queue_material2d_meshes:: - .in_set(RenderSet::QueueMeshes) - .after(prepare_assets::>), + ( + specialize_material2d_meshes:: + .in_set(RenderSet::PrepareAssets) + .after(prepare_assets::>) + .after(prepare_assets::), + queue_material2d_meshes:: + .in_set(RenderSet::QueueMeshes) + .after(prepare_assets::>), + ), ); } } @@ -519,10 +548,107 @@ pub const fn tonemapping_pipeline_key(tonemapping: Tonemapping) -> Mesh2dPipelin } } -pub fn queue_material2d_meshes( - opaque_draw_functions: Res>, - alpha_mask_draw_functions: Res>, - transparent_draw_functions: Res>, +pub fn extract_entities_needs_specialization( + mut entities_needing_specialization: Extract>>, + mut entity_specialization_ticks: ResMut>, + ticks: SystemChangeTick, +) where + M: Material2d, +{ + for entity in &entities_needing_specialization.entities { + // Update the entity's specialization tick with this run's tick + entity_specialization_ticks + .entities + .insert((*entity).into(), ticks.this_run()); + } +} + +#[derive(Clone, Resource, Debug)] +pub struct EntitiesNeedingSpecialization { + pub entities: Vec, + _marker: PhantomData, +} + +impl Default for EntitiesNeedingSpecialization { + fn default() -> Self { + Self { + entities: Default::default(), + _marker: Default::default(), + } + } +} + +#[derive(Clone, Resource, Debug)] +pub struct EntitySpecializationTicks { + pub entities: MainEntityHashMap, + _marker: PhantomData, +} + +impl Default for EntitySpecializationTicks { + fn default() -> Self { + Self { + entities: MainEntityHashMap::default(), + _marker: Default::default(), + } + } +} + +#[derive(Resource)] +pub struct SpecializedMaterialPipelineCache { + map: HashMap<(MainEntity, MainEntity), (Tick, CachedRenderPipelineId), EntityHash>, + marker: PhantomData, +} + +impl Default for SpecializedMaterialPipelineCache { + fn default() -> Self { + Self { + map: HashMap::default(), + marker: PhantomData, + } + } +} + +impl Deref for SpecializedMaterialPipelineCache { + type Target = HashMap<(MainEntity, MainEntity), (Tick, CachedRenderPipelineId), EntityHash>; + + fn deref(&self) -> &Self::Target { + &self.map + } +} + +impl DerefMut for SpecializedMaterialPipelineCache { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.map + } +} + +fn check_entities_needing_specialization( + mut thread_queues: Local>>, + mut needs_specialization: Query< + Entity, + Or<( + Changed, + AssetChanged, + Changed>, + AssetChanged>, + )>, + >, + mut entities_needing_specialization: ResMut>, +) where + M: Material2d, +{ + entities_needing_specialization.entities.clear(); + needs_specialization.par_iter_mut().for_each_init( + || thread_queues.borrow_local_mut(), + |queue, entity| { + queue.push(entity.into()); + }, + ); + + thread_queues.drain_into(&mut entities_needing_specialization.entities); +} + +pub fn specialize_material2d_meshes( material2d_pipeline: Res>, mut pipelines: ResMut>>, pipeline_cache: Res, @@ -536,12 +662,18 @@ pub fn queue_material2d_meshes( mut opaque_render_phases: ResMut>, mut alpha_mask_render_phases: ResMut>, views: Query<( + &MainEntity, &ExtractedView, &RenderVisibleEntities, &Msaa, Option<&Tonemapping>, Option<&DebandDither>, )>, + view_key_cache: Res, + entity_specialization_ticks: Res>, + view_specialization_ticks: Res, + ticks: SystemChangeTick, + mut specialized_material_pipeline_cache: ResMut>, ) where M::Data: PartialEq + Eq + Hash + Clone, { @@ -549,36 +681,40 @@ pub fn queue_material2d_meshes( return; } - for (view, visible_entities, msaa, tonemapping, dither) in &views { - let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity) - else { - continue; - }; - let Some(opaque_phase) = opaque_render_phases.get_mut(&view.retained_view_entity) else { + for (view_entity, view, visible_entities, msaa, tonemapping, dither) in &views { + if !transparent_render_phases.contains_key(&view.retained_view_entity) + && !opaque_render_phases.contains_key(&view.retained_view_entity) + && !alpha_mask_render_phases.contains_key(&view.retained_view_entity) + { continue; - }; - let Some(alpha_mask_phase) = alpha_mask_render_phases.get_mut(&view.retained_view_entity) - else { + } + + let Some(view_key) = view_key_cache.get(view_entity) else { continue; }; - let draw_transparent_2d = transparent_draw_functions.read().id::>(); - let draw_opaque_2d = opaque_draw_functions.read().id::>(); - let draw_alpha_mask_2d = alpha_mask_draw_functions.read().id::>(); - - let mut view_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples()) - | Mesh2dPipelineKey::from_hdr(view.hdr); - - if !view.hdr { - if let Some(tonemapping) = tonemapping { - view_key |= Mesh2dPipelineKey::TONEMAP_IN_SHADER; - view_key |= tonemapping_pipeline_key(*tonemapping); - } - if let Some(DebandDither::Enabled) = dither { - view_key |= Mesh2dPipelineKey::DEBAND_DITHER; + for (_, visible_entity) in visible_entities.iter::() { + let view_tick = view_specialization_ticks + .get(view_entity) + .expect("View entity not found in specialization ticks"); + let entity_tick = entity_specialization_ticks + .entities + .get(visible_entity) + .expect("Entity not found in specialization ticks"); + let last_specialized_tick = specialized_material_pipeline_cache + .get(&(*view_entity, *visible_entity)) + .map(|(tick, _)| *tick); + let needs_specialization = + last_specialized_tick.map_or(true, |last_specialized_tick| { + let view_changed = + view_tick.is_newer_than(last_specialized_tick, ticks.this_run()); + let entity_changed = + entity_tick.is_newer_than(last_specialized_tick, ticks.this_run()); + view_changed || entity_changed + }); + if !needs_specialization { + continue; } - } - for (render_entity, visible_entity) in visible_entities.iter::() { let Some(material_asset_id) = render_material_instances.get(visible_entity) else { continue; }; @@ -591,7 +727,7 @@ pub fn queue_material2d_meshes( let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else { continue; }; - let mesh_key = view_key + let mesh_key = *view_key | Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology()) | material_2d.properties.mesh_pipeline_key_bits; @@ -613,6 +749,76 @@ pub fn queue_material2d_meshes( } }; + specialized_material_pipeline_cache.insert( + (*view_entity, *visible_entity), + (ticks.this_run(), pipeline_id), + ); + } + } +} + +pub fn queue_material2d_meshes( + material2d_pipeline: Res>, + mut pipelines: ResMut>>, + pipeline_cache: Res, + (render_meshes, render_materials): ( + Res>, + Res>>, + ), + mut render_mesh_instances: ResMut, + render_material_instances: Res>, + mut transparent_render_phases: ResMut>, + mut opaque_render_phases: ResMut>, + mut alpha_mask_render_phases: ResMut>, + views: Query<( + &MainEntity, + &ExtractedView, + &RenderVisibleEntities, + &Msaa, + Option<&Tonemapping>, + Option<&DebandDither>, + )>, + specialized_material_pipeline_cache: ResMut>, +) where + M::Data: PartialEq + Eq + Hash + Clone, +{ + if render_material_instances.is_empty() { + return; + } + + for (view_entity, view, visible_entities, msaa, tonemapping, dither) in &views { + let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity) + else { + continue; + }; + let Some(opaque_phase) = opaque_render_phases.get_mut(&view.retained_view_entity) else { + continue; + }; + let Some(alpha_mask_phase) = alpha_mask_render_phases.get_mut(&view.retained_view_entity) + else { + continue; + }; + + for (render_entity, visible_entity) in visible_entities.iter::() { + let Some(pipeline_id) = specialized_material_pipeline_cache + .get(&(*view_entity, *visible_entity)) + .map(|(_, pipeline_id)| *pipeline_id) + else { + continue; + }; + let Some(material_asset_id) = render_material_instances.get(visible_entity) else { + continue; + }; + let Some(mesh_instance) = render_mesh_instances.get_mut(visible_entity) else { + continue; + }; + let Some(material_2d) = render_materials.get(*material_asset_id) else { + continue; + }; + let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else { + continue; + }; + mesh_instance.material_bind_group_id = material_2d.get_bind_group_id(); let mesh_z = mesh_instance.transforms.world_from_local.translation.z; @@ -631,7 +837,7 @@ pub fn queue_material2d_meshes( AlphaMode2d::Opaque => { let bin_key = Opaque2dBinKey { pipeline: pipeline_id, - draw_function: draw_opaque_2d, + draw_function: material_2d.properties.draw_function_id, asset_id: mesh_instance.mesh_asset_id.into(), material_bind_group_id: material_2d.get_bind_group_id().0, }; @@ -647,7 +853,7 @@ pub fn queue_material2d_meshes( AlphaMode2d::Mask(_) => { let bin_key = AlphaMask2dBinKey { pipeline: pipeline_id, - draw_function: draw_alpha_mask_2d, + draw_function: material_2d.properties.draw_function_id, asset_id: mesh_instance.mesh_asset_id.into(), material_bind_group_id: material_2d.get_bind_group_id().0, }; @@ -663,7 +869,7 @@ pub fn queue_material2d_meshes( AlphaMode2d::Blend => { transparent_phase.add(Transparent2d { entity: (*render_entity, *visible_entity), - draw_function: draw_transparent_2d, + draw_function: material_2d.properties.draw_function_id, pipeline: pipeline_id, // NOTE: Back-to-front ordering for transparent with ascending sort means far should have the // lowest sort key and getting closer should increase. As we have @@ -697,6 +903,7 @@ pub struct Material2dProperties { /// These are precalculated so that we can just "or" them together in /// [`queue_material2d_meshes`]. pub mesh_pipeline_key_bits: Mesh2dPipelineKey, + pub draw_function_id: DrawFunctionId, } /// Data prepared for a [`Material2d`] instance. @@ -716,17 +923,42 @@ impl PreparedMaterial2d { impl RenderAsset for PreparedMaterial2d { type SourceAsset = M; - type Param = (SRes, SRes>, M::Param); + type Param = ( + SRes, + SRes>, + SRes>, + SRes>, + SRes>, + M::Param, + ); fn prepare_asset( material: Self::SourceAsset, _: AssetId, - (render_device, pipeline, material_param): &mut SystemParamItem, + ( + render_device, + pipeline, + opaque_draw_functions, + alpha_mask_draw_functions, + transparent_draw_functions, + material_param, + ): &mut SystemParamItem, ) -> Result> { match material.as_bind_group(&pipeline.material2d_layout, render_device, material_param) { Ok(prepared) => { let mut mesh_pipeline_key_bits = Mesh2dPipelineKey::empty(); mesh_pipeline_key_bits.insert(alpha_mode_pipeline_key(material.alpha_mode())); + + let draw_function_id = match material.alpha_mode() { + AlphaMode2d::Opaque => opaque_draw_functions.read().id::>(), + AlphaMode2d::Mask(_) => { + alpha_mask_draw_functions.read().id::>() + } + AlphaMode2d::Blend => { + transparent_draw_functions.read().id::>() + } + }; + Ok(PreparedMaterial2d { bindings: prepared.bindings, bind_group: prepared.bind_group, @@ -735,6 +967,7 @@ impl RenderAsset for PreparedMaterial2d { depth_bias: material.depth_bias(), alpha_mode: material.alpha_mode(), mesh_pipeline_key_bits, + draw_function_id, }, }) } diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index 86d02d5680b8b..c126f8fdc6e46 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -1,7 +1,8 @@ use bevy_app::Plugin; use bevy_asset::{load_internal_asset, AssetId, Handle}; -use crate::Material2dBindGroupId; +use crate::{tonemapping_pipeline_key, Material2dBindGroupId}; +use bevy_core_pipeline::tonemapping::DebandDither; use bevy_core_pipeline::{ core_2d::{AlphaMask2d, Camera2d, Opaque2d, Transparent2d, CORE_2D_DEPTH_FORMAT}, tonemapping::{ @@ -9,6 +10,8 @@ use bevy_core_pipeline::{ }, }; use bevy_derive::{Deref, DerefMut}; +use bevy_ecs::component::Tick; +use bevy_ecs::system::SystemChangeTick; use bevy_ecs::{ prelude::*, query::ROQueryItem, @@ -16,6 +19,9 @@ use bevy_ecs::{ }; use bevy_image::{BevyDefault, Image, ImageSampler, TextureFormatPixelInfo}; use bevy_math::{Affine3, Vec4}; +use bevy_render::prelude::Msaa; +use bevy_render::view::RenderVisibleEntities; +use bevy_render::RenderSet::PrepareAssets; use bevy_render::{ batching::{ gpu_preprocessing::IndirectParametersMetadata, @@ -94,6 +100,7 @@ impl Plugin for Mesh2dRenderPlugin { if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app + .init_resource::() .init_resource::() .init_resource::>() .add_systems(ExtractSchedule, extract_mesh2d) @@ -137,7 +144,13 @@ impl Plugin for Mesh2dRenderPlugin { render_app .insert_resource(batched_instance_buffer) - .init_resource::(); + .init_resource::() + .init_resource::() + .init_resource::() + .add_systems( + Render, + check_views_need_specialization.in_set(PrepareAssets), + ); } // Load the mesh_bindings shader module here as it depends on runtime information about @@ -152,6 +165,50 @@ impl Plugin for Mesh2dRenderPlugin { } } +#[derive(Resource, Deref, DerefMut, Default, Debug, Clone)] +pub struct ViewKeyCache(MainEntityHashMap); + +#[derive(Resource, Deref, DerefMut, Default, Debug, Clone)] +pub struct ViewSpecializationTicks(MainEntityHashMap); + +pub fn check_views_need_specialization( + mut view_key_cache: ResMut, + mut view_specialization_ticks: ResMut, + views: Query<( + &MainEntity, + &ExtractedView, + &Msaa, + Option<&Tonemapping>, + Option<&DebandDither>, + )>, + ticks: SystemChangeTick, +) { + for (view_entity, view, msaa, tonemapping, dither) in &views { + let mut view_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples()) + | Mesh2dPipelineKey::from_hdr(view.hdr); + + if !view.hdr { + if let Some(tonemapping) = tonemapping { + view_key |= Mesh2dPipelineKey::TONEMAP_IN_SHADER; + view_key |= tonemapping_pipeline_key(*tonemapping); + } + if let Some(DebandDither::Enabled) = dither { + view_key |= Mesh2dPipelineKey::DEBAND_DITHER; + } + } + + if let Some(current_key) = view_key_cache.get_mut(view_entity) { + if *current_key != view_key { + view_key_cache.insert(*view_entity, view_key); + view_specialization_ticks.insert(*view_entity, ticks.this_run()); + } + } else { + view_key_cache.insert(*view_entity, view_key); + view_specialization_ticks.insert(*view_entity, ticks.this_run()); + } + } +} + #[derive(Component)] pub struct Mesh2dTransforms { pub world_from_local: Affine3, From 7129629df786d4568723cb2b781ecd853459fc88 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Mon, 27 Jan 2025 13:56:01 -0800 Subject: [PATCH 17/35] Cleanup. --- examples/3d/3d_scene.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/examples/3d/3d_scene.rs b/examples/3d/3d_scene.rs index 21371e6a29e4a..5ea7e20b29662 100644 --- a/examples/3d/3d_scene.rs +++ b/examples/3d/3d_scene.rs @@ -6,7 +6,6 @@ fn main() { App::new() .add_plugins(DefaultPlugins) .add_systems(Startup, setup) - .add_systems(Update, update_meshes) .run(); } @@ -42,5 +41,3 @@ fn setup( Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y), )); } - -fn update_meshes(mut meshes: ResMut>, query: Query<(&Mesh3d)>) {} From f46864d61eb8e7c4c72a4bef2245f23688c2ebfe Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Mon, 27 Jan 2025 13:59:09 -0800 Subject: [PATCH 18/35] Remove dep. --- crates/bevy_render/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/bevy_render/Cargo.toml b/crates/bevy_render/Cargo.toml index 577a082acbcea..4962125622270 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -96,7 +96,6 @@ smallvec = { version = "1.11", features = ["const_new"] } offset-allocator = "0.2" variadics_please = "1.1" tracing = { version = "0.1", default-features = false, features = ["std"] } -crossbeam-channel = "0.5" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] # Omit the `glsl` feature in non-WebAssembly by default. From da672a91d21ea8266a6631b8075c0a589672feba Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Mon, 27 Jan 2025 14:05:26 -0800 Subject: [PATCH 19/35] Updates from rebase. --- crates/bevy_pbr/src/prepass/mod.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 55b10c3f8dc1c..1b5f7518bd653 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -1,13 +1,6 @@ mod prepass_bindings; -use crate::{ - alpha_mode_pipeline_key, binding_arrays_are_usable, buffer_layout, - material_bind_groups::MaterialBindGroupAllocator, queue_material_meshes, - setup_morph_and_skinning_defs, skin, DrawMesh, Material, MaterialPipeline, MaterialPipelineKey, - MeshLayouts, MeshPipeline, MeshPipelineKey, OpaqueRendererMethod, PreparedMaterial, - RenderLightmaps, RenderMaterialInstances, RenderMeshInstanceFlags, RenderMeshInstances, - SetMaterialBindGroup, SetMeshBindGroup, ShadowView, StandardMaterial, -}; +use crate::{alpha_mode_pipeline_key, binding_arrays_are_usable, buffer_layout, material_bind_groups::MaterialBindGroupAllocator, queue_material_meshes, setup_morph_and_skinning_defs, skin, DrawMesh, EntitySpecializationTicks, Material, MaterialPipeline, MaterialPipelineKey, MeshLayouts, MeshPipeline, MeshPipelineKey, OpaqueRendererMethod, PreparedMaterial, RenderLightmaps, RenderMaterialInstances, RenderMeshInstanceFlags, RenderMeshInstances, RenderPhaseType, SetMaterialBindGroup, SetMeshBindGroup, ShadowView, StandardMaterial}; use bevy_app::{App, Plugin, PreUpdate}; use bevy_render::{ alpha::AlphaMode, From ba3f95a8cb6ae317d5ec0d496cc0820ff7da9800 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Wed, 29 Jan 2025 22:31:33 -0800 Subject: [PATCH 20/35] Fix SSR. --- crates/bevy_pbr/src/material.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index e6ecc974551a6..fd3b573adaeb2 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -1018,6 +1018,9 @@ pub fn queue_material_meshes( }); } RenderPhaseType::Opaque => { + if material.properties.render_method == OpaqueRendererMethod::Deferred { + continue; + } let batch_set_key = Opaque3dBatchSetKey { pipeline: pipeline_id, draw_function: material.properties.draw_function_id, From 987688dcba338d42f54d340c6746ca5fcc5009fb Mon Sep 17 00:00:00 2001 From: charlotte Date: Wed, 29 Jan 2025 22:33:39 -0800 Subject: [PATCH 21/35] Update crates/bevy_sprite/src/mesh2d/mesh.rs Co-authored-by: Patrick Walton --- crates/bevy_sprite/src/mesh2d/mesh.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index c126f8fdc6e46..2632651033e4f 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -197,12 +197,7 @@ pub fn check_views_need_specialization( } } - if let Some(current_key) = view_key_cache.get_mut(view_entity) { - if *current_key != view_key { - view_key_cache.insert(*view_entity, view_key); - view_specialization_ticks.insert(*view_entity, ticks.this_run()); - } - } else { + if !view_key_cache.get_mut(view_entity).is_some_and(|current_key| **current_key == view_key) { view_key_cache.insert(*view_entity, view_key); view_specialization_ticks.insert(*view_entity, ticks.this_run()); } From 6e02ac43c10e2dd71cbe567cd89fccb66f1d1288 Mon Sep 17 00:00:00 2001 From: charlotte Date: Wed, 29 Jan 2025 22:53:05 -0800 Subject: [PATCH 22/35] Update crates/bevy_pbr/src/material.rs Co-authored-by: Patrick Walton --- crates/bevy_pbr/src/material.rs | 31 +++++++------------------------ 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index fd3b573adaeb2..6d59ebeb19df8 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -1259,30 +1259,13 @@ impl RenderAsset for PreparedMaterial { mesh_pipeline_key_bits.contains(MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE); let render_phase_type = match material.alpha_mode() { - AlphaMode::Opaque => { - if reads_view_transmission_texture { - RenderPhaseType::Transmissive - } else { - RenderPhaseType::Opaque - } - } - AlphaMode::Mask(_) => { - if reads_view_transmission_texture { - RenderPhaseType::Transmissive - } else { - RenderPhaseType::AlphaMask - } - } - AlphaMode::AlphaToCoverage => { - if reads_view_transmission_texture { - RenderPhaseType::Transmissive - } else { - RenderPhaseType::Opaque - } - } - AlphaMode::Blend | AlphaMode::Premultiplied | AlphaMode::Add | AlphaMode::Multiply => { - RenderPhaseType::Transparent - } + AlphaMode::Blend | + AlphaMode::Premultiplied | + AlphaMode::Add | + AlphaMode::Multiply => RenderPhaseType::Transparent, + _ if reads_view_transmission_texture => RenderPhaseType::Transmissive, + AlphaMode::Opaque | AlphaMode::AlphaToCoverage => RenderPhaseType::Opaque, + AlphaMode::Mask(_) => RenderPhaseType::AlphaMask, }; let draw_function_id = match render_phase_type { From 1f06ac69a250205e9cbb2c810d2ed35b691cc364 Mon Sep 17 00:00:00 2001 From: charlotte Date: Wed, 29 Jan 2025 22:54:22 -0800 Subject: [PATCH 23/35] Update crates/bevy_pbr/src/render/mesh.rs Co-authored-by: Patrick Walton --- crates/bevy_pbr/src/render/mesh.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 2ea9e21a79f50..f4d59c0e85b8e 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -430,12 +430,7 @@ pub fn check_views_need_specialization( camera_3d.screen_space_specular_transmission_quality, ); } - if let Some(current_key) = view_key_cache.get_mut(view_entity) { - if *current_key != view_key { - view_key_cache.insert(*view_entity, view_key); - view_specialization_ticks.insert(*view_entity, ticks.this_run()); - } - } else { + if !view_key_cache.get_mut(view_entity).is_some_and(|current_key| **current_key == view_key) { view_key_cache.insert(*view_entity, view_key); view_specialization_ticks.insert(*view_entity, ticks.this_run()); } From fee692bdd30543b6285ae182e446f51ea3f7b203 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Wed, 29 Jan 2025 22:55:52 -0800 Subject: [PATCH 24/35] Respond to review. --- crates/bevy_pbr/src/material.rs | 77 +++++++---------------- crates/bevy_pbr/src/prepass/mod.rs | 53 ++++++---------- crates/bevy_pbr/src/render/light.rs | 54 ++++------------ crates/bevy_pbr/src/render/mesh.rs | 5 +- crates/bevy_render/src/view/mod.rs | 10 +++ crates/bevy_sprite/src/mesh2d/material.rs | 73 +++++++-------------- crates/bevy_sprite/src/mesh2d/mesh.rs | 5 +- 7 files changed, 93 insertions(+), 184 deletions(-) diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 6d59ebeb19df8..6e2565d22c78a 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -705,9 +705,7 @@ pub fn extract_entities_needs_specialization( { for entity in &entities_needing_specialization.entities { // Update the entity's specialization tick with this run's tick - entity_specialization_ticks - .entities - .insert((*entity).into(), ticks.this_run()); + entity_specialization_ticks.insert((*entity).into(), ticks.this_run()); } } @@ -726,8 +724,9 @@ impl Default for EntitiesNeedingSpecialization { } } -#[derive(Clone, Resource, Debug)] +#[derive(Clone, Resource, Deref, DerefMut, Debug)] pub struct EntitySpecializationTicks { + #[deref] pub entities: MainEntityHashMap, _marker: PhantomData, } @@ -741,8 +740,10 @@ impl Default for EntitySpecializationTicks { } } -#[derive(Resource)] +#[derive(Resource, Deref, DerefMut)] pub struct SpecializedMaterialPipelineCache { + // (view_entity, material_entity) -> (tick, pipeline_id) + #[deref] map: HashMap<(MainEntity, MainEntity), (Tick, CachedRenderPipelineId), EntityHash>, marker: PhantomData, } @@ -756,23 +757,8 @@ impl Default for SpecializedMaterialPipelineCache { } } -impl Deref for SpecializedMaterialPipelineCache { - type Target = HashMap<(MainEntity, MainEntity), (Tick, CachedRenderPipelineId), EntityHash>; - - fn deref(&self) -> &Self::Target { - &self.map - } -} - -impl DerefMut for SpecializedMaterialPipelineCache { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.map - } -} - fn check_entities_needing_specialization( - mut thread_queues: Local>>, - mut needs_specialization: Query< + needs_specialization: Query< Entity, Or<( Changed, @@ -786,14 +772,9 @@ fn check_entities_needing_specialization( M: Material, { entities_needing_specialization.entities.clear(); - needs_specialization.par_iter_mut().for_each_init( - || thread_queues.borrow_local_mut(), - |queue, entity| { - queue.push(entity.into()); - }, - ); - - thread_queues.drain_into(&mut entities_needing_specialization.entities); + for entity in &needs_specialization { + entities_needing_specialization.entities.push(entity); + } } pub fn specialize_material_meshes( @@ -842,24 +823,15 @@ pub fn specialize_material_meshes( }; for (_, visible_entity) in visible_entities.iter::() { - let view_tick = view_specialization_ticks - .get(view_entity) - .expect("View entity not found in specialization ticks"); - let entity_tick = entity_specialization_ticks - .entities - .get(visible_entity) - .expect("Entity not found in specialization ticks"); + let view_tick = view_specialization_ticks.get(view_entity).unwrap(); + let entity_tick = entity_specialization_ticks.get(visible_entity).unwrap(); let last_specialized_tick = specialized_material_pipeline_cache .get(&(*view_entity, *visible_entity)) .map(|(tick, _)| *tick); - let needs_specialization = - last_specialized_tick.map_or(true, |last_specialized_tick| { - let view_changed = - view_tick.is_newer_than(last_specialized_tick, ticks.this_run()); - let entity_changed = - entity_tick.is_newer_than(last_specialized_tick, ticks.this_run()); - view_changed || entity_changed - }); + let needs_specialization = last_specialized_tick.is_none_or(|tick| { + view_tick.is_newer_than(tick, ticks.this_run()) + || entity_tick.is_newer_than(tick, ticks.this_run()) + }); if !needs_specialization { continue; } @@ -885,13 +857,7 @@ pub fn specialize_material_meshes( let mut mesh_pipeline_key_bits = material.properties.mesh_pipeline_key_bits; mesh_pipeline_key_bits.insert(alpha_mode_pipeline_key( material.properties.alpha_mode, - match view_key.msaa_samples() { - 1 => &Msaa::Off, - 2 => &Msaa::Sample2, - 4 => &Msaa::Sample4, - 8 => &Msaa::Sample8, - _ => unreachable!("Unsupported MSAA sample count"), - }, + &Msaa::from_samples(view_key.msaa_samples()), )); let mut mesh_key = *view_key | MeshPipelineKey::from_bits_retain(mesh.key_bits.bits()) @@ -936,7 +902,7 @@ pub fn specialize_material_meshes( Ok(id) => id, Err(err) => { error!("{}", err); - return; + continue; } }; @@ -1259,10 +1225,9 @@ impl RenderAsset for PreparedMaterial { mesh_pipeline_key_bits.contains(MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE); let render_phase_type = match material.alpha_mode() { - AlphaMode::Blend | - AlphaMode::Premultiplied | - AlphaMode::Add | - AlphaMode::Multiply => RenderPhaseType::Transparent, + AlphaMode::Blend | AlphaMode::Premultiplied | AlphaMode::Add | AlphaMode::Multiply => { + RenderPhaseType::Transparent + } _ if reads_view_transmission_texture => RenderPhaseType::Transmissive, AlphaMode::Opaque | AlphaMode::AlphaToCoverage => RenderPhaseType::Opaque, AlphaMode::Mask(_) => RenderPhaseType::AlphaMask, diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 1b5f7518bd653..28811f04fe74e 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -1,6 +1,14 @@ mod prepass_bindings; -use crate::{alpha_mode_pipeline_key, binding_arrays_are_usable, buffer_layout, material_bind_groups::MaterialBindGroupAllocator, queue_material_meshes, setup_morph_and_skinning_defs, skin, DrawMesh, EntitySpecializationTicks, Material, MaterialPipeline, MaterialPipelineKey, MeshLayouts, MeshPipeline, MeshPipelineKey, OpaqueRendererMethod, PreparedMaterial, RenderLightmaps, RenderMaterialInstances, RenderMeshInstanceFlags, RenderMeshInstances, RenderPhaseType, SetMaterialBindGroup, SetMeshBindGroup, ShadowView, StandardMaterial}; +use crate::{ + alpha_mode_pipeline_key, binding_arrays_are_usable, buffer_layout, + material_bind_groups::MaterialBindGroupAllocator, queue_material_meshes, + setup_morph_and_skinning_defs, skin, DrawMesh, EntitySpecializationTicks, Material, + MaterialPipeline, MaterialPipelineKey, MeshLayouts, MeshPipeline, MeshPipelineKey, + OpaqueRendererMethod, PreparedMaterial, RenderLightmaps, RenderMaterialInstances, + RenderMeshInstanceFlags, RenderMeshInstances, RenderPhaseType, SetMaterialBindGroup, + SetMeshBindGroup, ShadowView, StandardMaterial, +}; use bevy_app::{App, Plugin, PreUpdate}; use bevy_render::{ alpha::AlphaMode, @@ -787,8 +795,10 @@ pub fn prepare_prepass_view_bind_group( } } -#[derive(Resource)] +#[derive(Resource, Deref, DerefMut)] pub struct SpecializedPrepassMaterialPipelineCache { + // (view_entity, material_entity) -> (tick, pipeline_id) + #[deref] map: HashMap<(MainEntity, MainEntity), (Tick, CachedRenderPipelineId), EntityHash>, marker: PhantomData, } @@ -802,20 +812,6 @@ impl Default for SpecializedPrepassMaterialPipelineCache { } } -impl Deref for SpecializedPrepassMaterialPipelineCache { - type Target = HashMap<(MainEntity, MainEntity), (Tick, CachedRenderPipelineId), EntityHash>; - - fn deref(&self) -> &Self::Target { - &self.map - } -} - -impl DerefMut for SpecializedPrepassMaterialPipelineCache { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.map - } -} - #[derive(Resource, Deref, DerefMut, Default, Clone)] pub struct ViewKeyPrepassCache(MainEntityHashMap); @@ -874,8 +870,6 @@ pub fn specialize_prepass_material_meshes( &ExtractedView, &RenderVisibleEntities, &Msaa, - Option<&DepthPrepass>, - Option<&NormalPrepass>, Option<&MotionVectorPrepass>, Option<&DeferredPrepass>, )>, @@ -916,8 +910,6 @@ pub fn specialize_prepass_material_meshes( extracted_view, visible_entities, msaa, - depth_prepass, - normal_prepass, motion_vector_prepass, deferred_prepass, ) in &views @@ -935,24 +927,15 @@ pub fn specialize_prepass_material_meshes( }; for (_, visible_entity) in visible_entities.iter::() { - let view_tick = view_specialization_ticks - .get(view_entity) - .expect("View entity not found in specialization ticks"); - let entity_tick = entity_specialization_ticks - .entities - .get(visible_entity) - .expect("Entity not found in specialization ticks"); + let view_tick = view_specialization_ticks.get(view_entity).unwrap(); + let entity_tick = entity_specialization_ticks.get(visible_entity).unwrap(); let last_specialized_tick = specialized_material_pipeline_cache .get(&(*view_entity, *visible_entity)) .map(|(tick, _)| *tick); - let needs_specialization = - last_specialized_tick.map_or(true, |last_specialized_tick| { - let view_changed = - view_tick.is_newer_than(last_specialized_tick, ticks.this_run()); - let entity_changed = - entity_tick.is_newer_than(last_specialized_tick, ticks.this_run()); - view_changed || entity_changed - }); + let needs_specialization = last_specialized_tick.is_none_or(|tick| { + view_tick.is_newer_than(tick, ticks.this_run()) + || entity_tick.is_newer_than(tick, ticks.this_run()) + }); if !needs_specialization { continue; } diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 1361768546854..ef8afb4dba61c 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -1589,37 +1589,32 @@ fn despawn_entities(commands: &mut Commands, entities: Vec) { /// These will be extracted in the material extraction pub fn check_entities_needing_specialization( - mut thread_queues: Local>>, - mut needs_specialization: Query>, Changed)>, + needs_specialization: Query>, Changed)>, mut entities_needing_specialization: ResMut>, mut removed_components: RemovedComponents, ) { entities_needing_specialization.entities.clear(); - needs_specialization.par_iter_mut().for_each_init( - || thread_queues.borrow_local_mut(), - |queue, entity| { - queue.push(entity.into()); - }, - ); + for entity in &needs_specialization { + entities_needing_specialization.entities.push(entity); + } for removed in removed_components.read() { entities_needing_specialization .entities .push(removed.into()); } - - thread_queues.drain_into(&mut entities_needing_specialization.entities); } -//ODO: Are these render entities or main entities?These are ex #[derive(Resource, Deref, DerefMut, Default, Debug, Clone)] pub struct LightKeyCache(EntityHashMap); #[derive(Resource, Deref, DerefMut, Default, Debug, Clone)] pub struct LightSpecializationTicks(EntityHashMap); -#[derive(Resource)] +#[derive(Resource, Deref, DerefMut)] pub struct SpecializedShadowMaterialPipelineCache { + // (view_light_entity, visible_entity) -> (tick, pipeline_id) + #[deref] map: HashMap<(Entity, MainEntity), (Tick, CachedRenderPipelineId), EntityHash>, marker: PhantomData, } @@ -1633,20 +1628,6 @@ impl Default for SpecializedShadowMaterialPipelineCache { } } -impl Deref for SpecializedShadowMaterialPipelineCache { - type Target = HashMap<(Entity, MainEntity), (Tick, CachedRenderPipelineId), EntityHash>; - - fn deref(&self) -> &Self::Target { - &self.map - } -} - -impl DerefMut for SpecializedShadowMaterialPipelineCache { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.map - } -} - pub fn check_views_lights_need_specialization( view_lights: Query<(Entity, &ViewLightEntities), With>, view_light_entities: Query<(&LightEntity, &ExtractedView)>, @@ -1759,24 +1740,15 @@ pub fn specialize_shadows( // so no meshes will be queued for (_, visible_entity) in visible_entities.iter().copied() { - let view_tick = light_specialization_ticks - .get(&view_light_entity) - .expect("View entity not found in specialization ticks"); - let entity_tick = entity_specialization_ticks - .entities - .get(&visible_entity) - .expect("Entity not found in specialization ticks"); + let view_tick = light_specialization_ticks.get(&view_light_entity).unwrap(); + let entity_tick = entity_specialization_ticks.get(&visible_entity).unwrap(); let last_specialized_tick = specialized_material_pipeline_cache .get(&(view_light_entity, visible_entity)) .map(|(tick, _)| *tick); - let needs_specialization = - last_specialized_tick.map_or(true, |last_specialized_tick| { - let view_changed = - view_tick.is_newer_than(last_specialized_tick, ticks.this_run()); - let entity_changed = - entity_tick.is_newer_than(last_specialized_tick, ticks.this_run()); - view_changed || entity_changed - }); + let needs_specialization = last_specialized_tick.is_none_or(|tick| { + view_tick.is_newer_than(tick, ticks.this_run()) + || entity_tick.is_newer_than(tick, ticks.this_run()) + }); if !needs_specialization { continue; } diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index f4d59c0e85b8e..b95a1d799ba6d 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -430,7 +430,10 @@ pub fn check_views_need_specialization( camera_3d.screen_space_specular_transmission_quality, ); } - if !view_key_cache.get_mut(view_entity).is_some_and(|current_key| **current_key == view_key) { + if !view_key_cache + .get_mut(view_entity) + .is_some_and(|current_key| **current_key == view_key) + { view_key_cache.insert(*view_entity, view_key); view_specialization_ticks.insert(*view_entity, ticks.this_run()); } diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs index a3a5118c30cca..f046568dc59ae 100644 --- a/crates/bevy_render/src/view/mod.rs +++ b/crates/bevy_render/src/view/mod.rs @@ -186,6 +186,16 @@ impl Msaa { pub fn samples(&self) -> u32 { *self as u32 } + + pub fn from_samples(samples: u32) -> Self { + match samples { + 1 => Msaa::Off, + 2 => Msaa::Sample2, + 4 => Msaa::Sample4, + 8 => Msaa::Sample8, + _ => panic!("Unsupported MSAA sample count: {}", samples), + } + } } /// An identifier for a view that is stable across frames. diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index c258b537d3f5e..d3452962543e4 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -22,10 +22,10 @@ use bevy_ecs::{ use bevy_math::FloatOrd; use bevy_platform_support::collections::HashMap; use bevy_reflect::{prelude::ReflectDefault, Reflect}; -use bevy_render::view::RenderVisibleEntities; use bevy_render::mesh::Mesh3d; use bevy_render::render_phase::DrawFunctionId; use bevy_render::render_resource::CachedRenderPipelineId; +use bevy_render::view::RenderVisibleEntities; use bevy_render::{ mesh::{MeshVertexBufferLayoutRef, RenderMesh}, render_asset::{ @@ -273,7 +273,7 @@ where if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app .init_resource::>() - .init_resource::>() + .init_resource::>() .add_render_command::>() .add_render_command::>() .add_render_command::>() @@ -557,9 +557,7 @@ pub fn extract_entities_needs_specialization( { for entity in &entities_needing_specialization.entities { // Update the entity's specialization tick with this run's tick - entity_specialization_ticks - .entities - .insert((*entity).into(), ticks.this_run()); + entity_specialization_ticks.insert((*entity).into(), ticks.this_run()); } } @@ -578,8 +576,9 @@ impl Default for EntitiesNeedingSpecialization { } } -#[derive(Clone, Resource, Debug)] +#[derive(Clone, Resource, Deref, DerefMut, Debug)] pub struct EntitySpecializationTicks { + #[deref] pub entities: MainEntityHashMap, _marker: PhantomData, } @@ -593,13 +592,15 @@ impl Default for EntitySpecializationTicks { } } -#[derive(Resource)] -pub struct SpecializedMaterialPipelineCache { +#[derive(Resource, Deref, DerefMut)] +pub struct SpecializedMaterial2dPipelineCache { + // (view_entity, material_entity) -> (tick, pipeline_id) + #[deref] map: HashMap<(MainEntity, MainEntity), (Tick, CachedRenderPipelineId), EntityHash>, marker: PhantomData, } -impl Default for SpecializedMaterialPipelineCache { +impl Default for SpecializedMaterial2dPipelineCache { fn default() -> Self { Self { map: HashMap::default(), @@ -608,23 +609,8 @@ impl Default for SpecializedMaterialPipelineCache { } } -impl Deref for SpecializedMaterialPipelineCache { - type Target = HashMap<(MainEntity, MainEntity), (Tick, CachedRenderPipelineId), EntityHash>; - - fn deref(&self) -> &Self::Target { - &self.map - } -} - -impl DerefMut for SpecializedMaterialPipelineCache { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.map - } -} - fn check_entities_needing_specialization( - mut thread_queues: Local>>, - mut needs_specialization: Query< + needs_specialization: Query< Entity, Or<( Changed, @@ -638,14 +624,9 @@ fn check_entities_needing_specialization( M: Material2d, { entities_needing_specialization.entities.clear(); - needs_specialization.par_iter_mut().for_each_init( - || thread_queues.borrow_local_mut(), - |queue, entity| { - queue.push(entity.into()); - }, - ); - - thread_queues.drain_into(&mut entities_needing_specialization.entities); + for entity in &needs_specialization { + entities_needing_specialization.entities.push(entity); + } } pub fn specialize_material2d_meshes( @@ -673,7 +654,7 @@ pub fn specialize_material2d_meshes( entity_specialization_ticks: Res>, view_specialization_ticks: Res, ticks: SystemChangeTick, - mut specialized_material_pipeline_cache: ResMut>, + mut specialized_material_pipeline_cache: ResMut>, ) where M::Data: PartialEq + Eq + Hash + Clone, { @@ -694,27 +675,19 @@ pub fn specialize_material2d_meshes( }; for (_, visible_entity) in visible_entities.iter::() { - let view_tick = view_specialization_ticks - .get(view_entity) - .expect("View entity not found in specialization ticks"); - let entity_tick = entity_specialization_ticks - .entities - .get(visible_entity) - .expect("Entity not found in specialization ticks"); + let view_tick = view_specialization_ticks.get(view_entity).unwrap(); + let entity_tick = entity_specialization_ticks.get(visible_entity).unwrap(); let last_specialized_tick = specialized_material_pipeline_cache .get(&(*view_entity, *visible_entity)) .map(|(tick, _)| *tick); - let needs_specialization = - last_specialized_tick.map_or(true, |last_specialized_tick| { - let view_changed = - view_tick.is_newer_than(last_specialized_tick, ticks.this_run()); - let entity_changed = - entity_tick.is_newer_than(last_specialized_tick, ticks.this_run()); - view_changed || entity_changed - }); + let needs_specialization = last_specialized_tick.is_none_or(|tick| { + view_tick.is_newer_than(tick, ticks.this_run()) + || entity_tick.is_newer_than(tick, ticks.this_run()) + }); if !needs_specialization { continue; } + let Some(material_asset_id) = render_material_instances.get(visible_entity) else { continue; }; @@ -778,7 +751,7 @@ pub fn queue_material2d_meshes( Option<&Tonemapping>, Option<&DebandDither>, )>, - specialized_material_pipeline_cache: ResMut>, + specialized_material_pipeline_cache: ResMut>, ) where M::Data: PartialEq + Eq + Hash + Clone, { diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index 2632651033e4f..0baf48aaa6c2f 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -197,7 +197,10 @@ pub fn check_views_need_specialization( } } - if !view_key_cache.get_mut(view_entity).is_some_and(|current_key| **current_key == view_key) { + if !view_key_cache + .get_mut(view_entity) + .is_some_and(|current_key| **current_key == view_key) + { view_key_cache.insert(*view_entity, view_key); view_specialization_ticks.insert(*view_entity, ticks.this_run()); } From 640dda915cf62a50f3b16b67f8e024dff9e03be3 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Wed, 29 Jan 2025 23:02:52 -0800 Subject: [PATCH 25/35] Clippy. --- crates/bevy_asset/src/lib.rs | 2 +- crates/bevy_pbr/src/material.rs | 47 +++++++++------------- crates/bevy_pbr/src/prepass/mod.rs | 24 +++++------ crates/bevy_pbr/src/render/light.rs | 9 ++--- crates/bevy_pbr/src/render/mesh.rs | 6 +-- crates/bevy_render/src/camera/camera.rs | 2 - crates/bevy_sprite/src/mesh2d/material.rs | 49 +++++++---------------- crates/bevy_sprite/src/mesh2d/mesh.rs | 3 +- 8 files changed, 49 insertions(+), 93 deletions(-) diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index 53e029ebf93eb..a3211fe2b36ab 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -212,7 +212,7 @@ use alloc::{ sync::Arc, vec::Vec, }; -use bevy_app::{App, Last, Plugin, PostUpdate, PreUpdate}; +use bevy_app::{App, Plugin, PostUpdate, PreUpdate}; use bevy_ecs::prelude::Component; use bevy_ecs::{ reflect::AppTypeRegistry, diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 6e2565d22c78a..002b5d20fd930 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -1,4 +1,3 @@ -use self::{irradiance_volume::IrradianceVolume, prelude::EnvironmentMapLight}; use crate::material_bind_groups::{MaterialBindGroupAllocator, MaterialBindingId}; #[cfg(feature = "meshlet")] use crate::meshlet::{ @@ -12,21 +11,15 @@ use bevy_core_pipeline::deferred::{AlphaMask3dDeferred, Opaque3dDeferred}; use bevy_core_pipeline::prepass::{AlphaMask3dPrepass, Opaque3dPrepass}; use bevy_core_pipeline::{ core_3d::{ - AlphaMask3d, Camera3d, Opaque3d, Opaque3dBatchSetKey, Opaque3dBinKey, - ScreenSpaceTransmissionQuality, Transmissive3d, Transparent3d, + AlphaMask3d, Opaque3d, Opaque3dBatchSetKey, Opaque3dBinKey, ScreenSpaceTransmissionQuality, + Transmissive3d, Transparent3d, }, - oit::OrderIndependentTransparencySettings, - prepass::{ - DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass, - OpaqueNoLightmap3dBatchSetKey, OpaqueNoLightmap3dBinKey, - }, - tonemapping::{DebandDither, Tonemapping}, + prepass::{OpaqueNoLightmap3dBatchSetKey, OpaqueNoLightmap3dBinKey}, + tonemapping::Tonemapping, }; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::component::Tick; use bevy_ecs::entity::EntityHash; -use bevy_ecs::query::{QueryItem, ROQueryItem}; -use bevy_ecs::system::lifetimeless::Read; use bevy_ecs::system::SystemChangeTick; use bevy_ecs::{ prelude::*, @@ -40,7 +33,6 @@ use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::Reflect; use bevy_render::{ batching::gpu_preprocessing::GpuPreprocessingSupport, - camera::TemporalJitter, extract_resource::ExtractResource, mesh::{self, Mesh3d, MeshVertexBufferLayoutRef, RenderMesh}, render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets}, @@ -53,9 +45,7 @@ use bevy_render::{ }; use bevy_render::{mesh::allocator::MeshAllocator, sync_world::MainEntityHashMap}; use bevy_render::{texture::FallbackImage, view::RenderVisibleEntities}; -use bevy_utils::Parallel; use core::{hash::Hash, marker::PhantomData}; -use std::ops::{Deref, DerefMut}; use tracing::error; /// Materials are used alongside [`MaterialPlugin`], [`Mesh3d`], and [`MeshMaterial3d`] @@ -697,20 +687,21 @@ fn extract_mesh_materials( } pub fn extract_entities_needs_specialization( - mut entities_needing_specialization: Extract>>, + entities_needing_specialization: Extract>>, mut entity_specialization_ticks: ResMut>, ticks: SystemChangeTick, ) where M: Material, { - for entity in &entities_needing_specialization.entities { + for entity in entities_needing_specialization.iter() { // Update the entity's specialization tick with this run's tick entity_specialization_ticks.insert((*entity).into(), ticks.this_run()); } } -#[derive(Clone, Resource, Debug)] +#[derive(Resource, Deref, DerefMut, Clone, Debug)] pub struct EntitiesNeedingSpecialization { + #[deref] pub entities: Vec, _marker: PhantomData, } @@ -724,7 +715,7 @@ impl Default for EntitiesNeedingSpecialization { } } -#[derive(Clone, Resource, Deref, DerefMut, Debug)] +#[derive(Resource, Deref, DerefMut, Clone, Debug)] pub struct EntitySpecializationTicks { #[deref] pub entities: MainEntityHashMap, @@ -771,9 +762,9 @@ fn check_entities_needing_specialization( ) where M: Material, { - entities_needing_specialization.entities.clear(); + entities_needing_specialization.clear(); for entity in &needs_specialization { - entities_needing_specialization.entities.push(entity); + entities_needing_specialization.push(entity); } } @@ -786,16 +777,16 @@ pub fn specialize_material_meshes( render_visibility_ranges: Res, ( material_bind_group_allocator, - mut opaque_render_phases, - mut alpha_mask_render_phases, - mut transmissive_render_phases, - mut transparent_render_phases, + opaque_render_phases, + alpha_mask_render_phases, + transmissive_render_phases, + transparent_render_phases, ): ( Res>, - ResMut>, - ResMut>, - ResMut>, - ResMut>, + Res>, + Res>, + Res>, + Res>, ), views: Query<(&MainEntity, &ExtractedView, &RenderVisibleEntities)>, view_key_cache: Res, diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 28811f04fe74e..3d6dcf0c12538 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -23,7 +23,7 @@ use bevy_render::{ }; pub use prepass_bindings::*; -use bevy_asset::{load_internal_asset, AsAssetId, AssetId, AssetServer, Handle}; +use bevy_asset::{load_internal_asset, AssetServer, Handle}; use bevy_core_pipeline::{ core_3d::CORE_3D_DEPTH_FORMAT, deferred::*, prelude::Camera3d, prepass::*, }; @@ -54,21 +54,15 @@ use crate::meshlet::{ MeshletMesh3d, }; -use bevy_asset::prelude::AssetChanged; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::component::Tick; use bevy_ecs::entity::EntityHash; -use bevy_ecs::query::{QueryItem, ROQueryItem}; use bevy_ecs::system::SystemChangeTick; use bevy_platform_support::collections::HashMap; -use bevy_reflect::Reflect; -use bevy_render::extract_resource::ExtractResource; use bevy_render::sync_world::{MainEntity, MainEntityHashMap}; use bevy_render::view::RenderVisibleEntities; use bevy_render::RenderSet::PrepareAssets; use core::{hash::Hash, marker::PhantomData}; -use derive_more::From; -use std::ops::{Deref, DerefMut}; pub const PREPASS_SHADER_HANDLE: Handle = Handle::weak_from_u128(921124473254008983); @@ -874,15 +868,15 @@ pub fn specialize_prepass_material_meshes( Option<&DeferredPrepass>, )>, ( - mut opaque_prepass_render_phases, - mut alpha_mask_prepass_render_phases, - mut opaque_deferred_render_phases, - mut alpha_mask_deferred_render_phases, + opaque_prepass_render_phases, + alpha_mask_prepass_render_phases, + opaque_deferred_render_phases, + alpha_mask_deferred_render_phases, ): ( - ResMut>, - ResMut>, - ResMut>, - ResMut>, + Res>, + Res>, + Res>, + Res>, ), ( mut specialized_material_pipeline_cache, diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index ef8afb4dba61c..1e001f132f02d 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -15,8 +15,6 @@ use bevy_ecs::{ }; use bevy_math::{ops, Mat4, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles}; use bevy_platform_support::collections::{HashMap, HashSet}; -use bevy_render::extract_resource::ExtractResource; -use bevy_render::sync_world::MainEntityHashMap; use bevy_render::{ batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport}, camera::SortedCameras, @@ -41,10 +39,9 @@ use bevy_render::{ sync_world::{MainEntity, RenderEntity}, }; use bevy_transform::{components::GlobalTransform, prelude::Transform}; -use bevy_utils::{default, Parallel}; +use bevy_utils::default; use core::{hash::Hash, ops::Range}; use std::marker::PhantomData; -use std::ops::{Deref, DerefMut}; #[cfg(feature = "trace")] use tracing::info_span; use tracing::{error, warn}; @@ -1593,9 +1590,9 @@ pub fn check_entities_needing_specialization( mut entities_needing_specialization: ResMut>, mut removed_components: RemovedComponents, ) { - entities_needing_specialization.entities.clear(); + entities_needing_specialization.clear(); for entity in &needs_specialization { - entities_needing_specialization.entities.push(entity); + entities_needing_specialization.push(entity); } for removed in removed_components.read() { diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index b95a1d799ba6d..0b29e73f5b6f6 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -47,7 +47,6 @@ use bevy_utils::{default, Parallel}; use core::mem::size_of; use material_bind_groups::MaterialBindingId; use render::skin::{self, SkinIndex}; -use std::marker::PhantomData; use tracing::{error, warn}; use self::irradiance_volume::IRRADIANCE_VOLUMES_ARE_USABLE; @@ -63,16 +62,13 @@ use crate::{ }, *, }; -use bevy_asset::prelude::AssetChanged; use bevy_core_pipeline::core_3d::Camera3d; use bevy_core_pipeline::oit::OrderIndependentTransparencySettings; use bevy_core_pipeline::prepass::{DeferredPrepass, DepthPrepass, NormalPrepass}; use bevy_core_pipeline::tonemapping::{DebandDither, Tonemapping}; use bevy_ecs::component::Tick; -use bevy_ecs::query::QueryItem; use bevy_ecs::system::SystemChangeTick; use bevy_render::camera::TemporalJitter; -use bevy_render::extract_resource::ExtractResource; use bevy_render::prelude::Msaa; use bevy_render::sync_world::{MainEntity, MainEntityHashMap}; use bevy_render::view::ExtractedView; @@ -432,7 +428,7 @@ pub fn check_views_need_specialization( } if !view_key_cache .get_mut(view_entity) - .is_some_and(|current_key| **current_key == view_key) + .is_some_and(|current_key| *current_key == view_key) { view_key_cache.insert(*view_entity, view_key); view_specialization_ticks.insert(*view_entity, ticks.this_run()); diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index ae6d4824d0a1a..28362a30e80dc 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -3,7 +3,6 @@ reason = "The parent module contains all things viewport-related, while this module handles cameras as a component. However, a rename/refactor which should clear up this lint is being discussed; see #17196." )] use super::{ClearColorConfig, Projection}; -use crate::sync_world::MainEntity; use crate::{ batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport}, camera::{CameraProjection, ManualTextureViewHandle, ManualTextureViews}, @@ -45,7 +44,6 @@ use bevy_window::{ }; use core::ops::Range; use derive_more::derive::From; -use std::ops::Deref; use tracing::warn; use wgpu::{BlendState, TextureFormat, TextureUsages}; diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index d3452962543e4..edcebf21481ff 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -9,7 +9,7 @@ use bevy_core_pipeline::{ core_2d::{ AlphaMask2d, AlphaMask2dBinKey, BatchSetKey2d, Opaque2d, Opaque2dBinKey, Transparent2d, }, - tonemapping::{DebandDither, Tonemapping}, + tonemapping::Tonemapping, }; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::component::Tick; @@ -22,7 +22,6 @@ use bevy_ecs::{ use bevy_math::FloatOrd; use bevy_platform_support::collections::HashMap; use bevy_reflect::{prelude::ReflectDefault, Reflect}; -use bevy_render::mesh::Mesh3d; use bevy_render::render_phase::DrawFunctionId; use bevy_render::render_resource::CachedRenderPipelineId; use bevy_render::view::RenderVisibleEntities; @@ -43,13 +42,11 @@ use bevy_render::{ }, renderer::RenderDevice, sync_world::{MainEntity, MainEntityHashMap}, - view::{ExtractedView, Msaa, ViewVisibility}, + view::{ExtractedView, ViewVisibility}, Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; -use bevy_utils::Parallel; use core::{hash::Hash, marker::PhantomData}; use derive_more::derive::From; -use std::ops::{Deref, DerefMut}; use tracing::error; /// Materials are used alongside [`Material2dPlugin`], [`Mesh2d`], and [`MeshMaterial2d`] @@ -549,20 +546,21 @@ pub const fn tonemapping_pipeline_key(tonemapping: Tonemapping) -> Mesh2dPipelin } pub fn extract_entities_needs_specialization( - mut entities_needing_specialization: Extract>>, + entities_needing_specialization: Extract>>, mut entity_specialization_ticks: ResMut>, ticks: SystemChangeTick, ) where M: Material2d, { - for entity in &entities_needing_specialization.entities { + for entity in entities_needing_specialization.iter() { // Update the entity's specialization tick with this run's tick entity_specialization_ticks.insert((*entity).into(), ticks.this_run()); } } -#[derive(Clone, Resource, Debug)] +#[derive(Clone, Resource, Deref, DerefMut, Debug)] pub struct EntitiesNeedingSpecialization { + #[deref] pub entities: Vec, _marker: PhantomData, } @@ -623,9 +621,9 @@ fn check_entities_needing_specialization( ) where M: Material2d, { - entities_needing_specialization.entities.clear(); + entities_needing_specialization.clear(); for entity in &needs_specialization { - entities_needing_specialization.entities.push(entity); + entities_needing_specialization.push(entity); } } @@ -639,17 +637,10 @@ pub fn specialize_material2d_meshes( ), mut render_mesh_instances: ResMut, render_material_instances: Res>, - mut transparent_render_phases: ResMut>, - mut opaque_render_phases: ResMut>, - mut alpha_mask_render_phases: ResMut>, - views: Query<( - &MainEntity, - &ExtractedView, - &RenderVisibleEntities, - &Msaa, - Option<&Tonemapping>, - Option<&DebandDither>, - )>, + transparent_render_phases: Res>, + opaque_render_phases: Res>, + alpha_mask_render_phases: Res>, + views: Query<(&MainEntity, &ExtractedView, &RenderVisibleEntities)>, view_key_cache: Res, entity_specialization_ticks: Res>, view_specialization_ticks: Res, @@ -662,7 +653,7 @@ pub fn specialize_material2d_meshes( return; } - for (view_entity, view, visible_entities, msaa, tonemapping, dither) in &views { + for (view_entity, view, visible_entities) in &views { if !transparent_render_phases.contains_key(&view.retained_view_entity) && !opaque_render_phases.contains_key(&view.retained_view_entity) && !alpha_mask_render_phases.contains_key(&view.retained_view_entity) @@ -731,9 +722,6 @@ pub fn specialize_material2d_meshes( } pub fn queue_material2d_meshes( - material2d_pipeline: Res>, - mut pipelines: ResMut>>, - pipeline_cache: Res, (render_meshes, render_materials): ( Res>, Res>>, @@ -743,14 +731,7 @@ pub fn queue_material2d_meshes( mut transparent_render_phases: ResMut>, mut opaque_render_phases: ResMut>, mut alpha_mask_render_phases: ResMut>, - views: Query<( - &MainEntity, - &ExtractedView, - &RenderVisibleEntities, - &Msaa, - Option<&Tonemapping>, - Option<&DebandDither>, - )>, + views: Query<(&MainEntity, &ExtractedView, &RenderVisibleEntities)>, specialized_material_pipeline_cache: ResMut>, ) where M::Data: PartialEq + Eq + Hash + Clone, @@ -759,7 +740,7 @@ pub fn queue_material2d_meshes( return; } - for (view_entity, view, visible_entities, msaa, tonemapping, dither) in &views { + for (view_entity, view, visible_entities) in &views { let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity) else { continue; diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index 0baf48aaa6c2f..42a249a86abb1 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -20,7 +20,6 @@ use bevy_ecs::{ use bevy_image::{BevyDefault, Image, ImageSampler, TextureFormatPixelInfo}; use bevy_math::{Affine3, Vec4}; use bevy_render::prelude::Msaa; -use bevy_render::view::RenderVisibleEntities; use bevy_render::RenderSet::PrepareAssets; use bevy_render::{ batching::{ @@ -199,7 +198,7 @@ pub fn check_views_need_specialization( if !view_key_cache .get_mut(view_entity) - .is_some_and(|current_key| **current_key == view_key) + .is_some_and(|current_key| *current_key == view_key) { view_key_cache.insert(*view_entity, view_key); view_specialization_ticks.insert(*view_entity, ticks.this_run()); From 9ca60297d8c60c4c07b0fefc759c56d76c654e4a Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Wed, 29 Jan 2025 23:07:00 -0800 Subject: [PATCH 26/35] Clippy. --- crates/bevy_pbr/src/render/light.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 1e001f132f02d..830de22d5267a 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -40,8 +40,7 @@ use bevy_render::{ }; use bevy_transform::{components::GlobalTransform, prelude::Transform}; use bevy_utils::default; -use core::{hash::Hash, ops::Range}; -use std::marker::PhantomData; +use core::{hash::Hash, ops::Range, marker::PhantomData}; #[cfg(feature = "trace")] use tracing::info_span; use tracing::{error, warn}; @@ -1598,7 +1597,7 @@ pub fn check_entities_needing_specialization( for removed in removed_components.read() { entities_needing_specialization .entities - .push(removed.into()); + .push(removed); } } From e2911156f946e9959e637b85d6023eea3cea2bf2 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Wed, 29 Jan 2025 23:17:30 -0800 Subject: [PATCH 27/35] Fmt. --- crates/bevy_pbr/src/render/light.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 830de22d5267a..53052dca5f19d 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -40,7 +40,7 @@ use bevy_render::{ }; use bevy_transform::{components::GlobalTransform, prelude::Transform}; use bevy_utils::default; -use core::{hash::Hash, ops::Range, marker::PhantomData}; +use core::{hash::Hash, marker::PhantomData, ops::Range}; #[cfg(feature = "trace")] use tracing::info_span; use tracing::{error, warn}; @@ -1595,9 +1595,7 @@ pub fn check_entities_needing_specialization( } for removed in removed_components.read() { - entities_needing_specialization - .entities - .push(removed); + entities_needing_specialization.entities.push(removed); } } From 73c6b1a499dd91d7804cc1a51f3d3dd249863336 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Wed, 29 Jan 2025 23:39:21 -0800 Subject: [PATCH 28/35] Cleanup for ambiguity. --- crates/bevy_animation/src/lib.rs | 4 ++-- crates/bevy_pbr/src/material.rs | 16 +++++++++------- crates/bevy_pbr/src/render/light.rs | 6 +++--- crates/bevy_render/src/camera/projection.rs | 5 ++++- crates/bevy_render/src/mesh/mod.rs | 5 +++-- crates/bevy_sprite/src/lib.rs | 4 ++-- crates/bevy_sprite/src/mesh2d/material.rs | 2 +- crates/bevy_text/src/lib.rs | 4 ++-- 8 files changed, 26 insertions(+), 20 deletions(-) diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index 2bfd54d847d84..aeaa0d0ab429c 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -32,7 +32,7 @@ use crate::{ }; use bevy_app::{Animation, App, Plugin, PostUpdate}; -use bevy_asset::{Asset, AssetApp, Assets}; +use bevy_asset::{Asset, AssetApp, AssetEvents, Assets}; use bevy_ecs::{ entity::{VisitEntities, VisitEntitiesMut}, prelude::*, @@ -1244,7 +1244,7 @@ impl Plugin for AnimationPlugin { .add_systems( PostUpdate, ( - graph::thread_animation_graphs, + graph::thread_animation_graphs.before(AssetEvents), advance_transitions, advance_animations, // TODO: `animate_targets` can animate anything, so diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 002b5d20fd930..a689a10018dcb 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -31,6 +31,7 @@ use bevy_ecs::{ use bevy_platform_support::collections::HashMap; use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::Reflect; +use bevy_render::mesh::mark_3d_meshes_as_changed_if_their_assets_changed; use bevy_render::{ batching::gpu_preprocessing::GpuPreprocessingSupport, extract_resource::ExtractResource, @@ -272,15 +273,16 @@ where app.init_asset::() .register_type::>() .init_resource::>() - .add_systems( - PostUpdate, - check_entities_needing_specialization::.after(AssetEvents), - ) .add_plugins((RenderAssetPlugin::>::default(),)) .add_systems( PostUpdate, - mark_meshes_as_changed_if_their_materials_changed:: - .ambiguous_with_all() + ( + mark_meshes_as_changed_if_their_materials_changed::.ambiguous_with_all(), + check_light_entities_needing_specialization::.after(AssetEvents), + check_entities_needing_specialization:: + .after(AssetEvents) + .ambiguous_with(mark_3d_meshes_as_changed_if_their_assets_changed), + ) .after(mesh::mark_3d_meshes_as_changed_if_their_assets_changed), ); @@ -748,7 +750,7 @@ impl Default for SpecializedMaterialPipelineCache { } } -fn check_entities_needing_specialization( +pub fn check_entities_needing_specialization( needs_specialization: Query< Entity, Or<( diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 53052dca5f19d..5cdea0627b7b7 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -1583,13 +1583,13 @@ fn despawn_entities(commands: &mut Commands, entities: Vec) { }); } -/// These will be extracted in the material extraction -pub fn check_entities_needing_specialization( +// These will be extracted in the material extraction, which will also clear the needs_specialization +// collection. +pub fn check_light_entities_needing_specialization( needs_specialization: Query>, Changed)>, mut entities_needing_specialization: ResMut>, mut removed_components: RemovedComponents, ) { - entities_needing_specialization.clear(); for entity in &needs_specialization { entities_needing_specialization.push(entity); } diff --git a/crates/bevy_render/src/camera/projection.rs b/crates/bevy_render/src/camera/projection.rs index 3b04334f5b207..88d7c583ce7b9 100644 --- a/crates/bevy_render/src/camera/projection.rs +++ b/crates/bevy_render/src/camera/projection.rs @@ -2,6 +2,7 @@ use core::fmt::Debug; use crate::{primitives::Frustum, view::VisibilitySystems}; use bevy_app::{App, Plugin, PostStartup, PostUpdate}; +use bevy_asset::AssetEvents; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::prelude::*; use bevy_math::{ops, AspectRatio, Mat4, Rect, Vec2, Vec3A, Vec4}; @@ -29,7 +30,9 @@ impl Plugin for CameraProjectionPlugin { .add_systems( PostUpdate, ( - crate::camera::camera_system.in_set(CameraUpdateSystem), + crate::camera::camera_system + .in_set(CameraUpdateSystem) + .before(AssetEvents), crate::view::update_frusta .in_set(VisibilitySystems::UpdateFrusta) .after(crate::camera::camera_system) diff --git a/crates/bevy_render/src/mesh/mod.rs b/crates/bevy_render/src/mesh/mod.rs index 6f9339bb06198..1667d5019634c 100644 --- a/crates/bevy_render/src/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mod.rs @@ -13,7 +13,7 @@ use crate::{ }; use allocator::MeshAllocatorPlugin; use bevy_app::{App, Plugin, PostUpdate}; -use bevy_asset::{AssetApp, AssetId, RenderAssetUsages}; +use bevy_asset::{AssetApp, AssetEvents, AssetId, RenderAssetUsages}; use bevy_ecs::{ prelude::*, system::{ @@ -41,7 +41,8 @@ impl Plugin for MeshPlugin { .add_systems( PostUpdate, mark_3d_meshes_as_changed_if_their_assets_changed - .ambiguous_with(VisibilitySystems::CalculateBounds), + .ambiguous_with(VisibilitySystems::CalculateBounds) + .before(AssetEvents), ); let Some(render_app) = app.get_sub_app_mut(RenderApp) else { diff --git a/crates/bevy_sprite/src/lib.rs b/crates/bevy_sprite/src/lib.rs index 2bd354ac8b263..090a7853c4d85 100644 --- a/crates/bevy_sprite/src/lib.rs +++ b/crates/bevy_sprite/src/lib.rs @@ -37,7 +37,7 @@ pub use sprite::*; pub use texture_slice::*; use bevy_app::prelude::*; -use bevy_asset::{load_internal_asset, Assets, Handle}; +use bevy_asset::{load_internal_asset, AssetEvents, Assets, Handle}; use bevy_core_pipeline::core_2d::Transparent2d; use bevy_ecs::prelude::*; use bevy_image::{prelude::*, TextureAtlasPlugin}; @@ -115,7 +115,7 @@ impl Plugin for SpritePlugin { ( calculate_bounds_2d.in_set(VisibilitySystems::CalculateBounds), ( - compute_slices_on_asset_event, + compute_slices_on_asset_event.before(AssetEvents), compute_slices_on_sprite_change, ) .in_set(SpriteSystem::ComputeSlices), diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index edcebf21481ff..745dee2f2ecd1 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -607,7 +607,7 @@ impl Default for SpecializedMaterial2dPipelineCache { } } -fn check_entities_needing_specialization( +pub fn check_entities_needing_specialization( needs_specialization: Query< Entity, Or<( diff --git a/crates/bevy_text/src/lib.rs b/crates/bevy_text/src/lib.rs index 7f71f486e4eb6..670f793c31c15 100644 --- a/crates/bevy_text/src/lib.rs +++ b/crates/bevy_text/src/lib.rs @@ -67,9 +67,9 @@ pub mod prelude { } use bevy_app::{prelude::*, Animation}; -use bevy_asset::AssetApp; #[cfg(feature = "default_font")] use bevy_asset::{load_internal_binary_asset, Handle}; +use bevy_asset::{AssetApp, AssetEvents}; use bevy_ecs::prelude::*; use bevy_render::{ camera::CameraUpdateSystem, view::VisibilitySystems, ExtractSchedule, RenderApp, @@ -124,7 +124,7 @@ impl Plugin for TextPlugin { .add_systems( PostUpdate, ( - remove_dropped_font_atlas_sets, + remove_dropped_font_atlas_sets.before(AssetEvents), detect_text_needs_rerender::, update_text2d_layout // Potential conflict: `Assets` From 278cf6509bee5110dea9489bf859cc8862ec2ac1 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Wed, 29 Jan 2025 23:42:51 -0800 Subject: [PATCH 29/35] Clippy. --- crates/bevy_pbr/src/material.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index a689a10018dcb..4ff367eaadc90 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -283,7 +283,7 @@ where .after(AssetEvents) .ambiguous_with(mark_3d_meshes_as_changed_if_their_assets_changed), ) - .after(mesh::mark_3d_meshes_as_changed_if_their_assets_changed), + .after(mark_3d_meshes_as_changed_if_their_assets_changed), ); if self.shadows_enabled { From 86bdb269d766ad570443a799238c33894c18c1a3 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Wed, 29 Jan 2025 23:46:43 -0800 Subject: [PATCH 30/35] Clippy. --- crates/bevy_pbr/src/material.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 4ff367eaadc90..74324f01c1e8e 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -35,7 +35,7 @@ use bevy_render::mesh::mark_3d_meshes_as_changed_if_their_assets_changed; use bevy_render::{ batching::gpu_preprocessing::GpuPreprocessingSupport, extract_resource::ExtractResource, - mesh::{self, Mesh3d, MeshVertexBufferLayoutRef, RenderMesh}, + mesh::{Mesh3d, MeshVertexBufferLayoutRef, RenderMesh}, render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets}, render_phase::*, render_resource::*, From ad20a91034803f4762456ffabc7d448c58bca721 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Thu, 30 Jan 2025 00:03:58 -0800 Subject: [PATCH 31/35] Ambiguity. --- crates/bevy_pbr/src/material.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 74324f01c1e8e..dc9ef87b97957 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -283,6 +283,7 @@ where .after(AssetEvents) .ambiguous_with(mark_3d_meshes_as_changed_if_their_assets_changed), ) + .chain() .after(mark_3d_meshes_as_changed_if_their_assets_changed), ); From 0be3cdd2c2b07d2ff89bcc6c9445de7623fa242b Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Thu, 30 Jan 2025 00:11:29 -0800 Subject: [PATCH 32/35] Ambiguity. --- crates/bevy_pbr/src/material.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index dc9ef87b97957..da016d54995ed 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -278,12 +278,13 @@ where PostUpdate, ( mark_meshes_as_changed_if_their_materials_changed::.ambiguous_with_all(), - check_light_entities_needing_specialization::.after(AssetEvents), + check_light_entities_needing_specialization:: + .after(AssetEvents) + .ambiguous_with(check_entities_needing_specialization), check_entities_needing_specialization:: .after(AssetEvents) .ambiguous_with(mark_3d_meshes_as_changed_if_their_assets_changed), ) - .chain() .after(mark_3d_meshes_as_changed_if_their_assets_changed), ); From 5a00374867d35e4693616f779404f1e3acfdf5c7 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Thu, 30 Jan 2025 00:23:23 -0800 Subject: [PATCH 33/35] Ambiguity. --- crates/bevy_pbr/src/material.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index da016d54995ed..66cbee3241c00 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -280,7 +280,7 @@ where mark_meshes_as_changed_if_their_materials_changed::.ambiguous_with_all(), check_light_entities_needing_specialization:: .after(AssetEvents) - .ambiguous_with(check_entities_needing_specialization), + .ambiguous_with(check_entities_needing_specialization::), check_entities_needing_specialization:: .after(AssetEvents) .ambiguous_with(mark_3d_meshes_as_changed_if_their_assets_changed), From f2e6ebbcb32dd1653ae9b50380de4a47511a14c1 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Thu, 30 Jan 2025 17:55:26 -0800 Subject: [PATCH 34/35] Temp fix ambiguity. --- crates/bevy_pbr/src/material.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 66cbee3241c00..539e14a191e0a 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -279,11 +279,9 @@ where ( mark_meshes_as_changed_if_their_materials_changed::.ambiguous_with_all(), check_light_entities_needing_specialization:: - .after(AssetEvents) - .ambiguous_with(check_entities_needing_specialization::), + .after(AssetEvents), check_entities_needing_specialization:: .after(AssetEvents) - .ambiguous_with(mark_3d_meshes_as_changed_if_their_assets_changed), ) .after(mark_3d_meshes_as_changed_if_their_assets_changed), ); From e3aff84eaaf2d7808eec77f61a64971db27d4330 Mon Sep 17 00:00:00 2001 From: Charlotte McElwain Date: Tue, 4 Feb 2025 11:31:44 -0800 Subject: [PATCH 35/35] Fix ambiguity. --- crates/bevy_pbr/src/material.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 539e14a191e0a..5b863eaa96d93 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -278,10 +278,7 @@ where PostUpdate, ( mark_meshes_as_changed_if_their_materials_changed::.ambiguous_with_all(), - check_light_entities_needing_specialization:: - .after(AssetEvents), - check_entities_needing_specialization:: - .after(AssetEvents) + check_entities_needing_specialization::.after(AssetEvents), ) .after(mark_3d_meshes_as_changed_if_their_assets_changed), ); @@ -289,7 +286,8 @@ where if self.shadows_enabled { app.add_systems( PostUpdate, - check_entities_needing_specialization::.after(AssetEvents), + check_light_entities_needing_specialization:: + .after(check_entities_needing_specialization::), ); }