diff --git a/Cargo.toml b/Cargo.toml index 61d0d5a580a80..055ba0252a91d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -722,6 +722,7 @@ bevy_image = { path = "crates/bevy_image", version = "0.18.0-dev", default-featu bevy_reflect = { path = "crates/bevy_reflect", version = "0.18.0-dev", default-features = false } bevy_render = { path = "crates/bevy_render", version = "0.18.0-dev", default-features = false } bevy_state = { path = "crates/bevy_state", version = "0.18.0-dev", default-features = false } +bevy_pbr = { path = "crates/bevy_pbr", version = "0.18.0-dev", default-features = false } # Needed to poll Task examples futures-lite = "2.0.1" futures-timer = { version = "3", features = ["wasm-bindgen", "gloo-timers"] } @@ -4957,3 +4958,14 @@ name = "Mirror" description = "Demonstrates how to create a mirror with a second camera" category = "3D Rendering" wasm = true + +[[example]] +name = "custom_mesh_pass" +path = "examples/shader_advanced/custom_mesh_pass.rs" +doc-scrape-examples = true + +[package.metadata.example.custom_mesh_pass] +name = "Custom Mesh Pass" +description = "Demonstrates how to write a custom mesh pass" +category = "Shaders" +wasm = true diff --git a/assets/shaders/custom_mesh_pass_material.wgsl b/assets/shaders/custom_mesh_pass_material.wgsl new file mode 100644 index 0000000000000..aece49fdeb898 --- /dev/null +++ b/assets/shaders/custom_mesh_pass_material.wgsl @@ -0,0 +1,59 @@ +#import bevy_pbr::{ + pbr_bindings, + pbr_types, + mesh_functions, + mesh_view_bindings, + view_transformations, +} + +@group(#{MATERIAL_BIND_GROUP}) @binding(100) var outline_color: vec4; + +const OUTLINE_WIDTH = 0.1; + +struct Vertex { + @builtin(instance_index) instance_index: u32, + @location(0) position: vec3, + @location(1) normal: vec3, +}; + +struct VertexOutput { + @builtin(position) clip_position: vec4, + @location(0) world_position: vec4, + @location(1) world_normal: vec3, +}; + +@vertex +fn vertex(vertex: Vertex) -> VertexOutput { + var out: VertexOutput; + + // This only works when the mesh is at the origin. + let expanded_position = vertex.position * (1 + OUTLINE_WIDTH); + + var world_from_local = mesh_functions::get_world_from_local(vertex.instance_index); + out.world_position = mesh_functions::mesh_position_local_to_world(world_from_local, vec4(expanded_position, 1.0)); + out.clip_position = view_transformations::position_world_to_clip(out.world_position.xyz); + + out.world_normal = mesh_functions::mesh_normal_local_to_world(vertex.normal, vertex.instance_index); + + return out; +} + +fn fresnel(normal: vec3, view: vec3, power: f32) -> f32 { + return pow(1.0 - clamp(dot(normalize(normal), normalize(view)), 0.0, 1.0), power); +} + +@fragment +fn fragment(input: VertexOutput) -> @location(0) vec4 { + let flags = pbr_bindings::material.flags; + let alpha_mode = flags & pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS; + var color = outline_color; + + if alpha_mode != pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE { + let V = normalize(mesh_view_bindings::view.world_position.xyz - input.world_position.xyz); + let N = normalize(input.world_normal); + + color *= fresnel(N, V, 3.0); + } + + return color; +} \ No newline at end of file diff --git a/crates/bevy_core_pipeline/src/core_2d/main_opaque_pass_2d_node.rs b/crates/bevy_core_pipeline/src/core_2d/main_opaque_pass_2d_node.rs index e8cd0c65c6888..f437ecaee9a08 100644 --- a/crates/bevy_core_pipeline/src/core_2d/main_opaque_pass_2d_node.rs +++ b/crates/bevy_core_pipeline/src/core_2d/main_opaque_pass_2d_node.rs @@ -4,10 +4,10 @@ use bevy_render::{ camera::ExtractedCamera, diagnostic::RecordDiagnostics, render_graph::{NodeRunError, RenderGraphContext, ViewNode}, - render_phase::{TrackedRenderPass, ViewBinnedRenderPhases}, + render_phase::{BinnedRenderPhase, TrackedRenderPass}, render_resource::{CommandEncoderDescriptor, RenderPassDescriptor, StoreOp}, renderer::RenderContext, - view::{ExtractedView, ViewDepthTexture, ViewTarget}, + view::{ViewDepthTexture, ViewTarget}, }; use tracing::error; #[cfg(feature = "trace")] @@ -16,43 +16,31 @@ use tracing::info_span; use super::AlphaMask2d; /// A [`bevy_render::render_graph::Node`] that runs the -/// [`Opaque2d`] [`ViewBinnedRenderPhases`] and [`AlphaMask2d`] [`ViewBinnedRenderPhases`] +/// [`Opaque2d`] [`BinnedRenderPhase`]s and the [`AlphaMask2d`] [`BinnedRenderPhase`]s. #[derive(Default)] pub struct MainOpaquePass2dNode; impl ViewNode for MainOpaquePass2dNode { type ViewQuery = ( &'static ExtractedCamera, - &'static ExtractedView, &'static ViewTarget, &'static ViewDepthTexture, + &'static BinnedRenderPhase, + &'static BinnedRenderPhase, ); fn run<'w>( &self, graph: &mut RenderGraphContext, render_context: &mut RenderContext<'w>, - (camera, view, target, depth): QueryItem<'w, '_, Self::ViewQuery>, + (camera, target, depth, opaque_phase, alpha_mask_phase): QueryItem<'w, '_, Self::ViewQuery>, world: &'w World, ) -> Result<(), NodeRunError> { - let (Some(opaque_phases), Some(alpha_mask_phases)) = ( - world.get_resource::>(), - world.get_resource::>(), - ) else { - return Ok(()); - }; - let diagnostics = render_context.diagnostic_recorder(); let color_attachments = [Some(target.get_color_attachment())]; let depth_stencil_attachment = Some(depth.get_attachment(StoreOp::Store)); let view_entity = graph.view_entity(); - let (Some(opaque_phase), Some(alpha_mask_phase)) = ( - opaque_phases.get(&view.retained_view_entity), - alpha_mask_phases.get(&view.retained_view_entity), - ) else { - return Ok(()); - }; render_context.add_command_buffer_generation_task(move |render_device| { #[cfg(feature = "trace")] let _main_opaque_pass_2d_span = info_span!("main_opaque_pass_2d").entered(); diff --git a/crates/bevy_core_pipeline/src/core_2d/main_transparent_pass_2d_node.rs b/crates/bevy_core_pipeline/src/core_2d/main_transparent_pass_2d_node.rs index 4054283a5738a..e5287fe090b11 100644 --- a/crates/bevy_core_pipeline/src/core_2d/main_transparent_pass_2d_node.rs +++ b/crates/bevy_core_pipeline/src/core_2d/main_transparent_pass_2d_node.rs @@ -4,10 +4,10 @@ use bevy_render::{ camera::ExtractedCamera, diagnostic::RecordDiagnostics, render_graph::{NodeRunError, RenderGraphContext, ViewNode}, - render_phase::{TrackedRenderPass, ViewSortedRenderPhases}, + render_phase::{SortedRenderPhase, TrackedRenderPass}, render_resource::{CommandEncoderDescriptor, RenderPassDescriptor, StoreOp}, renderer::RenderContext, - view::{ExtractedView, ViewDepthTexture, ViewTarget}, + view::{ViewDepthTexture, ViewTarget}, }; use tracing::error; #[cfg(feature = "trace")] @@ -19,28 +19,23 @@ pub struct MainTransparentPass2dNode {} impl ViewNode for MainTransparentPass2dNode { type ViewQuery = ( &'static ExtractedCamera, - &'static ExtractedView, &'static ViewTarget, &'static ViewDepthTexture, + &'static SortedRenderPhase, ); fn run<'w>( &self, graph: &mut RenderGraphContext, render_context: &mut RenderContext<'w>, - (camera, view, target, depth): bevy_ecs::query::QueryItem<'w, '_, Self::ViewQuery>, + (camera, target, depth, transparent_phase): bevy_ecs::query::QueryItem< + 'w, + '_, + Self::ViewQuery, + >, world: &'w World, ) -> Result<(), NodeRunError> { - let Some(transparent_phases) = - world.get_resource::>() - else { - return Ok(()); - }; - let view_entity = graph.view_entity(); - let Some(transparent_phase) = transparent_phases.get(&view.retained_view_entity) else { - return Ok(()); - }; let diagnostics = render_context.diagnostic_recorder(); diff --git a/crates/bevy_core_pipeline/src/core_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs index dee095185d44b..cc16205ff67d9 100644 --- a/crates/bevy_core_pipeline/src/core_2d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -36,12 +36,12 @@ use core::ops::Range; use bevy_asset::UntypedAssetId; use bevy_camera::{Camera, Camera2d}; use bevy_image::ToExtents; -use bevy_platform::collections::{HashMap, HashSet}; +use bevy_platform::collections::HashMap; use bevy_render::{ batching::gpu_preprocessing::GpuPreprocessingMode, camera::CameraRenderGraph, - render_phase::PhaseItemBatchSetKey, - view::{ExtractedView, RetainedViewEntity}, + render_phase::{BinnedRenderPhase, PhaseItemBatchSetKey, SortedRenderPhase}, + sync_world::RenderEntity, }; pub use main_opaque_pass_2d_node::*; pub use main_transparent_pass_2d_node::*; @@ -59,8 +59,7 @@ use bevy_render::{ render_graph::{EmptyNode, RenderGraphExt, ViewNodeRunner}, render_phase::{ sort_phase_system, BinnedPhaseItem, CachedRenderPipelinePhaseItem, DrawFunctionId, - DrawFunctions, PhaseItem, PhaseItemExtraIndex, SortedPhaseItem, ViewBinnedRenderPhases, - ViewSortedRenderPhases, + DrawFunctions, PhaseItem, PhaseItemExtraIndex, SortedPhaseItem, }, render_resource::{ BindGroupId, CachedRenderPipelineId, TextureDescriptor, TextureDimension, TextureFormat, @@ -95,9 +94,6 @@ impl Plugin for Core2dPlugin { .init_resource::>() .init_resource::>() .init_resource::>() - .init_resource::>() - .init_resource::>() - .init_resource::>() .add_systems(ExtractSchedule, extract_core_2d_camera_phases) .add_systems( Render, @@ -432,52 +428,55 @@ impl CachedRenderPipelinePhaseItem for Transparent2d { } pub fn extract_core_2d_camera_phases( - mut transparent_2d_phases: ResMut>, - mut opaque_2d_phases: ResMut>, - mut alpha_mask_2d_phases: ResMut>, - cameras_2d: Extract>>, - mut live_entities: Local>, + mut commands: Commands, + cameras_2d: Extract>>, + mut phases: Query<( + &mut BinnedRenderPhase, + &mut BinnedRenderPhase, + &mut SortedRenderPhase, + )>, ) { - live_entities.clear(); - - for (main_entity, camera) in &cameras_2d { + for (entity, camera) in &cameras_2d { if !camera.is_active { + commands.entity(entity).remove::<( + BinnedRenderPhase, + BinnedRenderPhase, + SortedRenderPhase, + )>(); continue; } - // This is the main 2D camera, so we use the first subview index (0). - let retained_view_entity = RetainedViewEntity::new(main_entity.into(), None, 0); - - transparent_2d_phases.insert_or_clear(retained_view_entity); - opaque_2d_phases.prepare_for_new_frame(retained_view_entity, GpuPreprocessingMode::None); - alpha_mask_2d_phases - .prepare_for_new_frame(retained_view_entity, GpuPreprocessingMode::None); - - live_entities.insert(retained_view_entity); + if let Ok((mut opaque_2d_phase, mut alpha_mask_2d_phase, mut transparent_2d_phase)) = + phases.get_mut(entity) + { + opaque_2d_phase.prepare_for_new_frame(); + alpha_mask_2d_phase.prepare_for_new_frame(); + transparent_2d_phase.clear(); + } else { + commands.entity(entity).insert(( + BinnedRenderPhase::::new(GpuPreprocessingMode::None), + BinnedRenderPhase::::new(GpuPreprocessingMode::None), + SortedRenderPhase::::default(), + )); + } } - - // Clear out all dead views. - transparent_2d_phases.retain(|camera_entity, _| live_entities.contains(camera_entity)); - opaque_2d_phases.retain(|camera_entity, _| live_entities.contains(camera_entity)); - alpha_mask_2d_phases.retain(|camera_entity, _| live_entities.contains(camera_entity)); } pub fn prepare_core_2d_depth_textures( mut commands: Commands, mut texture_cache: ResMut, render_device: Res, - transparent_2d_phases: Res>, - opaque_2d_phases: Res>, - views_2d: Query<(Entity, &ExtractedCamera, &ExtractedView, &Msaa), (With,)>, + views_2d: Query< + (Entity, &ExtractedCamera, &Msaa), + ( + With, + With>, + With>, + ), + >, ) { let mut textures = >::default(); - for (view, camera, extracted_view, msaa) in &views_2d { - if !opaque_2d_phases.contains_key(&extracted_view.retained_view_entity) - || !transparent_2d_phases.contains_key(&extracted_view.retained_view_entity) - { - continue; - }; - + for (view, camera, msaa) in &views_2d { let Some(physical_target_size) = camera.physical_target_size else { continue; }; diff --git a/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs b/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs index 25cf2ac75c58e..01c710db080d3 100644 --- a/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs +++ b/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs @@ -8,10 +8,10 @@ use bevy_render::{ camera::ExtractedCamera, diagnostic::RecordDiagnostics, render_graph::{NodeRunError, RenderGraphContext, ViewNode}, - render_phase::{TrackedRenderPass, ViewBinnedRenderPhases}, + render_phase::{BinnedRenderPhase, TrackedRenderPass}, render_resource::{CommandEncoderDescriptor, PipelineCache, RenderPassDescriptor, StoreOp}, renderer::RenderContext, - view::{ExtractedView, ViewDepthTexture, ViewTarget, ViewUniformOffset}, + view::{ViewDepthTexture, ViewTarget, ViewUniformOffset}, }; use tracing::error; #[cfg(feature = "trace")] @@ -20,15 +20,16 @@ use tracing::info_span; use super::AlphaMask3d; /// A [`bevy_render::render_graph::Node`] that runs the [`Opaque3d`] and [`AlphaMask3d`] -/// [`ViewBinnedRenderPhases`]s. +/// [`BinnedRenderPhase`]s. #[derive(Default)] pub struct MainOpaquePass3dNode; impl ViewNode for MainOpaquePass3dNode { type ViewQuery = ( &'static ExtractedCamera, - &'static ExtractedView, &'static ViewTarget, &'static ViewDepthTexture, + &'static BinnedRenderPhase, + &'static BinnedRenderPhase, Option<&'static SkyboxPipelineId>, Option<&'static SkyboxBindGroup>, &'static ViewUniformOffset, @@ -41,9 +42,10 @@ impl ViewNode for MainOpaquePass3dNode { render_context: &mut RenderContext<'w>, ( camera, - extracted_view, target, depth, + opaque_phase, + alpha_mask_phase, skybox_pipeline, skybox_bind_group, view_uniform_offset, @@ -51,20 +53,6 @@ impl ViewNode for MainOpaquePass3dNode { ): QueryItem<'w, '_, Self::ViewQuery>, world: &'w World, ) -> Result<(), NodeRunError> { - let (Some(opaque_phases), Some(alpha_mask_phases)) = ( - world.get_resource::>(), - world.get_resource::>(), - ) else { - return Ok(()); - }; - - let (Some(opaque_phase), Some(alpha_mask_phase)) = ( - opaque_phases.get(&extracted_view.retained_view_entity), - alpha_mask_phases.get(&extracted_view.retained_view_entity), - ) else { - return Ok(()); - }; - let diagnostics = render_context.diagnostic_recorder(); let color_attachments = [Some(target.get_color_attachment())]; diff --git a/crates/bevy_core_pipeline/src/core_3d/main_transmissive_pass_3d_node.rs b/crates/bevy_core_pipeline/src/core_3d/main_transmissive_pass_3d_node.rs index 1319534bf3971..345b62b5f6c05 100644 --- a/crates/bevy_core_pipeline/src/core_3d/main_transmissive_pass_3d_node.rs +++ b/crates/bevy_core_pipeline/src/core_3d/main_transmissive_pass_3d_node.rs @@ -7,10 +7,10 @@ use bevy_render::{ camera::ExtractedCamera, diagnostic::RecordDiagnostics, render_graph::{NodeRunError, RenderGraphContext, ViewNode}, - render_phase::ViewSortedRenderPhases, + render_phase::SortedRenderPhase, render_resource::{RenderPassDescriptor, StoreOp}, renderer::RenderContext, - view::{ExtractedView, ViewDepthTexture, ViewTarget}, + view::{ViewDepthTexture, ViewTarget}, }; use core::ops::Range; use tracing::error; @@ -18,16 +18,16 @@ use tracing::error; use tracing::info_span; /// A [`bevy_render::render_graph::Node`] that runs the [`Transmissive3d`] -/// [`ViewSortedRenderPhases`]. +/// [`SortedRenderPhase`]s. #[derive(Default)] pub struct MainTransmissivePass3dNode; impl ViewNode for MainTransmissivePass3dNode { type ViewQuery = ( &'static ExtractedCamera, - &'static ExtractedView, &'static Camera3d, &'static ViewTarget, + &'static SortedRenderPhase, Option<&'static ViewTransmissionTexture>, &'static ViewDepthTexture, Option<&'static MainPassResolutionOverride>, @@ -37,23 +37,19 @@ impl ViewNode for MainTransmissivePass3dNode { &self, graph: &mut RenderGraphContext, render_context: &mut RenderContext, - (camera, view, camera_3d, target, transmission, depth, resolution_override): QueryItem< - Self::ViewQuery, - >, + ( + camera, + camera_3d, + target, + transmissive_phase, + transmission, + depth, + resolution_override, + ): QueryItem, world: &World, ) -> Result<(), NodeRunError> { let view_entity = graph.view_entity(); - let Some(transmissive_phases) = - world.get_resource::>() - else { - return Ok(()); - }; - - let Some(transmissive_phase) = transmissive_phases.get(&view.retained_view_entity) else { - return Ok(()); - }; - let diagnostics = render_context.diagnostic_recorder(); let physical_target_size = camera.physical_target_size.unwrap(); diff --git a/crates/bevy_core_pipeline/src/core_3d/main_transparent_pass_3d_node.rs b/crates/bevy_core_pipeline/src/core_3d/main_transparent_pass_3d_node.rs index bd54b7849e9c5..0b3e631ebafb9 100644 --- a/crates/bevy_core_pipeline/src/core_3d/main_transparent_pass_3d_node.rs +++ b/crates/bevy_core_pipeline/src/core_3d/main_transparent_pass_3d_node.rs @@ -5,47 +5,37 @@ use bevy_render::{ camera::ExtractedCamera, diagnostic::RecordDiagnostics, render_graph::{NodeRunError, RenderGraphContext, ViewNode}, - render_phase::ViewSortedRenderPhases, + render_phase::SortedRenderPhase, render_resource::{RenderPassDescriptor, StoreOp}, renderer::RenderContext, - view::{ExtractedView, ViewDepthTexture, ViewTarget}, + view::{ViewDepthTexture, ViewTarget}, }; use tracing::error; #[cfg(feature = "trace")] use tracing::info_span; /// A [`bevy_render::render_graph::Node`] that runs the [`Transparent3d`] -/// [`ViewSortedRenderPhases`]. +/// [`SortedRenderPhase`]s. #[derive(Default)] pub struct MainTransparentPass3dNode; impl ViewNode for MainTransparentPass3dNode { type ViewQuery = ( &'static ExtractedCamera, - &'static ExtractedView, &'static ViewTarget, &'static ViewDepthTexture, + &'static SortedRenderPhase, Option<&'static MainPassResolutionOverride>, ); fn run( &self, graph: &mut RenderGraphContext, render_context: &mut RenderContext, - (camera, view, target, depth, resolution_override): QueryItem, + (camera, target, depth, transparent_phase, resolution_override): QueryItem, world: &World, ) -> Result<(), NodeRunError> { let view_entity = graph.view_entity(); - let Some(transparent_phases) = - world.get_resource::>() - else { - return Ok(()); - }; - - let Some(transparent_phase) = transparent_phases.get(&view.retained_view_entity) else { - return Ok(()); - }; - if !transparent_phase.items.is_empty() { // Run the transparent pass, sorted back-to-front // NOTE: Scoped to drop the mutable borrow of render_context diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index 5a090b5da610a..afd8b44005595 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -75,13 +75,12 @@ use core::ops::Range; use bevy_camera::{Camera, Camera3d, Camera3dDepthLoadOp}; use bevy_diagnostic::FrameCount; use bevy_render::{ - batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport}, camera::CameraRenderGraph, experimental::occlusion_culling::OcclusionCulling, mesh::allocator::SlabId, - render_phase::PhaseItemBatchSetKey, + render_phase::{BinnedRenderPhase, PhaseItemBatchSetKey, SortedRenderPhase}, texture::CachedTexture, - view::{prepare_view_targets, NoIndirectDrawing, RetainedViewEntity}, + view::{prepare_view_targets, NoIndirectDrawing}, }; pub use main_opaque_pass_3d_node::*; pub use main_transparent_pass_3d_node::*; @@ -92,7 +91,7 @@ use bevy_color::LinearRgba; use bevy_ecs::prelude::*; use bevy_image::{BevyDefault, ToExtents}; use bevy_math::FloatOrd; -use bevy_platform::collections::{HashMap, HashSet}; +use bevy_platform::collections::HashMap; use bevy_render::{ camera::ExtractedCamera, extract_component::ExtractComponentPlugin, @@ -100,18 +99,17 @@ use bevy_render::{ render_graph::{EmptyNode, RenderGraphExt, ViewNodeRunner}, render_phase::{ sort_phase_system, BinnedPhaseItem, CachedRenderPipelinePhaseItem, DrawFunctionId, - DrawFunctions, PhaseItem, PhaseItemExtraIndex, SortedPhaseItem, ViewBinnedRenderPhases, - ViewSortedRenderPhases, + DrawFunctions, PhaseItem, PhaseItemExtraIndex, SortedPhaseItem, }, render_resource::{ CachedRenderPipelineId, FilterMode, Sampler, SamplerDescriptor, Texture, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, TextureView, }, renderer::RenderDevice, - sync_world::{MainEntity, RenderEntity}, + sync_world::MainEntity, texture::{ColorAttachment, TextureCache}, view::{ExtractedView, ViewDepthTexture, ViewTarget}, - Extract, ExtractSchedule, Render, RenderApp, RenderSystems, + Render, RenderApp, RenderSystems, }; use nonmax::NonMaxU32; use tracing::warn; @@ -162,16 +160,6 @@ impl Plugin for Core3dPlugin { .init_resource::>() .init_resource::>() .init_resource::>() - .init_resource::>() - .init_resource::>() - .init_resource::>() - .init_resource::>() - .init_resource::>() - .init_resource::>() - .init_resource::>() - .init_resource::>() - .add_systems(ExtractSchedule, extract_core_3d_camera_phases) - .add_systems(ExtractSchedule, extract_camera_prepass_phase) .add_systems( Render, ( @@ -619,202 +607,28 @@ impl CachedRenderPipelinePhaseItem for Transparent3d { } } -pub fn extract_core_3d_camera_phases( - mut opaque_3d_phases: ResMut>, - mut alpha_mask_3d_phases: ResMut>, - mut transmissive_3d_phases: ResMut>, - mut transparent_3d_phases: ResMut>, - cameras_3d: Extract), With>>, - mut live_entities: Local>, - gpu_preprocessing_support: Res, -) { - live_entities.clear(); - - for (main_entity, camera, no_indirect_drawing) in &cameras_3d { - if !camera.is_active { - continue; - } - - // If GPU culling is in use, use it (and indirect mode); otherwise, just - // preprocess the meshes. - let gpu_preprocessing_mode = gpu_preprocessing_support.min(if !no_indirect_drawing { - GpuPreprocessingMode::Culling - } else { - GpuPreprocessingMode::PreprocessingOnly - }); - - // This is the main 3D camera, so use the first subview index (0). - let retained_view_entity = RetainedViewEntity::new(main_entity.into(), None, 0); - - opaque_3d_phases.prepare_for_new_frame(retained_view_entity, gpu_preprocessing_mode); - alpha_mask_3d_phases.prepare_for_new_frame(retained_view_entity, gpu_preprocessing_mode); - transmissive_3d_phases.insert_or_clear(retained_view_entity); - transparent_3d_phases.insert_or_clear(retained_view_entity); - - live_entities.insert(retained_view_entity); - } - - opaque_3d_phases.retain(|view_entity, _| live_entities.contains(view_entity)); - alpha_mask_3d_phases.retain(|view_entity, _| live_entities.contains(view_entity)); - transmissive_3d_phases.retain(|view_entity, _| live_entities.contains(view_entity)); - transparent_3d_phases.retain(|view_entity, _| live_entities.contains(view_entity)); -} - -// Extract the render phases for the prepass - -pub fn extract_camera_prepass_phase( - mut commands: Commands, - mut opaque_3d_prepass_phases: ResMut>, - mut alpha_mask_3d_prepass_phases: ResMut>, - mut opaque_3d_deferred_phases: ResMut>, - mut alpha_mask_3d_deferred_phases: ResMut>, - cameras_3d: Extract< - Query< - ( - Entity, - RenderEntity, - &Camera, - Has, - Has, - Has, - Has, - Has, - Has, - Has, - ), - With, - >, - >, - mut live_entities: Local>, - gpu_preprocessing_support: Res, -) { - live_entities.clear(); - - for ( - main_entity, - entity, - camera, - no_indirect_drawing, - depth_prepass, - normal_prepass, - motion_vector_prepass, - deferred_prepass, - depth_prepass_double_buffer, - deferred_prepass_double_buffer, - ) in cameras_3d.iter() - { - if !camera.is_active { - continue; - } - - // If GPU culling is in use, use it (and indirect mode); otherwise, just - // preprocess the meshes. - let gpu_preprocessing_mode = gpu_preprocessing_support.min(if !no_indirect_drawing { - GpuPreprocessingMode::Culling - } else { - GpuPreprocessingMode::PreprocessingOnly - }); - - // This is the main 3D camera, so we use the first subview index (0). - let retained_view_entity = RetainedViewEntity::new(main_entity.into(), None, 0); - - if depth_prepass || normal_prepass || motion_vector_prepass { - opaque_3d_prepass_phases - .prepare_for_new_frame(retained_view_entity, gpu_preprocessing_mode); - alpha_mask_3d_prepass_phases - .prepare_for_new_frame(retained_view_entity, gpu_preprocessing_mode); - } else { - opaque_3d_prepass_phases.remove(&retained_view_entity); - alpha_mask_3d_prepass_phases.remove(&retained_view_entity); - } - - if deferred_prepass { - opaque_3d_deferred_phases - .prepare_for_new_frame(retained_view_entity, gpu_preprocessing_mode); - alpha_mask_3d_deferred_phases - .prepare_for_new_frame(retained_view_entity, gpu_preprocessing_mode); - } else { - opaque_3d_deferred_phases.remove(&retained_view_entity); - alpha_mask_3d_deferred_phases.remove(&retained_view_entity); - } - live_entities.insert(retained_view_entity); - - // Add or remove prepasses as appropriate. - - let mut camera_commands = commands - .get_entity(entity) - .expect("Camera entity wasn't synced."); - - if depth_prepass { - camera_commands.insert(DepthPrepass); - } else { - camera_commands.remove::(); - } - - if normal_prepass { - camera_commands.insert(NormalPrepass); - } else { - camera_commands.remove::(); - } - - if motion_vector_prepass { - camera_commands.insert(MotionVectorPrepass); - } else { - camera_commands.remove::(); - } - - if deferred_prepass { - camera_commands.insert(DeferredPrepass); - } else { - camera_commands.remove::(); - } - - if depth_prepass_double_buffer { - camera_commands.insert(DepthPrepassDoubleBuffer); - } else { - camera_commands.remove::(); - } - - if deferred_prepass_double_buffer { - camera_commands.insert(DeferredPrepassDoubleBuffer); - } else { - camera_commands.remove::(); - } - } - - opaque_3d_prepass_phases.retain(|view_entity, _| live_entities.contains(view_entity)); - alpha_mask_3d_prepass_phases.retain(|view_entity, _| live_entities.contains(view_entity)); - opaque_3d_deferred_phases.retain(|view_entity, _| live_entities.contains(view_entity)); - alpha_mask_3d_deferred_phases.retain(|view_entity, _| live_entities.contains(view_entity)); -} - pub fn prepare_core_3d_depth_textures( mut commands: Commands, mut texture_cache: ResMut, render_device: Res, - opaque_3d_phases: Res>, - alpha_mask_3d_phases: Res>, - transmissive_3d_phases: Res>, - transparent_3d_phases: Res>, - views_3d: Query<( - Entity, - &ExtractedCamera, - &ExtractedView, - Option<&DepthPrepass>, - &Camera3d, - &Msaa, - )>, + views_3d: Query< + ( + Entity, + &ExtractedCamera, + Option<&DepthPrepass>, + &Camera3d, + &Msaa, + ), + ( + With>, + With>, + With>, + With>, + ), + >, ) { let mut render_target_usage = >::default(); - for (_, camera, extracted_view, depth_prepass, camera_3d, _msaa) in &views_3d { - if !opaque_3d_phases.contains_key(&extracted_view.retained_view_entity) - || !alpha_mask_3d_phases.contains_key(&extracted_view.retained_view_entity) - || !transmissive_3d_phases.contains_key(&extracted_view.retained_view_entity) - || !transparent_3d_phases.contains_key(&extracted_view.retained_view_entity) - { - continue; - }; - + for (_, camera, depth_prepass, camera_3d, _msaa) in &views_3d { // Default usage required to write to the depth texture let mut usage: TextureUsages = camera_3d.depth_texture_usages.into(); if depth_prepass.is_some() { @@ -828,7 +642,7 @@ pub fn prepare_core_3d_depth_textures( } let mut textures = >::default(); - for (entity, camera, _, _, camera_3d, msaa) in &views_3d { + for (entity, camera, _, camera_3d, msaa) in &views_3d { let Some(physical_target_size) = camera.physical_target_size else { continue; }; @@ -877,26 +691,23 @@ pub fn prepare_core_3d_transmission_textures( mut commands: Commands, mut texture_cache: ResMut, render_device: Res, - opaque_3d_phases: Res>, - alpha_mask_3d_phases: Res>, - transmissive_3d_phases: Res>, - transparent_3d_phases: Res>, - views_3d: Query<(Entity, &ExtractedCamera, &Camera3d, &ExtractedView)>, + views_3d: Query< + ( + Entity, + &ExtractedCamera, + &Camera3d, + &ExtractedView, + &SortedRenderPhase, + ), + ( + With>, + With>, + With>, + ), + >, ) { let mut textures = >::default(); - for (entity, camera, camera_3d, view) in &views_3d { - if !opaque_3d_phases.contains_key(&view.retained_view_entity) - || !alpha_mask_3d_phases.contains_key(&view.retained_view_entity) - || !transparent_3d_phases.contains_key(&view.retained_view_entity) - { - continue; - }; - - let Some(transmissive_3d_phase) = transmissive_3d_phases.get(&view.retained_view_entity) - else { - continue; - }; - + for (entity, camera, camera_3d, view, transmissive_3d_phase) in &views_3d { let Some(physical_target_size) = camera.physical_target_size else { continue; }; @@ -993,22 +804,27 @@ pub fn prepare_prepass_textures( mut texture_cache: ResMut, render_device: Res, frame_count: Res, - opaque_3d_prepass_phases: Res>, - alpha_mask_3d_prepass_phases: Res>, - opaque_3d_deferred_phases: Res>, - alpha_mask_3d_deferred_phases: Res>, - views_3d: Query<( - Entity, - &ExtractedCamera, - &ExtractedView, - &Msaa, - Has, - Has, - Has, - Has, - Has, - Has, - )>, + views_3d: Query< + ( + Entity, + &ExtractedCamera, + &Msaa, + Has, + Has, + Has, + Has, + Has, + Has, + ), + ( + Or<( + With>, + With>, + With>, + With>, + )>, + ), + >, ) { let mut depth_textures1 = >::default(); let mut depth_textures2 = >::default(); @@ -1020,7 +836,6 @@ pub fn prepare_prepass_textures( for ( entity, camera, - view, msaa, depth_prepass, normal_prepass, @@ -1030,15 +845,6 @@ pub fn prepare_prepass_textures( deferred_prepass_double_buffer, ) in &views_3d { - if !opaque_3d_prepass_phases.contains_key(&view.retained_view_entity) - && !alpha_mask_3d_prepass_phases.contains_key(&view.retained_view_entity) - && !opaque_3d_deferred_phases.contains_key(&view.retained_view_entity) - && !alpha_mask_3d_deferred_phases.contains_key(&view.retained_view_entity) - { - commands.entity(entity).remove::(); - continue; - }; - let Some(physical_target_size) = camera.physical_target_size else { continue; }; diff --git a/crates/bevy_core_pipeline/src/deferred/node.rs b/crates/bevy_core_pipeline/src/deferred/node.rs index 49bf3dc5adf19..68bded2c6d07e 100644 --- a/crates/bevy_core_pipeline/src/deferred/node.rs +++ b/crates/bevy_core_pipeline/src/deferred/node.rs @@ -1,18 +1,16 @@ use bevy_camera::{MainPassResolutionOverride, Viewport}; use bevy_ecs::{prelude::*, query::QueryItem}; -use bevy_render::experimental::occlusion_culling::OcclusionCulling; -use bevy_render::render_graph::ViewNode; - -use bevy_render::view::{ExtractedView, NoIndirectDrawing}; use bevy_render::{ camera::ExtractedCamera, diagnostic::RecordDiagnostics, - render_graph::{NodeRunError, RenderGraphContext}, - render_phase::{TrackedRenderPass, ViewBinnedRenderPhases}, + experimental::occlusion_culling::OcclusionCulling, + render_graph::{NodeRunError, RenderGraphContext, ViewNode}, + render_phase::{BinnedRenderPhase, TrackedRenderPass}, render_resource::{CommandEncoderDescriptor, RenderPassDescriptor, StoreOp}, renderer::RenderContext, - view::ViewDepthTexture, + view::{NoIndirectDrawing, ViewDepthTexture}, }; + use tracing::error; #[cfg(feature = "trace")] use tracing::info_span; @@ -65,9 +63,10 @@ pub struct LateDeferredGBufferPrepassNode; impl ViewNode for LateDeferredGBufferPrepassNode { type ViewQuery = ( &'static ExtractedCamera, - &'static ExtractedView, &'static ViewDepthTexture, &'static ViewPrepassTextures, + &'static BinnedRenderPhase, + &'static BinnedRenderPhase, Option<&'static MainPassResolutionOverride>, Has, Has, @@ -108,29 +107,20 @@ impl ViewNode for LateDeferredGBufferPrepassNode { fn run_deferred_prepass<'w>( graph: &mut RenderGraphContext, render_context: &mut RenderContext<'w>, - (camera, extracted_view, view_depth_texture, view_prepass_textures, resolution_override, _, _): QueryItem< - 'w, - '_, - ::ViewQuery, - >, + ( + camera, + view_depth_texture, + view_prepass_textures, + opaque_deferred_phase, + alpha_mask_deferred_phase, + resolution_override, + _, + _, + ): QueryItem<'w, '_, ::ViewQuery>, is_late: bool, world: &'w World, label: &'static str, ) -> Result<(), NodeRunError> { - let (Some(opaque_deferred_phases), Some(alpha_mask_deferred_phases)) = ( - world.get_resource::>(), - world.get_resource::>(), - ) else { - return Ok(()); - }; - - let (Some(opaque_deferred_phase), Some(alpha_mask_deferred_phase)) = ( - opaque_deferred_phases.get(&extracted_view.retained_view_entity), - alpha_mask_deferred_phases.get(&extracted_view.retained_view_entity), - ) else { - return Ok(()); - }; - let diagnostic = render_context.diagnostic_recorder(); let mut color_attachments = vec![]; diff --git a/crates/bevy_core_pipeline/src/prepass/mod.rs b/crates/bevy_core_pipeline/src/prepass/mod.rs index c4ad850b348b7..f90b048337a3d 100644 --- a/crates/bevy_core_pipeline/src/prepass/mod.rs +++ b/crates/bevy_core_pipeline/src/prepass/mod.rs @@ -34,6 +34,7 @@ use bevy_asset::UntypedAssetId; use bevy_ecs::prelude::*; use bevy_math::Mat4; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; +use bevy_render::extract_component::ExtractComponent; use bevy_render::mesh::allocator::SlabId; use bevy_render::render_phase::PhaseItemBatchSetKey; use bevy_render::sync_world::MainEntity; @@ -53,13 +54,13 @@ pub const NORMAL_PREPASS_FORMAT: TextureFormat = TextureFormat::Rgb10a2Unorm; pub const MOTION_VECTOR_PREPASS_FORMAT: TextureFormat = TextureFormat::Rg16Float; /// If added to a [`bevy_camera::Camera3d`] then depth values will be copied to a separate texture available to the main pass. -#[derive(Component, Default, Reflect, Clone)] +#[derive(Component, Default, Reflect, Clone, ExtractComponent)] #[reflect(Component, Default, Clone)] pub struct DepthPrepass; /// If added to a [`bevy_camera::Camera3d`] then vertex world normals will be copied to a separate texture available to the main pass. /// Normals will have normal map textures already applied. -#[derive(Component, Default, Reflect, Clone)] +#[derive(Component, Default, Reflect, Clone, ExtractComponent)] #[reflect(Component, Default, Clone)] pub struct NormalPrepass; @@ -67,13 +68,13 @@ pub struct NormalPrepass; /// /// Motion vectors are stored in the range -1,1, with +x right and +y down. /// A value of (1.0,1.0) indicates a pixel moved from the top left corner to the bottom right corner of the screen. -#[derive(Component, Default, Reflect, Clone)] +#[derive(Component, Default, Reflect, Clone, ExtractComponent)] #[reflect(Component, Default, Clone)] pub struct MotionVectorPrepass; /// If added to a [`bevy_camera::Camera3d`] then deferred materials will be rendered to the deferred gbuffer texture and will be available to subsequent passes. /// Note the default deferred lighting plugin also requires `DepthPrepass` to work correctly. -#[derive(Component, Default, Reflect)] +#[derive(Component, Default, Reflect, Clone, ExtractComponent)] #[reflect(Component, Default)] pub struct DeferredPrepass; diff --git a/crates/bevy_core_pipeline/src/prepass/node.rs b/crates/bevy_core_pipeline/src/prepass/node.rs index d429fc9289d76..7b931ff52c7ca 100644 --- a/crates/bevy_core_pipeline/src/prepass/node.rs +++ b/crates/bevy_core_pipeline/src/prepass/node.rs @@ -5,10 +5,10 @@ use bevy_render::{ diagnostic::RecordDiagnostics, experimental::occlusion_culling::OcclusionCulling, render_graph::{NodeRunError, RenderGraphContext, ViewNode}, - render_phase::{TrackedRenderPass, ViewBinnedRenderPhases}, + render_phase::{BinnedRenderPhase, TrackedRenderPass}, render_resource::{CommandEncoderDescriptor, PipelineCache, RenderPassDescriptor, StoreOp}, renderer::RenderContext, - view::{ExtractedView, NoIndirectDrawing, ViewDepthTexture, ViewUniformOffset}, + view::{NoIndirectDrawing, ViewDepthTexture, ViewUniformOffset}, }; use tracing::error; #[cfg(feature = "trace")] @@ -58,13 +58,13 @@ impl ViewNode for LatePrepassNode { type ViewQuery = ( ( &'static ExtractedCamera, - &'static ExtractedView, &'static ViewDepthTexture, &'static ViewPrepassTextures, &'static ViewUniformOffset, + &'static BinnedRenderPhase, + &'static BinnedRenderPhase, ), ( - Option<&'static DeferredPrepass>, Option<&'static RenderSkyboxPrepassPipeline>, Option<&'static SkyboxPrepassBindGroup>, Option<&'static PreviousViewUniformOffset>, @@ -108,9 +108,15 @@ fn run_prepass<'w>( graph: &mut RenderGraphContext, render_context: &mut RenderContext<'w>, ( - (camera, extracted_view, view_depth_texture, view_prepass_textures, view_uniform_offset), ( - deferred_prepass, + camera, + view_depth_texture, + view_prepass_textures, + view_uniform_offset, + opaque_prepass_phase, + alpha_mask_prepass_phase, + ), + ( skybox_prepass_pipeline, skybox_prepass_bind_group, view_prev_uniform_offset, @@ -128,20 +134,6 @@ fn run_prepass<'w>( return Ok(()); } - let (Some(opaque_prepass_phases), Some(alpha_mask_prepass_phases)) = ( - world.get_resource::>(), - world.get_resource::>(), - ) else { - return Ok(()); - }; - - let (Some(opaque_prepass_phase), Some(alpha_mask_prepass_phase)) = ( - opaque_prepass_phases.get(&extracted_view.retained_view_entity), - alpha_mask_prepass_phases.get(&extracted_view.retained_view_entity), - ) else { - return Ok(()); - }; - let diagnostics = render_context.diagnostic_recorder(); let mut color_attachments = vec![ @@ -238,9 +230,7 @@ fn run_prepass<'w>( drop(render_pass); // After rendering to the view depth texture, copy it to the prepass depth texture if deferred isn't going to - if deferred_prepass.is_none() - && let Some(prepass_depth_texture) = &view_prepass_textures.depth - { + if !has_deferred && let Some(prepass_depth_texture) = &view_prepass_textures.depth { command_encoder.copy_texture_to_texture( view_depth_texture.texture.as_image_copy(), prepass_depth_texture.texture.texture.as_image_copy(), diff --git a/crates/bevy_gizmos_render/src/pipeline_2d.rs b/crates/bevy_gizmos_render/src/pipeline_2d.rs index 5e745fd60ef27..8bbec760eedb7 100644 --- a/crates/bevy_gizmos_render/src/pipeline_2d.rs +++ b/crates/bevy_gizmos_render/src/pipeline_2d.rs @@ -20,8 +20,7 @@ use bevy_math::FloatOrd; use bevy_render::{ render_asset::{prepare_assets, RenderAssets}, render_phase::{ - AddRenderCommand, DrawFunctions, PhaseItemExtraIndex, SetItemPipeline, - ViewSortedRenderPhases, + AddRenderCommand, DrawFunctions, PhaseItemExtraIndex, SetItemPipeline, SortedRenderPhase, }, render_resource::*, view::{ExtractedView, Msaa, ViewTarget}, @@ -294,8 +293,12 @@ fn queue_line_gizmos_2d( pipeline_cache: Res, line_gizmos: Query<(Entity, &MainEntity, &GizmoMeshConfig)>, line_gizmo_assets: Res>, - mut transparent_render_phases: ResMut>, - mut views: Query<(&ExtractedView, &Msaa, Option<&RenderLayers>)>, + mut views: Query<( + &ExtractedView, + &Msaa, + Option<&RenderLayers>, + &mut SortedRenderPhase, + )>, ) { let draw_function = draw_functions.read().get_id::().unwrap(); let draw_function_strip = draw_functions @@ -303,12 +306,7 @@ fn queue_line_gizmos_2d( .get_id::() .unwrap(); - for (view, msaa, render_layers) in &mut views { - let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity) - else { - continue; - }; - + for (view, msaa, render_layers, mut transparent_phase) in views.iter_mut() { let mesh_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples()) | Mesh2dPipelineKey::from_hdr(view.hdr); @@ -375,20 +373,19 @@ fn queue_line_joint_gizmos_2d( pipeline_cache: Res, line_gizmos: Query<(Entity, &MainEntity, &GizmoMeshConfig)>, line_gizmo_assets: Res>, - mut transparent_render_phases: ResMut>, - mut views: Query<(&ExtractedView, &Msaa, Option<&RenderLayers>)>, + mut views: Query<( + &ExtractedView, + &Msaa, + Option<&RenderLayers>, + &mut SortedRenderPhase, + )>, ) { let draw_function = draw_functions .read() .get_id::() .unwrap(); - for (view, msaa, render_layers) in &mut views { - let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity) - else { - continue; - }; - + for (view, msaa, render_layers, mut transparent_phase) in views.iter_mut() { let mesh_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples()) | Mesh2dPipelineKey::from_hdr(view.hdr); diff --git a/crates/bevy_gizmos_render/src/pipeline_3d.rs b/crates/bevy_gizmos_render/src/pipeline_3d.rs index 00fda49ee587b..0a71228ad84bc 100644 --- a/crates/bevy_gizmos_render/src/pipeline_3d.rs +++ b/crates/bevy_gizmos_render/src/pipeline_3d.rs @@ -26,8 +26,7 @@ use bevy_pbr::{MeshPipeline, MeshPipelineKey, SetMeshViewBindGroup}; use bevy_render::{ render_asset::{prepare_assets, RenderAssets}, render_phase::{ - AddRenderCommand, DrawFunctions, PhaseItemExtraIndex, SetItemPipeline, - ViewSortedRenderPhases, + AddRenderCommand, DrawFunctions, PhaseItemExtraIndex, SetItemPipeline, SortedRenderPhase, }, render_resource::*, view::{ExtractedView, Msaa, ViewTarget}, @@ -294,10 +293,10 @@ fn queue_line_gizmos_3d( pipeline_cache: Res, line_gizmos: Query<(Entity, &MainEntity, &GizmoMeshConfig)>, line_gizmo_assets: Res>, - mut transparent_render_phases: ResMut>, - views: Query<( + mut views: Query<( &ExtractedView, &Msaa, + &mut SortedRenderPhase, Option<&RenderLayers>, ( Has, @@ -317,15 +316,11 @@ fn queue_line_gizmos_3d( for ( view, msaa, + mut transparent_phase, render_layers, (normal_prepass, depth_prepass, motion_vector_prepass, deferred_prepass, oit), - ) in &views + ) in &mut views { - let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity) - else { - continue; - }; - let render_layers = render_layers.unwrap_or_default(); let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples()) @@ -414,10 +409,10 @@ fn queue_line_joint_gizmos_3d( pipeline_cache: Res, line_gizmos: Query<(Entity, &MainEntity, &GizmoMeshConfig)>, line_gizmo_assets: Res>, - mut transparent_render_phases: ResMut>, - views: Query<( + mut views: Query<( &ExtractedView, &Msaa, + &mut SortedRenderPhase, Option<&RenderLayers>, ( Has, @@ -435,15 +430,11 @@ fn queue_line_joint_gizmos_3d( for ( view, msaa, + mut transparent_phase, render_layers, (normal_prepass, depth_prepass, motion_vector_prepass, deferred_prepass), - ) in &views + ) in &mut views { - let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity) - else { - continue; - }; - let render_layers = render_layers.unwrap_or_default(); let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples()) diff --git a/crates/bevy_mesh/src/lib.rs b/crates/bevy_mesh/src/lib.rs index 5774761791098..9290d0d1e3311 100644 --- a/crates/bevy_mesh/src/lib.rs +++ b/crates/bevy_mesh/src/lib.rs @@ -42,7 +42,7 @@ bitflags! { /// downward. The PBR mesh pipeline key bits start from the lowest bit and /// go upward. This allows the PBR bits in the downstream crate `bevy_pbr` /// to coexist in the same field without any shifts. - #[derive(Clone, Debug)] + #[derive(Copy, Clone, Debug)] pub struct BaseMeshPipelineKey: u64 { const MORPH_TARGETS = 1 << (u64::BITS - 1); } diff --git a/crates/bevy_pbr/Cargo.toml b/crates/bevy_pbr/Cargo.toml index 6e9efc0faf49b..f50845e1e80b5 100644 --- a/crates/bevy_pbr/Cargo.toml +++ b/crates/bevy_pbr/Cargo.toml @@ -60,12 +60,14 @@ bevy_utils = { path = "../bevy_utils", version = "0.18.0-dev" } bevy_platform = { path = "../bevy_platform", version = "0.18.0-dev", default-features = false, features = [ "std", ] } +bevy_pbr_macros = { path = "macros", version = "0.18.0-dev" } # other bitflags = { version = "2.3", features = ["bytemuck"] } fixedbitset = "0.5" thiserror = { version = "2", default-features = false } derive_more = { version = "2", default-features = false, features = ["from"] } +variadics_please = "1.1" # meshlet lz4_flex = { version = "0.12", default-features = false, features = [ "frame", diff --git a/crates/bevy_pbr/macros/Cargo.toml b/crates/bevy_pbr/macros/Cargo.toml new file mode 100644 index 0000000000000..7a120fc4fd16c --- /dev/null +++ b/crates/bevy_pbr/macros/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "bevy_pbr_macros" +version = "0.18.0-dev" +edition = "2024" +description = "Derive implementations for bevy_pbr" +homepage = "https://bevy.org" +repository = "https://github.com/bevyengine/bevy" +license = "MIT OR Apache-2.0" +keywords = ["bevy"] + +[lib] +proc-macro = true + +[dependencies] +bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.18.0-dev" } + +syn = { version = "2.0", features = ["full"] } +proc-macro2 = "1.0" +quote = "1.0" + +[lints] +workspace = true + +[package.metadata.docs.rs] +rustdoc-args = ["-Zunstable-options", "--generate-link-to-definition"] +all-features = true diff --git a/crates/bevy_pbr/macros/LICENSE-APACHE b/crates/bevy_pbr/macros/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_pbr/macros/LICENSE-APACHE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/crates/bevy_pbr/macros/LICENSE-MIT b/crates/bevy_pbr/macros/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_pbr/macros/LICENSE-MIT @@ -0,0 +1,19 @@ +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/crates/bevy_pbr/macros/src/lib.rs b/crates/bevy_pbr/macros/src/lib.rs new file mode 100644 index 0000000000000..fe313299a17c5 --- /dev/null +++ b/crates/bevy_pbr/macros/src/lib.rs @@ -0,0 +1,538 @@ +#![expect(missing_docs, reason = "Not all docs are written yet, see #3492.")] +#![cfg_attr(docsrs, feature(doc_cfg))] + +use core::ops::Not; + +use bevy_macro_utils::BevyManifest; +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, Data, DeriveInput, Field, Index, Member}; + +const BINNED: &str = "BinnedPhaseItem"; +const SORTED: &str = "SortedPhaseItem"; + +const PHASE_ITEM_ATTR: &str = "phase_item"; +const SKIP_ATTR: &str = "skip"; + +const PHASE_ITEM_TRAITS: [&str; 7] = [ + "PhaseItem", // 0 + "BinnedPhaseItem", // 1 + "SortedPhaseItem", // 2 + "CachedRenderPipelinePhaseItem", // 3 + "QueueBinnedPhaseItem", // 4 + "QueueSortedPhaseItem", // 5 + "PhaseItemExt", // 6 +]; + +const BINNED_BLACKLIST: [usize; 1] = [1]; +const SORTED_BLACKLIST: [usize; 1] = [5]; + +pub(crate) fn bevy_render_path() -> syn::Path { + BevyManifest::shared(|manifest| manifest.get_path("bevy_render")) +} + +pub(crate) fn bevy_ecs_path() -> syn::Path { + BevyManifest::shared(|manifest| manifest.get_path("bevy_ecs")) +} + +pub(crate) fn bevy_pbr_path() -> syn::Path { + BevyManifest::shared(|manifest| manifest.get_path("bevy_pbr")) +} + +/// Implements `PhaseItem`, `BinnedPhaseItem`, `CachedRenderPipelinePhaseItem`, +/// `QueueBinnedPhaseItem` and `PhaseItemExt` for a wrapper type. +/// +/// ### Newtypes +/// For single-field tuple structs, all traits are derived automatically. +/// +/// ### Non-newtype structs +/// For other struct forms, `BinnedPhaseItem` cannot be derived automatically and must be +/// skipped explicitly using `#[phase_item(skip(...))]`. +/// +/// The `#[phase_item]` attribute is also responsible for indicating the inner phase item field. +#[proc_macro_derive(BinnedPhaseItem, attributes(phase_item))] +pub fn derive_binned_phase_item(input: TokenStream) -> TokenStream { + derive_phase_item(input, true) +} + +/// Implements `PhaseItem`, `SortedPhaseItem`, `CachedRenderPipelinePhaseItem`, +/// `QueueSortedPhaseItem` and `PhaseItemExt` for a wrapper type. +/// +/// NOTE: Currently, we are using the default implementation of `sort` for `SortedPhaseItem`. +/// +/// ### Newtypes +/// For single-field tuple structs, all traits are derived automatically. +/// +/// ### Non-newtype structs +/// For other struct forms, `QueueSortedPhaseItem` cannot be derived automatically and must be +/// skipped explicitly using `#[phase_item(skip(...))]`. +/// +/// The `#[phase_item]` attribute is responsible for indicating the inner phase item field. +#[proc_macro_derive(SortedPhaseItem, attributes(phase_item))] +pub fn derive_sorted_phase_item(input: TokenStream) -> TokenStream { + derive_phase_item(input, false) +} + +fn derive_phase_item(input: TokenStream, is_binned: bool) -> TokenStream { + let ast = parse_macro_input!(input as DeriveInput); + + let (member, inner_ty, skip_list) = match get_phase_item_field(&ast, is_binned) { + Ok(value) => value, + Err(err) => return err.to_compile_error().into(), + }; + + let bevy_render = bevy_render_path(); + let bevy_ecs = bevy_ecs_path(); + let bevy_pbr = bevy_pbr_path(); + + let struct_name = &ast.ident; + let generics = &ast.generics; + let (impl_generics, type_generics, where_clause) = generics.split_for_impl(); + + let phase_item_impl = + skip_list + .contains(&PHASE_ITEM_TRAITS[0]) + .not() + .then_some(impl_phase_item( + struct_name, + &impl_generics, + &type_generics, + where_clause, + &member, + &bevy_render, + &bevy_ecs, + )); + + let x_phase_item_impl = if is_binned { + skip_list + .contains(&PHASE_ITEM_TRAITS[1]) + .not() + .then_some(impl_binned_phase_item( + struct_name, + &impl_generics, + &type_generics, + where_clause, + inner_ty, + &bevy_render, + &bevy_ecs, + )) + } else { + skip_list + .contains(&PHASE_ITEM_TRAITS[2]) + .not() + .then_some(impl_sorted_phase_item( + struct_name, + &impl_generics, + &type_generics, + where_clause, + &member, + inner_ty, + &bevy_render, + )) + }; + + let cached_pipeline_impl = + skip_list + .contains(&PHASE_ITEM_TRAITS[3]) + .not() + .then_some(impl_cached_pipeline( + struct_name, + &impl_generics, + &type_generics, + &member, + where_clause, + &bevy_render, + )); + + let queue_x_phase_item_impl = if is_binned { + skip_list + .contains(&PHASE_ITEM_TRAITS[4]) + .not() + .then_some(impl_queue_binned_phase_item( + struct_name, + &impl_generics, + &type_generics, + where_clause, + inner_ty, + &bevy_pbr, + &bevy_render, + )) + } else { + skip_list + .contains(&PHASE_ITEM_TRAITS[5]) + .not() + .then_some(impl_queue_sorted_phase_item( + struct_name, + &impl_generics, + &type_generics, + where_clause, + inner_ty, + &bevy_pbr, + )) + }; + + let phase_item_ext_impl = + skip_list + .contains(&PHASE_ITEM_TRAITS[6]) + .not() + .then_some(impl_phase_item_ext( + struct_name, + &impl_generics, + &type_generics, + where_clause, + inner_ty, + &bevy_pbr, + )); + + TokenStream::from(quote! { + #phase_item_impl + #x_phase_item_impl + #cached_pipeline_impl + #queue_x_phase_item_impl + #phase_item_ext_impl + }) +} + +fn get_phase_item_field( + ast: &DeriveInput, + is_binned: bool, +) -> syn::Result<(Member, &syn::Type, Vec<&str>)> { + let phase_item_kind = if is_binned { BINNED } else { SORTED }; + + let Data::Struct(data_struct) = &ast.data else { + return Err(syn::Error::new_spanned( + &ast.ident, + format!("`#[derive({phase_item_kind})]` is only supported for structs. Please ensure your type is a struct."), + )); + }; + + if data_struct.fields.is_empty() { + return Err(syn::Error::new_spanned( + &ast.ident, + format!("{phase_item_kind} cannot be derived on field-less structs"), + )); + } + + // Collect all fields with #[phase_item] + let mut marked_fields: Vec<_> = data_struct + .fields + .iter() + .enumerate() + .filter_map(|(idx, field)| { + field + .attrs + .iter() + .find(|a| a.path().is_ident(PHASE_ITEM_ATTR)) + .map(|attr| (idx, field, attr)) + }) + .collect(); + + if marked_fields.len() > 1 { + let (_, _, second_attr) = marked_fields[1]; + return Err(syn::Error::new_spanned( + second_attr, + format!("`#[{PHASE_ITEM_ATTR}]` attribute can only be used on a single field"), + )); + } + + let (index, field, phase_item_attr) = match marked_fields.pop() { + // Handle explicit marking + Some((idx, field, attr)) => (idx, field, Some(attr)), + // Auto select for single field + None if data_struct.fields.len() == 1 => { + (0, data_struct.fields.iter().next().unwrap(), None) + } + None => { + return Err(syn::Error::new_spanned( + &ast.ident, + format!( + "`#[derive({phase_item_kind})]` requires a field with the `#[{PHASE_ITEM_ATTR}]` attribute on multi-field structs", + ), + )); + } + }; + + let mut skip_list = Vec::new(); + if let Some(attr) = phase_item_attr { + attr.parse_nested_meta(|meta| { + if meta.path.is_ident(SKIP_ATTR) { + meta.parse_nested_meta(|inner_meta| { + let trait_name = inner_meta + .path + .get_ident() + .ok_or_else(|| { + syn::Error::new_spanned(&inner_meta.path, "expected identifier") + })? + .to_string(); + + if let Some(idx) = PHASE_ITEM_TRAITS + .iter() + .position(|i| *i == trait_name.as_str()) + { + skip_list.push(PHASE_ITEM_TRAITS[idx]); + } else { + return Err(syn::Error::new_spanned( + &inner_meta.path, + format!( + "unexpected trait `{}`, expected one of: {}", + trait_name, + PHASE_ITEM_TRAITS.join(", ") + ), + )); + } + Ok(()) + }) + } else { + Err(meta.error(format!("unexpected attribute, expected `{SKIP_ATTR}`"))) + } + })?; + } + + let is_single_field = data_struct.fields.len() == 1; + let is_tuple_struct = matches!(data_struct.fields, syn::Fields::Unnamed(_)); + + if !(is_single_field && is_tuple_struct) { + let blacklist: Vec<_> = if is_binned { + &BINNED_BLACKLIST + } else { + &SORTED_BLACKLIST + } + .iter() + .map(|&idx| PHASE_ITEM_TRAITS[idx]) + .collect(); + + let all_traits_valid = blacklist.iter().all(|i| skip_list.contains(i)); + if !all_traits_valid { + return Err(syn::Error::new_spanned( + &ast.ident, + format!("`#[derive({phase_item_kind})]` can only implement the trait `{}` for single-field tuple structs.\n\ + help: Use `#[phase_item(skip({}))]` on the field to skip it.", blacklist.join(", "), blacklist.join(", ")), + )); + } + } + + let member = to_member(field, index); + Ok((member, &field.ty, skip_list)) +} + +fn to_member(field: &Field, index: usize) -> Member { + field + .ident + .as_ref() + .map(|name| Member::Named(name.clone())) + .unwrap_or_else(|| Member::Unnamed(Index::from(index))) +} + +fn impl_phase_item( + struct_name: &syn::Ident, + impl_generics: &impl quote::ToTokens, + type_generics: &impl quote::ToTokens, + where_clause: Option<&syn::WhereClause>, + member: &Member, + bevy_render: &syn::Path, + bevy_ecs: &syn::Path, +) -> proc_macro2::TokenStream { + quote! { + impl #impl_generics #bevy_render::render_phase::PhaseItem + for #struct_name #type_generics #where_clause + { + #[inline] + fn entity(&self) -> #bevy_ecs::entity::Entity { + self.#member.entity() + } + + #[inline] + fn main_entity(&self) -> #bevy_render::sync_world::MainEntity { + self.#member.main_entity() + } + + #[inline] + fn draw_function(&self) -> #bevy_render::render_phase::DrawFunctionId { + self.#member.draw_function() + } + + #[inline] + fn batch_range(&self) -> &::core::ops::Range { + self.#member.batch_range() + } + + #[inline] + fn batch_range_mut(&mut self) -> &mut ::core::ops::Range { + self.#member.batch_range_mut() + } + + #[inline] + fn extra_index(&self) -> #bevy_render::render_phase::PhaseItemExtraIndex { + self.#member.extra_index() + } + + #[inline] + fn batch_range_and_extra_index_mut( + &mut self, + ) -> ( + &mut ::core::ops::Range, + &mut #bevy_render::render_phase::PhaseItemExtraIndex + ) { + self.#member.batch_range_and_extra_index_mut() + } + } + } +} + +fn impl_binned_phase_item( + struct_name: &syn::Ident, + impl_generics: &impl quote::ToTokens, + type_generics: &impl quote::ToTokens, + where_clause: Option<&syn::WhereClause>, + inner_ty: &syn::Type, + bevy_render: &syn::Path, + bevy_ecs: &syn::Path, +) -> proc_macro2::TokenStream { + quote! { + impl #impl_generics #bevy_render::render_phase::BinnedPhaseItem + for #struct_name #type_generics #where_clause + { + type BatchSetKey = <#inner_ty as #bevy_render::render_phase::BinnedPhaseItem>::BatchSetKey; + type BinKey = <#inner_ty as #bevy_render::render_phase::BinnedPhaseItem>::BinKey; + + #[inline] + fn new( + batch_set_key: Self::BatchSetKey, + bin_key: Self::BinKey, + representative_entity: (#bevy_ecs::entity::Entity, #bevy_render::sync_world::MainEntity), + batch_range: ::core::ops::Range, + extra_index: #bevy_render::render_phase::PhaseItemExtraIndex, + ) -> Self { + Self(<#inner_ty as #bevy_render::render_phase::BinnedPhaseItem>::new( + batch_set_key, + bin_key, + representative_entity, + batch_range, + extra_index, + )) + } + } + } +} + +fn impl_sorted_phase_item( + struct_name: &syn::Ident, + impl_generics: &impl quote::ToTokens, + type_generics: &impl quote::ToTokens, + where_clause: Option<&syn::WhereClause>, + member: &Member, + inner_ty: &syn::Type, + bevy_render: &syn::Path, +) -> proc_macro2::TokenStream { + quote! { + impl #impl_generics #bevy_render::render_phase::SortedPhaseItem + for #struct_name #type_generics #where_clause + { + type SortKey = <#inner_ty as #bevy_render::render_phase::SortedPhaseItem>::SortKey; + + #[inline] + fn sort_key(&self) -> Self::SortKey { + <#inner_ty as #bevy_render::render_phase::SortedPhaseItem>::sort_key(&self.#member) + } + + // NOTE: Currently, we are using the default implementation of `sort`. + // #[inline] + // fn sort(items: &mut [Self]) { + // // To address this, we need to convert `&mut [Newtype]` to `&mut [Inner]`. + // <#inner_ty as #bevy_render::render_phase::SortedPhaseItem>::sort(items) + // + // // The simplest solution might be to reexport `radsort` and use it directly here. + // radsort::sort_by_key(items, |item| item.sort_key().#member) + // } + + #[inline] + fn indexed(&self) -> bool { + self.#member.indexed() + } + } + } +} + +fn impl_cached_pipeline( + struct_name: &syn::Ident, + impl_generics: &impl quote::ToTokens, + type_generics: &impl quote::ToTokens, + member: &Member, + where_clause: Option<&syn::WhereClause>, + bevy_render: &syn::Path, +) -> proc_macro2::TokenStream { + quote! { + impl #impl_generics #bevy_render::render_phase::CachedRenderPipelinePhaseItem + for #struct_name #type_generics #where_clause + { + #[inline] + fn cached_pipeline(&self) -> #bevy_render::render_resource::CachedRenderPipelineId { + self.#member.cached_pipeline() + } + } + } +} + +fn impl_queue_binned_phase_item( + struct_name: &syn::Ident, + impl_generics: &impl quote::ToTokens, + type_generics: &impl quote::ToTokens, + where_clause: Option<&syn::WhereClause>, + inner_ty: &syn::Type, + bevy_pbr: &syn::Path, + bevy_render: &syn::Path, +) -> proc_macro2::TokenStream { + quote! { + impl #impl_generics #bevy_pbr::QueueBinnedPhaseItem + for #struct_name #type_generics #where_clause + { + #[inline] + fn queue_item(context: &#bevy_pbr::PhaseContext, render_phase: &mut #bevy_render::render_phase::BinnedRenderPhase) + where + BPI: #bevy_render::render_phase::BinnedPhaseItem, + { + <#inner_ty as #bevy_pbr::QueueBinnedPhaseItem>::queue_item(context, render_phase) + } + } + } +} + +fn impl_queue_sorted_phase_item( + struct_name: &syn::Ident, + impl_generics: &impl quote::ToTokens, + type_generics: &impl quote::ToTokens, + where_clause: Option<&syn::WhereClause>, + inner_ty: &syn::Type, + bevy_pbr: &syn::Path, +) -> proc_macro2::TokenStream { + quote! { + impl #impl_generics #bevy_pbr::QueueSortedPhaseItem + for #struct_name #type_generics #where_clause + { + #[inline] + fn get_item(context: &#bevy_pbr::PhaseContext) -> Option { + <#inner_ty as #bevy_pbr::QueueSortedPhaseItem>::get_item(context).map(Self) + } + } + } +} + +fn impl_phase_item_ext( + struct_name: &syn::Ident, + impl_generics: &impl quote::ToTokens, + type_generics: &impl quote::ToTokens, + where_clause: Option<&syn::WhereClause>, + inner_ty: &syn::Type, + bevy_pbr: &syn::Path, +) -> proc_macro2::TokenStream { + quote! { + impl #impl_generics #bevy_pbr::PhaseItemExt + for #struct_name #type_generics #where_clause + { + type PhaseFamily = <#inner_ty as #bevy_pbr::PhaseItemExt>::PhaseFamily; + type ExtractCondition = <#inner_ty as #bevy_pbr::PhaseItemExt>::ExtractCondition; + type RenderCommand = <#inner_ty as #bevy_pbr::PhaseItemExt>::RenderCommand; + const PHASE_TYPES: RenderPhaseType = <#inner_ty as #bevy_pbr::PhaseItemExt>::PHASE_TYPES; + } + } +} diff --git a/crates/bevy_pbr/src/extended_material.rs b/crates/bevy_pbr/src/extended_material.rs index 535128d00ec1e..342dffd107999 100644 --- a/crates/bevy_pbr/src/extended_material.rs +++ b/crates/bevy_pbr/src/extended_material.rs @@ -16,7 +16,10 @@ use bevy_render::{ }; use bevy_shader::ShaderRef; -use crate::{Material, MaterialPipeline, MaterialPipelineKey, MeshPipeline, MeshPipelineKey}; +use crate::{ + DeferredPass, MainPass, Material, MaterialPipeline, MaterialPipelineKey, MeshPass, + MeshPipeline, MeshPipelineKey, PassId, PassShaders, Prepass, ShaderSet, +}; pub struct MaterialExtensionPipeline { pub mesh_pipeline: MeshPipeline, @@ -25,12 +28,27 @@ pub struct MaterialExtensionPipeline { pub struct MaterialExtensionKey { pub mesh_key: MeshPipelineKey, pub bind_group_data: E::Data, + pub pass_id: PassId, } /// A subset of the `Material` trait for defining extensions to a base `Material`, such as the builtin `StandardMaterial`. /// /// A user type implementing the trait should be used as the `E` generic param in an `ExtendedMaterial` struct. pub trait MaterialExtension: Asset + AsBindGroup + Clone + Sized { + /// Returns this material's shaders for supported passes. + /// + /// When the traditional shader method is used, the corresponding pass's shader in the [`PassShaders`] will be ignored. + /// Currently, only [`MainPass`], [`DeferredPass`] and [`Prepass`] are supported out of the box. + fn shaders() -> PassShaders { + let mut pass_shaders = PassShaders::default(); + pass_shaders.extend([ + (Prepass::id(), ShaderSet::default()), + (DeferredPass::id(), ShaderSet::default()), + (MainPass::id(), ShaderSet::default()), + ]); + pass_shaders + } + /// Returns this material's vertex shader. If [`ShaderRef::Default`] is returned, the base material mesh vertex shader /// will be used. fn vertex_shader() -> ShaderRef { @@ -314,6 +332,10 @@ impl AsBindGroup for ExtendedMaterial { } impl Material for ExtendedMaterial { + fn shaders() -> PassShaders { + E::shaders() + } + fn vertex_shader() -> ShaderRef { match E::vertex_shader() { ShaderRef::Default => B::vertex_shader(), @@ -417,6 +439,7 @@ impl Material for ExtendedMaterial { let base_key = MaterialPipelineKey:: { mesh_key: key.mesh_key, bind_group_data: key.bind_group_data.base, + pass_id: key.pass_id, }; B::specialize(pipeline, descriptor, layout, base_key)?; @@ -430,6 +453,7 @@ impl Material for ExtendedMaterial { MaterialExtensionKey { mesh_key: key.mesh_key, bind_group_data: key.bind_group_data.extension, + pass_id: key.pass_id, }, ) } diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index 79163cb648134..e1afb0dc11c52 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -34,6 +34,7 @@ mod extended_material; mod fog; mod light_probe; mod lightmap; +mod main_pass; mod material; mod material_bind_groups; mod medium; @@ -53,6 +54,7 @@ use bevy_light::{ AmbientLight, DirectionalLight, PointLight, ShadowFilteringMethod, SimulationLightSystems, SpotLight, }; +pub use bevy_pbr_macros::{BinnedPhaseItem, SortedPhaseItem}; use bevy_shader::{load_shader_library, ShaderRef}; pub use cluster::*; pub use components::*; @@ -61,6 +63,7 @@ pub use extended_material::*; pub use fog::*; pub use light_probe::*; pub use lightmap::*; +pub use main_pass::*; pub use material::*; pub use material_bind_groups::*; pub use medium::*; @@ -222,7 +225,7 @@ impl Plugin for PbrPlugin { use_gpu_instance_buffer_builder: self.use_gpu_instance_buffer_builder, debug_flags: self.debug_flags, }, - MaterialsPlugin { + MainPassPlugin { debug_flags: self.debug_flags, }, MaterialPlugin:: { @@ -327,7 +330,6 @@ impl Plugin for PbrPlugin { extract_ambient_light_resource, extract_ambient_light, extract_shadow_filtering_method, - late_sweep_material_instances, ), ) .add_systems( diff --git a/crates/bevy_pbr/src/lightmap/mod.rs b/crates/bevy_pbr/src/lightmap/mod.rs index a05d3ebd12db7..27188a1cdb957 100644 --- a/crates/bevy_pbr/src/lightmap/mod.rs +++ b/crates/bevy_pbr/src/lightmap/mod.rs @@ -111,7 +111,7 @@ pub struct Lightmap { /// /// There is one of these per visible lightmapped mesh instance. #[derive(Debug)] -pub(crate) struct RenderLightmap { +pub struct RenderLightmap { /// The rectangle within the lightmap texture that the UVs are relative to. /// /// The top left coordinate is the `min` part of the rect, and the bottom @@ -130,7 +130,7 @@ pub(crate) struct RenderLightmap { pub(crate) slot_index: LightmapSlotIndex, // Whether or not bicubic sampling should be used for this lightmap. - pub(crate) bicubic_sampling: bool, + pub bicubic_sampling: bool, } /// Stores data for all lightmaps in the render world. diff --git a/crates/bevy_pbr/src/main_pass.rs b/crates/bevy_pbr/src/main_pass.rs new file mode 100644 index 0000000000000..d0796ae37230b --- /dev/null +++ b/crates/bevy_pbr/src/main_pass.rs @@ -0,0 +1,519 @@ +use alloc::sync::Arc; + +use crate::*; +use bevy_app::Plugin; +use bevy_camera::{Camera3d, Projection}; +use bevy_core_pipeline::{ + core_3d::{ + AlphaMask3d, Opaque3d, Opaque3dBatchSetKey, Opaque3dBinKey, Transmissive3d, Transparent3d, + }, + oit::OrderIndependentTransparencySettings, + prepass::{ + DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass, + OpaqueNoLightmap3dBatchSetKey, OpaqueNoLightmap3dBinKey, + }, + tonemapping::{DebandDither, Tonemapping}, +}; +use bevy_ecs::{ + component::Component, + prelude::*, + query::{Has, QueryItem}, + system::{Query, ResMut, SystemChangeTick}, +}; +use bevy_light::{EnvironmentMapLight, IrradianceVolume, ShadowFilteringMethod}; +use bevy_mesh::MeshVertexBufferLayoutRef; +use bevy_render::{ + camera::TemporalJitter, + extract_component::ExtractComponent, + render_phase::{ + AddRenderCommand, BinnedPhaseItem, BinnedRenderPhase, BinnedRenderPhasePlugin, + BinnedRenderPhaseType, DrawFunctions, PhaseItemExtraIndex, + }, + render_resource::{ + RenderPipelineDescriptor, SpecializedMeshPipeline, SpecializedMeshPipelineError, + }, + view::{ExtractedView, Msaa}, + Render, RenderApp, RenderDebugFlags, RenderStartup, RenderSystems, +}; +use bevy_shader::ShaderDefVal; + +#[derive(Default)] +pub struct MainPassPlugin { + pub debug_flags: RenderDebugFlags, +} +impl Plugin for MainPassPlugin { + fn build(&self, app: &mut App) { + app.register_required_components::() + .add_plugins(MeshPassPlugin::::new(self.debug_flags)); + + let Some(render_app) = app.get_sub_app_mut(RenderApp) else { + return; + }; + + render_app + .add_systems(RenderStartup, init_material_pipeline) + .add_systems( + Render, + check_views_need_specialization::.in_set(RenderSystems::PrepareAssets), + ); + + add_prepass_and_shadow_pass(app, self.debug_flags); + } +} + +fn add_prepass_and_shadow_pass(app: &mut App, debug_flags: RenderDebugFlags) { + app.add_plugins((PrepassPipelinePlugin, PrepassPlugin::new(debug_flags))) + .add_plugins(BinnedRenderPhasePlugin::::new( + debug_flags, + )); + + let Some(render_app) = app.get_sub_app_mut(RenderApp) else { + return; + }; + + render_app + .init_resource::() + .init_resource::() + .init_resource::() + .init_resource::>() + .add_render_command::() + .add_systems( + Render, + ( + check_views_lights_need_specialization.in_set(RenderSystems::PrepareAssets), + // specialize_shadows also needs to run after prepare_assets::, + // which is fine since ManageViews is after PrepareAssets + specialize_shadows + .in_set(RenderSystems::ManageViews) + .after(prepare_lights), + queue_shadows.in_set(RenderSystems::QueueMeshes), + ), + ); +} + +#[derive(Clone, Copy, Default, Component, ExtractComponent)] +pub struct MainPass; + +impl MeshPass for MainPass { + type ViewKeySource = Self; + type Specializer = MaterialPipelineSpecializer; + type PhaseItems = (Opaque3d, AlphaMask3d, Transmissive3d, Transparent3d); +} + +pub fn check_views_need_specialization( + mut view_key_cache: ResMut>, + mut view_specialization_ticks: ResMut>, + mut views: Query< + ( + &ExtractedView, + &Msaa, + Option<&Tonemapping>, + Option<&DebandDither>, + Option<&ShadowFilteringMethod>, + Has, + ( + Has, + Has, + Has, + Has, + ), + Option<&Camera3d>, + Has, + Option<&Projection>, + Has, + ( + Has>, + Has>, + ), + Has, + ), + With, + >, + ticks: SystemChangeTick, +) { + for ( + 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); + + 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, + ); + } + if !view_key_cache + .get_mut(&view.retained_view_entity) + .is_some_and(|current_key| *current_key == view_key) + { + view_key_cache.insert(view.retained_view_entity, view_key); + view_specialization_ticks.insert(view.retained_view_entity, ticks.this_run()); + } + } +} + +pub fn init_material_pipeline(mut commands: Commands, mesh_pipeline: Res) { + commands.insert_resource(MaterialPipeline { + mesh_pipeline: mesh_pipeline.clone(), + }); +} +pub struct MaterialPipelineSpecializer { + pub(crate) pipeline: MaterialPipeline, + pub(crate) properties: Arc, +} + +impl MeshPassSpecializer for MaterialPipelineSpecializer { + type Pipeline = MaterialPipeline; + + fn create_key(context: &SpecializerKeyContext) -> Self::Key { + let mut mesh_pipeline_key_bits = context.material.properties.mesh_pipeline_key_bits; + mesh_pipeline_key_bits.insert(alpha_mode_pipeline_key( + context.material.properties.alpha_mode, + &Msaa::from_samples(context.view_key.msaa_samples()), + )); + let mut mesh_key = context.view_key + | MeshPipelineKey::from_bits_retain(context.mesh_pipeline_key.bits()) + | mesh_pipeline_key_bits; + + if let Some(lightmap) = context.lightmap { + mesh_key |= MeshPipelineKey::LIGHTMAPPED; + + if lightmap.bicubic_sampling { + mesh_key |= MeshPipelineKey::LIGHTMAP_BICUBIC_SAMPLING; + } + } + + if context.has_crossfade { + mesh_key |= MeshPipelineKey::VISIBILITY_RANGE_DITHER; + } + + if context + .view_key + .contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) + { + // If the previous frame have skins or morph targets, note that. + if context + .mesh_instance_flags + .contains(RenderMeshInstanceFlags::HAS_PREVIOUS_SKIN) + { + mesh_key |= MeshPipelineKey::HAS_PREVIOUS_SKIN; + } + if context + .mesh_instance_flags + .contains(RenderMeshInstanceFlags::HAS_PREVIOUS_MORPH) + { + mesh_key |= MeshPipelineKey::HAS_PREVIOUS_MORPH; + } + } + + let material_key = context.material.properties.material_key.clone(); + + Self::Key { + mesh_key, + material_key, + type_id: context.material_asset_id, + pass_id: context.pass_id, + } + } + + fn new(pipeline: &Self::Pipeline, material: &PreparedMaterial) -> Self { + MaterialPipelineSpecializer { + pipeline: pipeline.clone(), + properties: material.properties.clone(), + } + } +} + +impl SpecializedMeshPipeline for MaterialPipelineSpecializer { + type Key = ErasedMaterialPipelineKey; + + fn specialize( + &self, + key: Self::Key, + layout: &MeshVertexBufferLayoutRef, + ) -> Result { + let mut descriptor = self + .pipeline + .mesh_pipeline + .specialize(key.mesh_key, layout)?; + descriptor.vertex.shader_defs.push(ShaderDefVal::UInt( + "MATERIAL_BIND_GROUP".into(), + MATERIAL_BIND_GROUP_INDEX as u32, + )); + if let Some(ref mut fragment) = descriptor.fragment { + fragment.shader_defs.push(ShaderDefVal::UInt( + "MATERIAL_BIND_GROUP".into(), + MATERIAL_BIND_GROUP_INDEX as u32, + )); + }; + if let Some(vertex_shader) = self + .properties + .get_shader(MaterialVertexShader(key.pass_id)) + { + descriptor.vertex.shader = vertex_shader.clone(); + } + + if let Some(fragment_shader) = self + .properties + .get_shader(MaterialFragmentShader(key.pass_id)) + { + descriptor.fragment.as_mut().unwrap().shader = fragment_shader.clone(); + } + + descriptor + .layout + .insert(3, self.properties.material_layout.as_ref().unwrap().clone()); + + if let Some(specialize) = self.properties.specialize { + specialize(&self.pipeline, &mut descriptor, layout, key)?; + } + + // If bindless mode is on, add a `BINDLESS` define. + if self.properties.bindless { + descriptor.vertex.shader_defs.push("BINDLESS".into()); + if let Some(ref mut fragment) = descriptor.fragment { + fragment.shader_defs.push("BINDLESS".into()); + } + } + + Ok(descriptor) + } +} + +pub struct NoExtractCondition; + +impl ExtractCondition for NoExtractCondition { + type ViewQuery = (); + + #[inline] + fn should_extract(_item: QueryItem<'_, '_, Self::ViewQuery>) -> bool { + true + } +} + +impl PhaseItemExt for Opaque3d { + type PhaseFamily = BinnedPhaseFamily; + type ExtractCondition = NoExtractCondition; + type RenderCommand = DrawMaterial; + const PHASE_TYPES: RenderPhaseType = RenderPhaseType::Opaque; +} + +impl QueueBinnedPhaseItem for Opaque3d { + #[inline] + fn queue_item(context: &PhaseContext, render_phase: &mut BinnedRenderPhase) + where + BPI: BinnedPhaseItem, + { + if context.material.properties.render_method == OpaqueRendererMethod::Deferred { + // Even though we aren't going to insert the entity into + // a bin, we still want to update its cache entry. That + // way, we know we don't need to re-examine it in future + // frames. + render_phase.update_cache(context.main_entity, None, context.current_change_tick); + return; + } + let (vertex_slab, index_slab) = context + .mesh_allocator + .mesh_slabs(&context.mesh_instance.mesh_asset_id); + + render_phase.add( + Opaque3dBatchSetKey { + pipeline: context.pipeline_id, + draw_function: context.draw_function, + material_bind_group_index: Some(context.material.binding.group.0), + vertex_slab: vertex_slab.unwrap_or_default(), + index_slab, + lightmap_slab: context + .mesh_instance + .shared + .lightmap_slab_index + .map(|index| *index), + }, + Opaque3dBinKey { + asset_id: context.mesh_instance.mesh_asset_id.into(), + }, + (context.entity, context.main_entity), + context.mesh_instance.current_uniform_index, + BinnedRenderPhaseType::mesh( + context.mesh_instance.should_batch(), + &context.gpu_preprocessing_support, + ), + context.current_change_tick, + ); + } +} + +impl PhaseItemExt for AlphaMask3d { + type PhaseFamily = BinnedPhaseFamily; + type ExtractCondition = NoExtractCondition; + type RenderCommand = DrawMaterial; + const PHASE_TYPES: RenderPhaseType = RenderPhaseType::AlphaMask; +} + +impl QueueBinnedPhaseItem for AlphaMask3d { + #[inline] + fn queue_item(context: &PhaseContext, render_phase: &mut BinnedRenderPhase) + where + BPI: BinnedPhaseItem, + { + let (vertex_slab, index_slab) = context + .mesh_allocator + .mesh_slabs(&context.mesh_instance.mesh_asset_id); + + render_phase.add( + OpaqueNoLightmap3dBatchSetKey { + pipeline: context.pipeline_id, + draw_function: context.draw_function, + material_bind_group_index: Some(context.material.binding.group.0), + vertex_slab: vertex_slab.unwrap_or_default(), + index_slab, + }, + OpaqueNoLightmap3dBinKey { + asset_id: context.mesh_instance.mesh_asset_id.into(), + }, + (context.entity, context.main_entity), + context.mesh_instance.current_uniform_index, + BinnedRenderPhaseType::mesh( + context.mesh_instance.should_batch(), + &context.gpu_preprocessing_support, + ), + context.current_change_tick, + ); + } +} + +impl PhaseItemExt for Transmissive3d { + type PhaseFamily = SortedPhaseFamily; + type ExtractCondition = NoExtractCondition; + type RenderCommand = DrawMaterial; + const PHASE_TYPES: RenderPhaseType = RenderPhaseType::Transmissive; +} + +impl QueueSortedPhaseItem for Transmissive3d { + #[inline] + fn get_item(context: &PhaseContext) -> Option { + let (_, index_slab) = context + .mesh_allocator + .mesh_slabs(&context.mesh_instance.mesh_asset_id); + let distance = context.rangefinder.distance(&context.mesh_instance.center) + + context.material.properties.depth_bias; + + Some(Transmissive3d { + entity: (context.entity, context.main_entity), + draw_function: context.draw_function, + pipeline: context.pipeline_id, + distance, + batch_range: 0..1, + extra_index: PhaseItemExtraIndex::None, + indexed: index_slab.is_some(), + }) + } +} + +impl PhaseItemExt for Transparent3d { + type PhaseFamily = SortedPhaseFamily; + type ExtractCondition = NoExtractCondition; + type RenderCommand = DrawMaterial; + const PHASE_TYPES: RenderPhaseType = RenderPhaseType::Transparent; +} + +impl QueueSortedPhaseItem for Transparent3d { + #[inline] + fn get_item(context: &PhaseContext) -> Option { + let (_, index_slab) = context + .mesh_allocator + .mesh_slabs(&context.mesh_instance.mesh_asset_id); + let distance = context.rangefinder.distance(&context.mesh_instance.center) + + context.material.properties.depth_bias; + + Some(Transparent3d { + entity: (context.entity, context.main_entity), + draw_function: context.draw_function, + pipeline: context.pipeline_id, + distance, + batch_range: 0..1, + extra_index: PhaseItemExtraIndex::None, + indexed: index_slab.is_some(), + }) + } +} diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 42717b57c40fe..25517eff2552b 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -1,24 +1,20 @@ -use crate::material_bind_groups::{ - FallbackBindlessResources, MaterialBindGroupAllocator, MaterialBindingId, -}; -use crate::*; use alloc::sync::Arc; +use bevy_ecs::component::Mutable; +use bevy_ecs::query::{QueryData, QueryItem, ReadOnlyQueryData}; +use bevy_render::batching::gpu_preprocessing::GpuPreprocessingMode; +use bevy_render::sync_world::RenderEntity; +use bevy_render::view::NoIndirectDrawing; +use variadics_please::{all_tuples, all_tuples_enumerated}; + +use crate::*; use bevy_asset::prelude::AssetChanged; use bevy_asset::{Asset, AssetEventSystems, AssetId, AssetServer, UntypedAssetId}; use bevy_camera::visibility::ViewVisibility; -use bevy_camera::ScreenSpaceTransmissionQuality; -use bevy_core_pipeline::deferred::{AlphaMask3dDeferred, Opaque3dDeferred}; -use bevy_core_pipeline::prepass::{AlphaMask3dPrepass, Opaque3dPrepass}; -use bevy_core_pipeline::{ - core_3d::{ - AlphaMask3d, Opaque3d, Opaque3dBatchSetKey, Opaque3dBinKey, Transmissive3d, Transparent3d, - }, - prepass::{OpaqueNoLightmap3dBatchSetKey, OpaqueNoLightmap3dBinKey}, - tonemapping::Tonemapping, -}; +use bevy_camera::{Camera, Camera3d, ScreenSpaceTransmissionQuality}; +use bevy_core_pipeline::tonemapping::Tonemapping; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::change_detection::Tick; -use bevy_ecs::system::SystemChangeTick; +use bevy_ecs::system::{ReadOnlySystemParam, SystemChangeTick}; use bevy_ecs::{ prelude::*, system::{ @@ -27,17 +23,20 @@ use bevy_ecs::{ }, }; use bevy_mesh::{ - mark_3d_meshes_as_changed_if_their_assets_changed, Mesh3d, MeshVertexBufferLayoutRef, + mark_3d_meshes_as_changed_if_their_assets_changed, BaseMeshPipelineKey, Mesh3d, + MeshVertexBufferLayoutRef, }; use bevy_platform::collections::hash_map::Entry; use bevy_platform::collections::{HashMap, HashSet}; -use bevy_platform::hash::FixedHasher; +use bevy_platform::hash::{FixedHasher, NoOpHash}; use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::Reflect; +use bevy_render::batching::GetFullBatchData; use bevy_render::camera::extract_cameras; use bevy_render::erased_render_asset::{ ErasedRenderAsset, ErasedRenderAssetPlugin, ErasedRenderAssets, PrepareAssetError, }; +use bevy_render::extract_component::{ExtractComponent, ExtractComponentPlugin}; use bevy_render::render_asset::{prepare_assets, RenderAssets}; use bevy_render::renderer::RenderQueue; use bevy_render::RenderStartup; @@ -55,7 +54,7 @@ use bevy_render::{ }; use bevy_render::{mesh::allocator::MeshAllocator, sync_world::MainEntityHashMap}; use bevy_render::{texture::FallbackImage, view::RenderVisibleEntities}; -use bevy_shader::{Shader, ShaderDefVal}; +use bevy_shader::Shader; use bevy_utils::Parallel; use core::any::{Any, TypeId}; use core::hash::{BuildHasher, Hasher}; @@ -65,6 +64,8 @@ use tracing::error; pub const MATERIAL_BIND_GROUP_INDEX: usize = 3; +pub const MESH_PASS_MAX_PHASES: usize = 4; + /// Materials are used alongside [`MaterialPlugin`], [`Mesh3d`], and [`MeshMaterial3d`] /// to spawn entities that are rendered with a specific [`Material`] type. They serve as an easy to use high level /// way to render [`Mesh3d`] entities with custom shader logic. @@ -136,6 +137,20 @@ pub const MATERIAL_BIND_GROUP_INDEX: usize = 3; /// @group(#{MATERIAL_BIND_GROUP}) @binding(2) var color_sampler: sampler; /// ``` pub trait Material: Asset + AsBindGroup + Clone + Sized { + /// Returns this material's shaders for supported passes. + /// + /// When the traditional shader method is used, the corresponding pass's shader in the [`PassShaders`] will be ignored. + /// Currently, only [`MainPass`], [`DeferredPass`] and [`Prepass`] are supported out of the box. + fn shaders() -> PassShaders { + let mut pass_shaders = PassShaders::default(); + pass_shaders.extend([ + (Prepass::id(), ShaderSet::default()), + (DeferredPass::id(), ShaderSet::default()), + (MainPass::id(), ShaderSet::default()), + ]); + pass_shaders + } + /// Returns this material's vertex shader. If [`ShaderRef::Default`] is returned, the default mesh vertex shader /// will be used. fn vertex_shader() -> ShaderRef { @@ -173,8 +188,8 @@ pub trait Material: Asset + AsBindGroup + Clone + Sized { #[inline] /// Returns whether the material would like to read from [`ViewTransmissionTexture`](bevy_core_pipeline::core_3d::ViewTransmissionTexture). /// - /// 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. + /// This allows taking color output from the [`bevy_core_pipeline::core_3d::Opaque3d`] pass as an input, (for screen-space transmission) but requires + /// rendering to take place in a separate [`bevy_core_pipeline::core_3d::Transmissive3d`] pass. fn reads_view_transmission_texture(&self) -> bool { false } @@ -272,68 +287,335 @@ pub trait Material: Asset + AsBindGroup + Clone + Sized { } } +/// A set of shaders for a rasterization pass, +/// containing a vertex shader and fragment shader. #[derive(Default)] -pub struct MaterialsPlugin { - /// Debugging flags that can optionally be set when constructing the renderer. +pub struct ShaderSet { + pub vertex: ShaderRef, + pub fragment: ShaderRef, +} + +/// A map for storing the shaders for each pass used by a [`Material`]. +pub type PassShaders = HashMap; + +/// A unique identifier for a [`MeshPass`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deref)] +pub struct PassId(TypeId); + +impl PassId { + /// Creates a new [`PassId`] from a [`MeshPass`]. + pub fn of() -> Self { + PassId(TypeId::of::()) + } +} + +/// A trait for defining a render pass that can be used by [`Material`]. +/// +/// Add the pass to `Camera3d`s to mark views for [`MeshPassPlugin`] rendering. +pub trait MeshPass +where + Self: ExtractComponent + Default, + ::Key: Sync + Send, +{ + /// The pass responsible for checking view specialization. + /// + /// You can reuse existing pass implementations. If you want to create your own, this would be `Self`. Check out [`MainPass`] + /// for more details. + type ViewKeySource: MeshPass; + + /// The specializer for creating [`RenderPipelineDescriptor`]. + type Specializer: MeshPassSpecializer; + + /// The [`PhaseItem`]s processed by this pass. + /// + /// Each `PhaseItem` must implement [`PhaseItemExt`]. Currently, the maximum number of `PhaseItem`s in a pass is 4. + /// + /// **IMPORTANT:** A `PhaseItem` should never be reused between passes directly, use a newtype instead. + /// + /// ## Example + /// ```ignore + /// type PhaseItems = (MyOpaque3d, MyAlphaMask3d, MyTransmissive3d, MyTransparent3d); + /// ``` + type PhaseItems: PhaseItems; + + /// The identifier for this pass. + fn id() -> PassId { + PassId::of::() + } +} + +pub trait PhaseItems { + type QueryData: QueryData; + const PHASES_TYPES: &'static [RenderPhaseType]; + const SUPPORTED_PHASE_TYPES: RenderPhaseType; + + fn add_plugins(app: &mut App, debug_flags: RenderDebugFlags); +} + +impl PhaseItems for PIE { + type QueryData = Mut<'static, PIEPhase>; + const PHASES_TYPES: &'static [RenderPhaseType] = &[PIE::PHASE_TYPES]; + const SUPPORTED_PHASE_TYPES: RenderPhaseType = PIE::PHASE_TYPES; + + #[inline] + fn add_plugins(app: &mut App, debug_flags: RenderDebugFlags) { + app.add_plugins(MeshPassPhasePlugin::::new(0, debug_flags)); + } +} + +macro_rules! impl_phase_items_tuple { + ( + $(#[$meta:meta])* + $($PIE:ident),* + ) => { + $(#[$meta])* + impl<$($PIE: PhaseItemExt),*> PhaseItems for ($($PIE,)*) { + type QueryData = ($(Mut<'static, PIEPhase<$PIE>>,)*); + const PHASES_TYPES: &'static [RenderPhaseType] = &[ + $($PIE::PHASE_TYPES,)* + ]; + const SUPPORTED_PHASE_TYPES: RenderPhaseType = RenderPhaseType::from_bits_truncate( + 0u8 $(| $PIE::PHASE_TYPES.bits())* + ); + + #[inline] + fn add_plugins( + app: &mut App, + debug_flags: RenderDebugFlags, + ) { + let mut _idx = 0; + $( + app.add_plugins( + MeshPassPhasePlugin::::new(_idx, debug_flags) + ); + _idx += 1; + )* + } + } + }; +} + +all_tuples!( + #[doc(fake_variadic)] + impl_phase_items_tuple, + 1, + 4, + PIE +); + +pub trait PhasesQueryItem { + fn add( + &mut self, + context: &mut PhaseContext, + pass_id: PassId, + phases_types: &[RenderPhaseType], + ); + + fn validate_cached_entity( + &mut self, + visible_entity: MainEntity, + current_change_tick: Tick, + ) -> bool; +} + +impl<'w, T: RenderPhase> PhasesQueryItem for Mut<'w, T> { + #[inline] + fn add( + &mut self, + context: &mut PhaseContext, + pass_id: PassId, + phases_types: &[RenderPhaseType], + ) { + let mut tuple = (self.reborrow(),); + <(Mut<'_, T>,) as PhasesQueryItem>::add(&mut tuple, context, pass_id, phases_types); + } + + #[inline] + fn validate_cached_entity( + &mut self, + visible_entity: MainEntity, + current_change_tick: Tick, + ) -> bool { + self.as_mut() + .validate_cached_entity(visible_entity, current_change_tick) + } +} + +macro_rules! impl_phases_query_item_tuple { + ( + $(#[$meta:meta])* + $(($idx:tt, $phase:ident)),* + ) => { + $(#[$meta])* + impl<'w, $($phase: RenderPhase),*> PhasesQueryItem for ($(Mut<'w, $phase>,)*) { + #[inline] + fn add(&mut self, context: &mut PhaseContext, pass_id: PassId, phases_types: &[RenderPhaseType]) { + let render_phase_type = context.material.properties.render_phase_type; + $( + if phases_types[$idx].contains(render_phase_type) + && let Some(draw_function) = context.material + .properties + .get_draw_function(MeshPassDrawFunction { pass_id, phase_idx: $idx }) + { + context.draw_function = draw_function; + self.$idx.as_mut().add(context); + } + )* + } + + #[inline] + fn validate_cached_entity( + &mut self, + visible_entity: MainEntity, + current_change_tick: Tick, + ) -> bool { + $(self.$idx.validate_cached_entity(visible_entity, current_change_tick) ||)* false + } + } + }; +} + +all_tuples_enumerated!( + #[doc(fake_variadic)] + impl_phases_query_item_tuple, + 1, + 4, + P +); + +// Fake singleton for [`MeshPassPlugin`] +#[derive(Resource, Default)] +struct MeshPassPluginLoaded; + +/// A plugin for adding a [`MeshPass`] that can be used by [`Material`]. +/// +/// Currently handles the specialization and queuing stages. +#[derive(Default)] +pub struct MeshPassPlugin { pub debug_flags: RenderDebugFlags, + _marker: PhantomData, } -impl Plugin for MaterialsPlugin { +impl MeshPassPlugin { + pub fn new(debug_flags: RenderDebugFlags) -> Self { + Self { + debug_flags, + _marker: PhantomData, + } + } +} + +impl Plugin for MeshPassPlugin +where + for<'w, 's> PhasesQI<'w, 's, MP>: PhasesQueryItem, +{ fn build(&self, app: &mut App) { - app.add_plugins((PrepassPipelinePlugin, PrepassPlugin::new(self.debug_flags))); - if let Some(render_app) = app.get_sub_app_mut(RenderApp) { - render_app - .init_resource::() - .init_resource::() - .init_resource::>() - .init_resource::() - .init_resource::() - .init_resource::() - .init_resource::>() - .init_resource::() - .init_resource::() - .add_render_command::() - .add_render_command::() - .add_render_command::() - .add_render_command::() - .add_render_command::() - .add_systems(RenderStartup, init_material_pipeline) - .add_systems( - Render, - ( - specialize_material_meshes - .in_set(RenderSystems::PrepareMeshes) - .after(prepare_assets::) - .after(collect_meshes_for_gpu_building) - .after(set_mesh_motion_vector_flags), - queue_material_meshes.in_set(RenderSystems::QueueMeshes), - ), - ) - .add_systems( - Render, - ( - prepare_material_bind_groups, - write_material_bind_group_buffers, - ) - .chain() - .in_set(RenderSystems::PrepareBindGroups), + MP::PhaseItems::add_plugins::(app, self.debug_flags); + app.add_plugins(ExtractComponentPlugin::::default()); + + let Some(render_app) = app.get_sub_app_mut(RenderApp) else { + return; + }; + + // For all instances of MeshPassPlugin + render_app + .init_resource::() + .init_resource::>() + .init_resource::>() + .init_resource::>() // Double check + .init_resource::>() // Double check + .init_resource::() + .init_resource::() + .init_resource::() + .add_systems( + ExtractSchedule, + late_sweep_entities_needing_specialization:: + .after(MaterialEarlySweepEntitiesNeedingSpecializationSystems) + .before(late_sweep_material_instances), + ) + .add_systems( + Render, + ( + specialize_material_meshes:: + .in_set(RenderSystems::PrepareMeshes) + .after(prepare_assets::) + .after(collect_meshes_for_gpu_building) + .after(set_mesh_motion_vector_flags), + queue_material_meshes::.in_set(RenderSystems::QueueMeshes), + ), + ); + + // Fake singleton start + if render_app + .world() + .contains_resource::() + { + return; + } + render_app + .init_resource::() + .add_systems( + Render, + ( + prepare_material_bind_groups, + write_material_bind_group_buffers, ) - .add_systems( - Render, - ( - check_views_lights_need_specialization.in_set(RenderSystems::PrepareAssets), - // specialize_shadows also needs to run after prepare_assets::, - // which is fine since ManageViews is after PrepareAssets - specialize_shadows - .in_set(RenderSystems::ManageViews) - .after(prepare_lights), - queue_shadows.in_set(RenderSystems::QueueMeshes), - ), - ); + .chain() + .in_set(RenderSystems::PrepareBindGroups), + ); + } +} + +struct MeshPassPhasePlugin { + phase_index: usize, + debug_flags: RenderDebugFlags, + _marker: PhantomData<(MP, PIE)>, +} + +impl MeshPassPhasePlugin { + pub fn new(phase_index: usize, debug_flags: RenderDebugFlags) -> Self { + Self { + phase_index, + debug_flags, + _marker: PhantomData, } } } +impl Plugin for MeshPassPhasePlugin +where + MP: MeshPass, + PIE: PhaseItemExt, +{ + fn build(&self, app: &mut App) { + if app.is_plugin_added::>() { + panic!( + "Duplicate PhaseItem {} found in {}. Consider defining a newtype.", + core::any::type_name::(), + core::any::type_name::(), + ); + } + app.add_plugins(PIEPlugin::::new(self.debug_flags)); + + let Some(render_app) = app.get_sub_app_mut(RenderApp) else { + return; + }; + + render_app + .init_resource::() + .init_resource::>() + .add_render_command::() + .add_systems( + RenderStartup, + insert_mesh_pass_draw_functions::.with_input((MP::id(), self.phase_index)), + ) + .add_systems(ExtractSchedule, extruct_mesh_pass_phases::); + } +} + +#[derive(Resource, Default)] +struct MaterialPluginLoaded; + /// Adds the necessary ECS resources and render logic to enable rendering entities using the given [`Material`] /// asset type. pub struct MaterialPlugin { @@ -359,6 +641,7 @@ where app.init_asset::() .register_type::>() .init_resource::>() + .init_resource::() .add_plugins((ErasedRenderAssetPlugin::>::default(),)) .add_systems( PostUpdate, @@ -377,29 +660,41 @@ where ); } - if let Some(render_app) = app.get_sub_app_mut(RenderApp) { - render_app - .add_systems(RenderStartup, add_material_bind_group_allocator::) - .add_systems( - ExtractSchedule, - ( - extract_mesh_materials::.in_set(MaterialExtractionSystems), - early_sweep_material_instances:: - .after(MaterialExtractionSystems) - .before(late_sweep_material_instances), - // See the comments in - // `sweep_entities_needing_specialization` for an - // explanation of why the systems are ordered this way. - extract_entities_needs_specialization:: - .in_set(MaterialExtractEntitiesNeedingSpecializationSystems), - sweep_entities_needing_specialization:: - .after(MaterialExtractEntitiesNeedingSpecializationSystems) - .after(MaterialExtractionSystems) - .after(extract_cameras) - .before(late_sweep_material_instances), - ), - ); + let Some(render_app) = app.get_sub_app_mut(RenderApp) else { + return; + }; + render_app + .add_systems(RenderStartup, add_material_bind_group_allocator::) + .add_systems( + ExtractSchedule, + ( + extract_mesh_materials::.in_set(MaterialExtractionSystems), + early_sweep_material_instances:: + .after(MaterialExtractionSystems) + .before(late_sweep_material_instances), + // See the comments in + // `early_sweep_entities_needing_specialization` for an + // explanation of why the systems are ordered this way. + extract_entities_needs_specialization:: + .in_set(MaterialExtractEntitiesNeedingSpecializationSystems), + early_sweep_entities_needing_specialization:: + .in_set(MaterialEarlySweepEntitiesNeedingSpecializationSystems) + .after(MaterialExtractEntitiesNeedingSpecializationSystems) + .after(MaterialExtractionSystems) + .after(extract_cameras) + .before(late_sweep_material_instances), + ), + ); + + if render_app + .world() + .contains_resource::() + { + return; } + render_app + .init_resource::() + .add_systems(ExtractSchedule, late_sweep_material_instances); } } @@ -433,6 +728,7 @@ pub(crate) static DUMMY_MESH_MATERIAL: AssetId = pub struct MaterialPipelineKey { pub mesh_key: MeshPipelineKey, pub bind_group_data: M::Data, + pub pass_id: PassId, } #[derive(Clone, Debug, PartialEq, Eq, Hash)] @@ -440,6 +736,7 @@ pub struct ErasedMaterialPipelineKey { pub mesh_key: MeshPipelineKey, pub material_key: ErasedMaterialKey, pub type_id: TypeId, + pub pass_id: PassId, } /// Render pipeline data for a given [`Material`]. @@ -448,67 +745,81 @@ pub struct MaterialPipeline { pub mesh_pipeline: MeshPipeline, } -pub struct MaterialPipelineSpecializer { - pub(crate) pipeline: MaterialPipeline, - pub(crate) properties: Arc, +/// Inserts `PhaseItem`'s `DrawFunction`s into [`MeshPassDrawFunctions`] by their index in [`PhaseItems`]. +/// +/// This should be called per `PhaseItem`, and the corresponding `RenderCommand` should be registered before calling this. +pub fn insert_mesh_pass_draw_functions( + InMut((pass_id, phase_index)): InMut<(PassId, usize)>, + mut mesh_pass_draw_functions: ResMut, + draw_functions: Res>, +) { + let draw_function_id = draw_functions + .read() + .get_id::() + .expect("DrawFunctionId not found for the pass's RenderCommand. Call `add_draw_function` to register it first."); + + if let Some(draw) = mesh_pass_draw_functions + .entry(*pass_id) + .or_default() + .get_mut(*phase_index) + { + *draw = Some(draw_function_id); + } } -impl SpecializedMeshPipeline for MaterialPipelineSpecializer { - type Key = ErasedMaterialPipelineKey; - - fn specialize( - &self, - key: Self::Key, - layout: &MeshVertexBufferLayoutRef, - ) -> Result { - let mut descriptor = self - .pipeline - .mesh_pipeline - .specialize(key.mesh_key, layout)?; - descriptor.vertex.shader_defs.push(ShaderDefVal::UInt( - "MATERIAL_BIND_GROUP".into(), - MATERIAL_BIND_GROUP_INDEX as u32, - )); - if let Some(ref mut fragment) = descriptor.fragment { - fragment.shader_defs.push(ShaderDefVal::UInt( - "MATERIAL_BIND_GROUP".into(), - MATERIAL_BIND_GROUP_INDEX as u32, - )); - }; - if let Some(vertex_shader) = self.properties.get_shader(MaterialVertexShader) { - descriptor.vertex.shader = vertex_shader.clone(); - } - - if let Some(fragment_shader) = self.properties.get_shader(MaterialFragmentShader) { - descriptor.fragment.as_mut().unwrap().shader = fragment_shader.clone(); - } +/// A trait that allows to conditionally extract views for a [`PhaseItemExt`]. +pub trait ExtractCondition { + /// Query ECS data on the `Camera3d` entities. + type ViewQuery: ReadOnlyQueryData; - descriptor - .layout - .insert(3, self.properties.material_layout.as_ref().unwrap().clone()); + /// Determines if the view should be extracted for the `PhaseItemExt`. + fn should_extract(item: QueryItem<'_, '_, Self::ViewQuery>) -> bool; +} - if let Some(specialize) = self.properties.specialize { - specialize(&self.pipeline, &mut descriptor, layout, key)?; +fn extruct_mesh_pass_phases( + mut commands: Commands, + cameras_3d: Extract< + Query< + ( + RenderEntity, + &Camera, + Has, + ::ViewQuery, + ), + (With, With), + >, + >, + mut phase_query: Query<&mut PIEPhase, With>, + gpu_preprocessing_support: Res, +) where + MP: MeshPass, + PIE: PhaseItemExt, +{ + for (entity, camera, no_indirect_drawing, query_item) in &cameras_3d { + if !camera.is_active || !PIE::ExtractCondition::should_extract(query_item) { + commands.entity(entity).remove::>(); + continue; } - // If bindless mode is on, add a `BINDLESS` define. - if self.properties.bindless { - descriptor.vertex.shader_defs.push("BINDLESS".into()); - if let Some(ref mut fragment) = descriptor.fragment { - fragment.shader_defs.push("BINDLESS".into()); - } - } + if let Ok(mut phase) = phase_query.get_mut(entity) { + phase.prepare_for_new_frame(); + } else { + // If GPU culling is in use, use it (and indirect mode); otherwise, just + // preprocess the meshes. + let gpu_preprocessing_mode = gpu_preprocessing_support.min(if !no_indirect_drawing { + GpuPreprocessingMode::Culling + } else { + GpuPreprocessingMode::PreprocessingOnly + }); - Ok(descriptor) + commands + .entity(entity) + .insert(PIEPhase::::new(gpu_preprocessing_mode)); + }; } } -pub fn init_material_pipeline(mut commands: Commands, mesh_pipeline: Res) { - commands.insert_resource(MaterialPipeline { - mesh_pipeline: mesh_pipeline.clone(), - }); -} - +/// A [`RenderCommand`] for [`MainPass`]. pub type DrawMaterial = ( SetItemPipeline, SetMeshViewBindGroup<0>, @@ -616,6 +927,16 @@ pub struct MaterialExtractionSystems; #[derive(SystemSet, Clone, PartialEq, Eq, Debug, Hash)] pub struct MaterialExtractEntitiesNeedingSpecializationSystems; +// NOTE: This is for configuring the order between +// `early_sweep_entities_needs_specialization` and +// `late_sweep_entities_needs_specialization`, which +// have different generic types. +// +/// A [`SystemSet`] that contains all `early_sweep_entities_needs_specialization` +/// systems. +#[derive(SystemSet, Clone, PartialEq, Eq, Debug, Hash)] +pub struct MaterialEarlySweepEntitiesNeedingSpecializationSystems; + pub const fn alpha_mode_pipeline_key(alpha_mode: AlphaMode, msaa: &Msaa) -> MeshPipelineKey { match alpha_mode { // Premultiplied and Add share the same pipeline key @@ -666,6 +987,20 @@ pub const fn screen_space_specular_transmission_pipeline_key( } } +pub const fn alpha_mode_render_phase_type( + alpha_mode: AlphaMode, + reads_view_transmission_texture: bool, +) -> RenderPhaseType { + match alpha_mode { + 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, + } +} + /// A system that ensures that /// [`crate::render::mesh::extract_meshes_for_gpu_building`] re-extracts meshes /// whose materials changed. @@ -814,21 +1149,14 @@ pub fn extract_entities_needs_specialization( /// removed, and an entity changed material types, we might end up adding a new /// set of [`EntitySpecializationTickPair`] for the new material and then /// deleting it upon detecting the removed component for the old material. -/// Deferring [`sweep_entities_needing_specialization`] to the end allows us to +/// Deferring [`early_sweep_entities_needing_specialization`] to the end allows us to /// detect the case in which another material type updated the entity /// specialization ticks this frame and avoid deleting it if so. -pub fn sweep_entities_needing_specialization( +pub fn early_sweep_entities_needing_specialization( mut entity_specialization_ticks: ResMut, mut removed_mesh_material_components: Extract>>, - mut specialized_material_pipeline_cache: ResMut, - mut specialized_prepass_material_pipeline_cache: Option< - ResMut, - >, - mut specialized_shadow_material_pipeline_cache: Option< - ResMut, - >, + mut entities_needing_sweep: ResMut, render_material_instances: Res, - views: Query<&ExtractedView>, ) where M: Material, { @@ -857,26 +1185,48 @@ pub fn sweep_entities_needing_specialization( } entity_specialization_ticks.remove(&MainEntity::from(entity)); - for view in views { - if let Some(cache) = - specialized_material_pipeline_cache.get_mut(&view.retained_view_entity) - { - cache.remove(&MainEntity::from(entity)); - } - if let Some(cache) = specialized_prepass_material_pipeline_cache - .as_mut() - .and_then(|c| c.get_mut(&view.retained_view_entity)) - { - cache.remove(&MainEntity::from(entity)); - } - if let Some(cache) = specialized_shadow_material_pipeline_cache - .as_mut() - .and_then(|c| c.get_mut(&view.retained_view_entity)) - { - cache.remove(&MainEntity::from(entity)); - } + + // Because `SpecializedMaterialPipelineCache` is per-pass now, + // defer cleanup to the per-pass system `late_sweep_entities_needing_specialization`. + for pass_id in M::shaders().keys() { + entities_needing_sweep + .entry(*pass_id) + .or_default() + .push(entity); + } + } +} + +/// Entities needing to be removed from [`SpecializedMaterialPipelineCache`]. +#[derive(Resource, Default, Deref, DerefMut)] +pub struct EntitiesNeedingSweep { + pub entities: HashMap, NoOpHash>, +} + +/// Removes entities from [`SpecializedMaterialPipelineCache`] for the pass based on +/// [`EntitiesNeedingSweep`]. +/// +/// This runs after all invocations of `early_sweep_entities_needing_specialization`. +/// Because `early_sweep_entities_needing_specialization` is a per-material system and +/// the `SpecializedMaterialPipelineCache` is per-pass, we have to sweep this way. +pub fn late_sweep_entities_needing_specialization( + views: Query<&ExtractedView, With>, + mut entities_needing_sweep: ResMut, + mut specialized_material_pipeline_cache: ResMut>, +) { + let Some(entities) = entities_needing_sweep.get_mut(&MP::id()) else { + return; + }; + for view in views { + let Some(cache) = specialized_material_pipeline_cache.get_mut(&view.retained_view_entity) + else { + continue; + }; + for &entity in entities.iter() { + cache.remove(&MainEntity::from(entity)); } } + entities.clear(); } #[derive(Resource, Deref, DerefMut, Clone, Debug)] @@ -928,7 +1278,7 @@ pub struct EntitySpecializationTicks { /// 2. [`extract_entities_needs_specialization`] runs for material B and marks /// the mesh as up to date by recording the current tick. /// -/// 3. [`sweep_entities_needing_specialization`] runs for material A and checks +/// 3. [`early_sweep_entities_needing_specialization`] runs for material A and checks /// to ensure it's safe to remove the [`EntitySpecializationTickPair`] for the mesh /// from the [`EntitySpecializationTicks`]. To do this, it needs to know /// whether [`extract_entities_needs_specialization`] for some *different* @@ -937,9 +1287,9 @@ pub struct EntitySpecializationTicks { /// It can't reliably use the [`Self::system_tick`] to determine this because /// the [`SystemChangeTick`] can be updated multiple times in the same frame. /// Instead, it needs a type of tick that's updated only once per frame, after -/// all materials' versions of [`sweep_entities_needing_specialization`] have +/// all materials' versions of [`early_sweep_entities_needing_specialization`] have /// run. The [`RenderMaterialInstances`] tick satisfies this criterion, and so -/// that's what [`sweep_entities_needing_specialization`] uses. +/// that's what [`early_sweep_entities_needing_specialization`] uses. #[derive(Clone, Copy, Debug)] pub struct EntitySpecializationTickPair { /// The standard Bevy system tick. @@ -950,11 +1300,21 @@ pub struct EntitySpecializationTickPair { } /// Stores the [`SpecializedMaterialViewPipelineCache`] for each view. -#[derive(Resource, Deref, DerefMut, Default)] -pub struct SpecializedMaterialPipelineCache { +#[derive(Resource, Deref, DerefMut)] +pub struct SpecializedMaterialPipelineCache { // view entity -> view pipeline cache #[deref] map: HashMap, + _marker: PhantomData, +} + +impl Default for SpecializedMaterialPipelineCache { + fn default() -> Self { + Self { + map: Default::default(), + _marker: PhantomData, + } + } } /// Stores the cached render pipeline ID for each entity in a single view, as @@ -993,31 +1353,58 @@ pub fn check_entities_needing_specialization( par_local.drain_into(&mut entities_needing_specialization); } -pub fn specialize_material_meshes( +pub struct SpecializerKeyContext<'a> { + pub view_key: MeshPipelineKey, + pub mesh_pipeline_key: BaseMeshPipelineKey, + pub mesh_instance_flags: RenderMeshInstanceFlags, + pub material: &'a PreparedMaterial, + pub material_asset_id: TypeId, + pub lightmap: Option<&'a RenderLightmap>, + pub has_crossfade: bool, + pub pass_id: PassId, +} + +/// A trait for creating specializer used by [`specialize_material_meshes`]. +pub trait MeshPassSpecializer: SpecializedMeshPipeline { + /// The render pipeline data type used by the corresponding material. + type Pipeline: Resource; + + /// Creates the key for [`SpecializedMeshPipeline::Key`]. + fn create_key(context: &SpecializerKeyContext) -> Self::Key; + + /// Creates the [`SpecializedMeshPipeline`] for [`SpecializedMeshPipeline::specialize`]. + fn new(pipeline: &Self::Pipeline, material: &PreparedMaterial) -> Self; +} + +#[derive(Resource, Deref, DerefMut, Default, Debug, Clone)] +pub struct ViewKeyCache( + #[deref] HashMap, + PhantomData, +); + +#[derive(Resource, Deref, DerefMut, Default, Debug, Clone)] +pub struct ViewSpecializationTicks(#[deref] HashMap, PhantomData); + +#[derive(Resource, Deref, DerefMut, FromWorld)] +pub struct MeshPassSpecializedMeshPipelines( + #[deref] SpecializedMeshPipelines, + PhantomData, +); + +pub fn specialize_material_meshes( render_meshes: Res>, render_materials: Res>, render_mesh_instances: Res, render_material_instances: Res, render_lightmaps: Res, render_visibility_ranges: Res, - ( - opaque_render_phases, - alpha_mask_render_phases, - transmissive_render_phases, - transparent_render_phases, - ): ( - Res>, - Res>, - Res>, - Res>, - ), - views: Query<(&ExtractedView, &RenderVisibleEntities)>, - view_key_cache: Res, + views: Query<(&ExtractedView, &RenderVisibleEntities), With>, + view_key_cache: Res>, entity_specialization_ticks: Res, - view_specialization_ticks: Res, - mut specialized_material_pipeline_cache: ResMut, - mut pipelines: ResMut>, - pipeline: Res, + view_specialization_ticks: Res>, + mut specialized_material_pipeline_cache: ResMut>, + mut pipelines: ResMut>, + pipeline: Res<::Pipeline>, pipeline_cache: Res, ticks: SystemChangeTick, ) { @@ -1028,14 +1415,6 @@ pub fn specialize_material_meshes( for (view, visible_entities) in &views { all_views.insert(view.retained_view_entity); - 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 Some(view_key) = view_key_cache.get(&view.retained_view_entity) else { continue; }; @@ -1077,56 +1456,44 @@ pub fn specialize_material_meshes( continue; }; - 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::from_samples(view_key.msaa_samples()), - )); - let mut mesh_key = *view_key - | MeshPipelineKey::from_bits_retain(mesh.key_bits.bits()) - | mesh_pipeline_key_bits; - - if let Some(lightmap) = render_lightmaps.render_lightmaps.get(visible_entity) { - mesh_key |= MeshPipelineKey::LIGHTMAPPED; - - if lightmap.bicubic_sampling { - mesh_key |= MeshPipelineKey::LIGHTMAP_BICUBIC_SAMPLING; - } + if !material.properties.enabled_passes.contains(&MP::id()) { + // Prevent cases where the material was valid previously but switched to an unsupported pass during this frame. + view_specialized_material_pipeline_cache.remove(visible_entity); + continue; } - if render_visibility_ranges.entity_has_crossfading_visibility_ranges(*visible_entity) { - mesh_key |= MeshPipelineKey::VISIBILITY_RANGE_DITHER; + if !MP::PhaseItems::SUPPORTED_PHASE_TYPES + .contains(material.properties.render_phase_type) + { + // Prevent cases where the material was valid previously but switched to an unsupported phase during this frame. + view_specialized_material_pipeline_cache.remove(visible_entity); + continue; } - if view_key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) { - // If the previous frame have skins or morph targets, note that. - if mesh_instance - .flags - .contains(RenderMeshInstanceFlags::HAS_PREVIOUS_SKIN) - { - mesh_key |= MeshPipelineKey::HAS_PREVIOUS_SKIN; - } - if mesh_instance - .flags - .contains(RenderMeshInstanceFlags::HAS_PREVIOUS_MORPH) - { - mesh_key |= MeshPipelineKey::HAS_PREVIOUS_MORPH; - } - } + let lightmap = render_lightmaps.render_lightmaps.get(visible_entity); - let erased_key = ErasedMaterialPipelineKey { - type_id: material_instance.asset_id.type_id(), - mesh_key, - material_key: material.properties.material_key.clone(), - }; - let material_pipeline_specializer = MaterialPipelineSpecializer { - pipeline: pipeline.clone(), - properties: material.properties.clone(), + let has_crossfade = + render_visibility_ranges.entity_has_crossfading_visibility_ranges(*visible_entity); + + let key_context = SpecializerKeyContext { + view_key: *view_key, + mesh_pipeline_key: mesh.key_bits, + mesh_instance_flags: mesh_instance.flags, + material, + lightmap, + has_crossfade, + material_asset_id: material_instance.asset_id.type_id(), + pass_id: MP::id(), }; + + let key = MP::Specializer::create_key(&key_context); + + let material_pipeline_specializer = MP::Specializer::new(&pipeline, material); + let pipeline_id = pipelines.specialize( &pipeline_cache, &material_pipeline_specializer, - erased_key, + key, &mesh.layout, ); let pipeline_id = match pipeline_id { @@ -1147,37 +1514,199 @@ pub fn specialize_material_meshes( .retain(|retained_view_entity, _| all_views.contains(retained_view_entity)); } +pub type Phases = <::PhaseItems as PhaseItems>::QueryData; +pub type PhasesReadOnly = as QueryData>::ReadOnly; +pub type PhasesQI<'w, 's, MP> = as QueryData>::Item<'w, 's>; +pub type PhasesROQI<'w, 's, MP> = as QueryData>::Item<'w, 's>; + +pub type PIEPhase = <::PhaseFamily as PhaseFamily>::Phase; +pub type PIEPlugin = <::PhaseFamily as PhaseFamily>::Plugin; + +pub trait PhaseFamily { + type Phase: RenderPhase + Component; + type Plugin: RenderPhasePlugin + Plugin; +} + +pub struct BinnedPhaseFamily; + +impl PhaseFamily for BinnedPhaseFamily +where + PI: BinnedPhaseItem + QueueBinnedPhaseItem, +{ + type Phase = BinnedRenderPhase; + type Plugin = BinnedRenderPhasePlugin; +} + +pub struct SortedPhaseFamily; + +impl PhaseFamily for SortedPhaseFamily +where + PI: SortedPhaseItem + QueueSortedPhaseItem, + SortedRenderPhasePlugin: Plugin, +{ + type Phase = SortedRenderPhase; + type Plugin = SortedRenderPhasePlugin; +} + +pub trait RenderPhasePlugin { + fn new(debug_flags: RenderDebugFlags) -> Self; +} + +impl RenderPhasePlugin for BinnedRenderPhasePlugin +where + BPI: BinnedPhaseItem, + GFBD: GetFullBatchData, +{ + fn new(debug_flags: RenderDebugFlags) -> Self { + BinnedRenderPhasePlugin::new(debug_flags) + } +} + +impl RenderPhasePlugin for SortedRenderPhasePlugin +where + SPI: SortedPhaseItem, + GFBD: GetFullBatchData, +{ + fn new(debug_flags: RenderDebugFlags) -> Self { + SortedRenderPhasePlugin::new(debug_flags) + } +} + +pub struct PhaseContext<'a> { + pub mesh_instance: &'a RenderMeshQueueData<'a>, + pub material: &'a PreparedMaterial, + pub mesh_allocator: &'a MeshAllocator, + pub entity: Entity, + pub main_entity: MainEntity, + pub draw_function: DrawFunctionId, + pub pipeline_id: CachedRenderPipelineId, + pub current_change_tick: Tick, + pub gpu_preprocessing_support: GpuPreprocessingSupport, + pub rangefinder: &'a ViewRangefinder3d, +} + +pub trait QueueBinnedPhaseItem: BinnedPhaseItem { + fn queue_item(context: &PhaseContext, render_phase: &mut BinnedRenderPhase) + where + BPI: BinnedPhaseItem; +} + +pub trait QueueSortedPhaseItem: SortedPhaseItem { + fn get_item(context: &PhaseContext) -> Option; +} + +/// A trait for extending [`PhaseItem`] so that it can be used by [`MeshPass`]. +pub trait PhaseItemExt: PhaseItem +where + >::Param: ReadOnlySystemParam, +{ + /// Defines which [`PhaseFamily`] this [`PhaseItem`] should use. + /// + /// The `BinnedPhaseItem` should use `BinnedPhaseFamily`, and the `SortedPhaseItem` should use `SortedPhaseFamily`. + type PhaseFamily: PhaseFamily; + + /// Determines whether the views should be extracted for this [`PhaseItem`]. + type ExtractCondition: ExtractCondition; + + /// The `RenderCommand` used for rendering this [`PhaseItem`]. + /// See [`RenderCommand`] for more details. + type RenderCommand: RenderCommand + Send + Sync; + + /// The [`RenderPhaseType`]s that this [`PhaseItem`] can handle. + /// + /// Typically a `PhaseItem` will only handle one `RenderPhaseType`, but it is possible to have a `PhaseItem` handle multiple `RenderPhaseType`s. + const PHASE_TYPES: RenderPhaseType; +} + +pub trait RenderPhase { + fn new(gpu_preprocessing: GpuPreprocessingMode) -> Self; + + fn prepare_for_new_frame(&mut self); + + fn validate_cached_entity( + &mut self, + visible_entity: MainEntity, + current_change_tick: Tick, + ) -> bool; + + fn add(&mut self, context: &PhaseContext); +} + +impl RenderPhase for BinnedRenderPhase +where + BPI: BinnedPhaseItem + QueueBinnedPhaseItem, +{ + #[inline] + fn new(gpu_preprocessing: GpuPreprocessingMode) -> Self { + Self::new(gpu_preprocessing) + } + + #[inline] + fn prepare_for_new_frame(&mut self) { + self.prepare_for_new_frame(); + } + + #[inline] + fn validate_cached_entity( + &mut self, + visible_entity: MainEntity, + current_change_tick: Tick, + ) -> bool { + self.validate_cached_entity(visible_entity, current_change_tick) + } + + #[inline] + fn add(&mut self, context: &PhaseContext) { + ::queue_item(context, self); + } +} + +impl RenderPhase for SortedRenderPhase +where + SPI: SortedPhaseItem + QueueSortedPhaseItem, + SortedRenderPhasePlugin: Plugin, +{ + #[inline] + fn new(_gpu_preprocessing: GpuPreprocessingMode) -> Self { + Self::default() + } + + #[inline] + fn prepare_for_new_frame(&mut self) { + self.clear(); + } + + #[inline] + fn validate_cached_entity( + &mut self, + _visible_entity: MainEntity, + _current_change_tick: Tick, + ) -> bool { + false + } + + #[inline] + fn add(&mut self, context: &PhaseContext) { + if let Some(item) = ::get_item(context) { + self.add(item); + } + } +} + /// 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( +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<(&ExtractedView, &RenderVisibleEntities)>, - specialized_material_pipeline_cache: ResMut, -) { - for (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; - }; - + mut views: Query<(&ExtractedView, &RenderVisibleEntities, Phases)>, + specialized_material_pipeline_cache: ResMut>, +) where + for<'w, 's> PhasesQI<'w, 's, MP>: PhasesQueryItem, +{ + for (view, visible_entities, mut phases) in &mut views { let Some(view_specialized_material_pipeline_cache) = specialized_material_pipeline_cache.get(&view.retained_view_entity) else { @@ -1185,6 +1714,7 @@ pub fn queue_material_meshes( }; let rangefinder = view.rangefinder3d(); + for (render_entity, visible_entity) in visible_entities.iter::() { let Some((current_change_tick, pipeline_id)) = view_specialized_material_pipeline_cache .get(visible_entity) @@ -1194,9 +1724,8 @@ pub fn queue_material_meshes( }; // Skip the entity if it's cached in a bin and up to date. - if opaque_phase.validate_cached_entity(*visible_entity, current_change_tick) - || alpha_mask_phase.validate_cached_entity(*visible_entity, current_change_tick) - { + // NOTE: SortedRenderPhase will always return false. + if phases.validate_cached_entity(*visible_entity, current_change_tick) { continue; } @@ -1212,117 +1741,23 @@ pub fn queue_material_meshes( continue; }; - // Fetch the slabs that this mesh resides in. - let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id); - - match material.properties.render_phase_type { - RenderPhaseType::Transmissive => { - let distance = rangefinder.distance(&mesh_instance.center) - + material.properties.depth_bias; - let Some(draw_function) = material - .properties - .get_draw_function(MainPassTransmissiveDrawFunction) - else { - continue; - }; - transmissive_phase.add(Transmissive3d { - entity: (*render_entity, *visible_entity), - draw_function, - pipeline: pipeline_id, - distance, - batch_range: 0..1, - extra_index: PhaseItemExtraIndex::None, - indexed: index_slab.is_some(), - }); - } - RenderPhaseType::Opaque => { - if material.properties.render_method == OpaqueRendererMethod::Deferred { - // Even though we aren't going to insert the entity into - // a bin, we still want to update its cache entry. That - // way, we know we don't need to re-examine it in future - // frames. - opaque_phase.update_cache(*visible_entity, None, current_change_tick); - continue; - } - let Some(draw_function) = material - .properties - .get_draw_function(MainPassOpaqueDrawFunction) - else { - continue; - }; - let batch_set_key = Opaque3dBatchSetKey { - pipeline: pipeline_id, - draw_function, - 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), - mesh_instance.current_uniform_index, - BinnedRenderPhaseType::mesh( - mesh_instance.should_batch(), - &gpu_preprocessing_support, - ), - current_change_tick, - ); - } - // Alpha mask - RenderPhaseType::AlphaMask => { - let Some(draw_function) = material - .properties - .get_draw_function(MainPassAlphaMaskDrawFunction) - else { - continue; - }; - let batch_set_key = OpaqueNoLightmap3dBatchSetKey { - draw_function, - 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), - mesh_instance.current_uniform_index, - BinnedRenderPhaseType::mesh( - mesh_instance.should_batch(), - &gpu_preprocessing_support, - ), - current_change_tick, - ); - } - RenderPhaseType::Transparent => { - let distance = rangefinder.distance(&mesh_instance.center) - + material.properties.depth_bias; - let Some(draw_function) = material - .properties - .get_draw_function(MainPassTransparentDrawFunction) - else { - continue; - }; - transparent_phase.add(Transparent3d { - entity: (*render_entity, *visible_entity), - draw_function, - pipeline: pipeline_id, - distance, - batch_range: 0..1, - extra_index: PhaseItemExtraIndex::None, - indexed: index_slab.is_some(), - }); - } + if !material.properties.enabled_passes.contains(&MP::id()) { + continue; } + + let mut context = PhaseContext { + mesh_instance: &mesh_instance, + material, + mesh_allocator: &mesh_allocator, + entity: *render_entity, + main_entity: *visible_entity, + draw_function: DrawFunctionId::PLACEHOLDER, + pipeline_id, + current_change_tick, + gpu_preprocessing_support: *gpu_preprocessing_support, + rangefinder: &rangefinder, + }; + phases.add(&mut context, MP::id(), MP::PhaseItems::PHASES_TYPES); } } } @@ -1377,23 +1812,11 @@ pub enum OpaqueRendererMethod { Auto, } -#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)] -pub struct MaterialVertexShader; - -#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)] -pub struct MaterialFragmentShader; - -#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)] -pub struct PrepassVertexShader; - -#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)] -pub struct PrepassFragmentShader; - -#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)] -pub struct DeferredVertexShader; +#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone)] +pub struct MaterialVertexShader(pub PassId); -#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)] -pub struct DeferredFragmentShader; +#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone)] +pub struct MaterialFragmentShader(pub PassId); #[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)] pub struct MeshletFragmentShader; @@ -1404,28 +1827,15 @@ pub struct MeshletPrepassFragmentShader; #[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)] pub struct MeshletDeferredFragmentShader; -#[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone, Default)] -pub struct MainPassOpaqueDrawFunction; -#[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone, Default)] -pub struct MainPassAlphaMaskDrawFunction; -#[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone, Default)] -pub struct MainPassTransmissiveDrawFunction; -#[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone, Default)] -pub struct MainPassTransparentDrawFunction; - -#[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone, Default)] -pub struct PrepassOpaqueDrawFunction; -#[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone, Default)] -pub struct PrepassAlphaMaskDrawFunction; - -#[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone, Default)] -pub struct DeferredOpaqueDrawFunction; -#[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone, Default)] -pub struct DeferredAlphaMaskDrawFunction; - #[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone, Default)] pub struct ShadowsDrawFunction; +#[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone)] +pub struct MeshPassDrawFunction { + pub pass_id: PassId, + pub phase_idx: usize, +} + #[derive(Debug)] pub struct ErasedMaterialKey { type_id: TypeId, @@ -1524,8 +1934,8 @@ pub struct MaterialProperties { pub depth_bias: f32, /// Whether the material would like to read from [`ViewTransmissionTexture`](bevy_core_pipeline::core_3d::ViewTransmissionTexture). /// - /// 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. + /// This allows taking color output from the [`bevy_core_pipeline::core_3d::Opaque3d`] pass as an input, (for screen-space transmission) but requires + /// rendering to take place in a separate [`bevy_core_pipeline::core_3d::Transmissive3d`] pass. pub reads_view_transmission_texture: bool, pub render_phase_type: RenderPhaseType, pub material_layout: Option, @@ -1550,8 +1960,8 @@ pub struct MaterialProperties { pub material_key: ErasedMaterialKey, /// Whether shadows are enabled for this material pub shadows_enabled: bool, - /// Whether prepass is enabled for this material - pub prepass_enabled: bool, + /// Whether the passes are enabled for this material + pub enabled_passes: SmallVec<[PassId; 3]>, } impl MaterialProperties { @@ -1584,15 +1994,33 @@ impl MaterialProperties { } } -#[derive(Clone, Copy, Default)] -pub enum RenderPhaseType { - #[default] - Opaque, - AlphaMask, - Transmissive, - Transparent, +// NOTE: To handle the case like `Shadow` where a single phase works for materials +// with different render phase types, we use bitflags instead of enum. +bitflags::bitflags! { + /// Defines all the possible render phase types for a material. + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] + pub struct RenderPhaseType: u8 { + const Opaque = 1 << 0; + const AlphaMask = 1 << 1; + const Transmissive = 1 << 2; + const Transparent = 1 << 3; + } +} + +impl Default for RenderPhaseType { + fn default() -> Self { + RenderPhaseType::Opaque + } } +/// Stores draw functions for each `MeshPass`. +#[derive(Resource, Default, Deref, DerefMut)] +pub struct MeshPassDrawFunctions(HashMap); + +/// Stores the draw functions for each `PhaseItem` of a `MeshPass`. +#[derive(Default, Deref, DerefMut)] +pub struct PhaseDrawFunctions([Option; MESH_PASS_MAX_PHASES]); + /// A resource that maps each untyped material ID to its binding. /// /// This duplicates information in `RenderAssets`, but it doesn't have the @@ -1621,15 +2049,8 @@ where SRes, SResMut, SResMut, - SRes>, - SRes>, - SRes>, - SRes>, - SRes>, - SRes>, - SRes>, - SRes>, SRes>, + SRes, SRes, M::Param, ); @@ -1643,57 +2064,19 @@ where default_opaque_render_method, bind_group_allocators, 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, shadow_draw_functions, + mesh_pass_draw_functions, asset_server, material_param, ): &mut SystemParamItem, ) -> Result> { let shadows_enabled = M::enable_shadows(); - let prepass_enabled = M::enable_prepass(); - - 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 mut enabled_passes = SmallVec::new(); + let draw_shadows = shadow_draw_functions.read().id::(); - let draw_functions = SmallVec::from_iter([ - (MainPassOpaqueDrawFunction.intern(), draw_opaque_pbr), - (MainPassAlphaMaskDrawFunction.intern(), draw_alpha_mask_pbr), - ( - MainPassTransmissiveDrawFunction.intern(), - draw_transmissive_pbr, - ), - ( - MainPassTransparentDrawFunction.intern(), - draw_transparent_pbr, - ), - (PrepassOpaqueDrawFunction.intern(), draw_opaque_prepass), - ( - PrepassAlphaMaskDrawFunction.intern(), - draw_alpha_mask_prepass, - ), - (DeferredOpaqueDrawFunction.intern(), draw_opaque_deferred), - ( - DeferredAlphaMaskDrawFunction.intern(), - draw_alpha_mask_deferred, - ), - (ShadowsDrawFunction.intern(), draw_shadows), - ]); + let mut draw_functions = + SmallVec::from_iter([(ShadowsDrawFunction.intern(), draw_shadows)]); let render_method = match material.opaque_render_method() { OpaqueRendererMethod::Forward => OpaqueRendererMethod::Forward, @@ -1710,14 +2093,8 @@ where let reads_view_transmission_texture = 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 - } - _ if reads_view_transmission_texture => RenderPhaseType::Transmissive, - AlphaMode::Opaque | AlphaMode::AlphaToCoverage => RenderPhaseType::Opaque, - AlphaMode::Mask(_) => RenderPhaseType::AlphaMask, - }; + let render_phase_type = + alpha_mode_render_phase_type(material.alpha_mode(), reads_view_transmission_texture); let mut shaders = SmallVec::new(); let mut add_shader = |label: InternedShaderLabel, shader_ref: ShaderRef| { @@ -1730,15 +2107,125 @@ where shaders.push((label, shader)); } }; - add_shader(MaterialVertexShader.intern(), M::vertex_shader()); - add_shader(MaterialFragmentShader.intern(), M::fragment_shader()); - add_shader(PrepassVertexShader.intern(), M::prepass_vertex_shader()); - add_shader(PrepassFragmentShader.intern(), M::prepass_fragment_shader()); - add_shader(DeferredVertexShader.intern(), M::deferred_vertex_shader()); - add_shader( - DeferredFragmentShader.intern(), - M::deferred_fragment_shader(), - ); + + for ( + pass_id, + ShaderSet { + mut vertex, + mut fragment, + }, + ) in M::shaders() + { + let Some(phase_draw_functions) = mesh_pass_draw_functions.get(&pass_id) else { + continue; + }; + + const PHASE_DRAW_FUNCTIONS_ERROR: &str = "The index here should never be out of bounds"; + + if let Some(draw_function) = phase_draw_functions + .first() + .expect(PHASE_DRAW_FUNCTIONS_ERROR) + { + draw_functions.push(( + MeshPassDrawFunction { + pass_id, + phase_idx: 0, + } + .intern(), + *draw_function, + )); + }; + + if let Some(draw_function) = phase_draw_functions + .get(1) + .expect(PHASE_DRAW_FUNCTIONS_ERROR) + { + draw_functions.push(( + MeshPassDrawFunction { + pass_id, + phase_idx: 1, + } + .intern(), + *draw_function, + )); + }; + + if let Some(draw_function) = phase_draw_functions + .get(2) + .expect(PHASE_DRAW_FUNCTIONS_ERROR) + { + draw_functions.push(( + MeshPassDrawFunction { + pass_id, + phase_idx: 2, + } + .intern(), + *draw_function, + )); + }; + + if let Some(draw_function) = phase_draw_functions + .get(3) + .expect(PHASE_DRAW_FUNCTIONS_ERROR) + { + draw_functions.push(( + MeshPassDrawFunction { + pass_id, + phase_idx: 3, + } + .intern(), + *draw_function, + )); + }; + + // If users are still using the traditional method, it continues to be used until they migrate. + if pass_id == Prepass::id() { + let prepass_vertex = M::prepass_vertex_shader(); + if !matches!(prepass_vertex, ShaderRef::Default) { + vertex = prepass_vertex; + } + + let prepass_fragment = M::prepass_fragment_shader(); + if !matches!(prepass_fragment, ShaderRef::Default) { + fragment = prepass_fragment; + } + } + + if pass_id == DeferredPass::id() { + let deferred_vertex = M::deferred_vertex_shader(); + if !matches!(deferred_vertex, ShaderRef::Default) { + vertex = deferred_vertex; + } + + let deferred_fragment = M::deferred_fragment_shader(); + if !matches!(deferred_fragment, ShaderRef::Default) { + fragment = deferred_fragment; + } + } + + if pass_id == MainPass::id() { + let main_pass_vertex = M::vertex_shader(); + if !matches!(main_pass_vertex, ShaderRef::Default) { + vertex = main_pass_vertex; + } + + let main_pass_fragment = M::fragment_shader(); + if !matches!(main_pass_fragment, ShaderRef::Default) { + fragment = main_pass_fragment; + } + } + + add_shader(MaterialVertexShader(pass_id).intern(), vertex); + add_shader(MaterialFragmentShader(pass_id).intern(), fragment); + + if pass_id == Prepass::id() { + if M::enable_prepass() { + enabled_passes.push(pass_id); + } + } else { + enabled_passes.push(pass_id); + } + } #[cfg(feature = "meshlet")] { @@ -1776,6 +2263,7 @@ where MaterialPipelineKey { mesh_key: erased_key.mesh_key, bind_group_data: material_key, + pass_id: erased_key.pass_id, }, ) } @@ -1826,7 +2314,7 @@ where specialize: Some(specialize::), material_key, shadows_enabled, - prepass_enabled, + enabled_passes, }), }) } @@ -1870,7 +2358,7 @@ where specialize: Some(specialize::), material_key, shadows_enabled, - prepass_enabled, + enabled_passes, }), }) } diff --git a/crates/bevy_pbr/src/meshlet/material_pipeline_prepare.rs b/crates/bevy_pbr/src/meshlet/material_pipeline_prepare.rs index e00c41900146d..f939a5e7ccaa8 100644 --- a/crates/bevy_pbr/src/meshlet/material_pipeline_prepare.rs +++ b/crates/bevy_pbr/src/meshlet/material_pipeline_prepare.rs @@ -162,6 +162,7 @@ pub fn prepare_material_meshlet_meshes_main_opaque_pass( mesh_key: view_key, material_key: material.properties.material_key.clone(), type_id: material_id.type_id(), + pass_id: MainPass::id(), }; let material_pipeline_specializer = MaterialPipelineSpecializer { pipeline: material_pipeline.clone(), @@ -334,6 +335,7 @@ pub fn prepare_material_meshlet_meshes_prepass( mesh_key: view_key, material_key: material.properties.material_key.clone(), type_id: material_id.type_id(), + pass_id: Prepass::id(), }; let material_pipeline_specializer = PrepassPipelineSpecializer { pipeline: prepass_pipeline.clone(), diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index d6e6ce69b49b0..799c64a1782aa 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -1,22 +1,13 @@ mod prepass_bindings; -use crate::{ - alpha_mode_pipeline_key, binding_arrays_are_usable, buffer_layout, - collect_meshes_for_gpu_building, init_material_pipeline, set_mesh_motion_vector_flags, - setup_morph_and_skinning_defs, skin, DeferredAlphaMaskDrawFunction, DeferredFragmentShader, - DeferredOpaqueDrawFunction, DeferredVertexShader, DrawMesh, EntitySpecializationTicks, - ErasedMaterialPipelineKey, MaterialPipeline, MaterialProperties, MeshLayouts, MeshPipeline, - MeshPipelineKey, OpaqueRendererMethod, PreparedMaterial, PrepassAlphaMaskDrawFunction, - PrepassFragmentShader, PrepassOpaqueDrawFunction, PrepassVertexShader, RenderLightmaps, - RenderMaterialInstances, RenderMeshInstanceFlags, RenderMeshInstances, RenderPhaseType, - SetMaterialBindGroup, SetMeshBindGroup, ShadowView, -}; +use crate::*; use bevy_app::{App, Plugin, PreUpdate}; use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle}; use bevy_camera::{Camera, Camera3d}; use bevy_core_pipeline::{core_3d::CORE_3D_DEPTH_FORMAT, deferred::*, prepass::*}; use bevy_ecs::{ prelude::*, + query::QueryItem, system::{ lifetimeless::{Read, SRes}, SystemParamItem, @@ -25,25 +16,22 @@ use bevy_ecs::{ use bevy_math::{Affine3A, Mat4, Vec4}; use bevy_mesh::{Mesh, Mesh3d, MeshVertexBufferLayoutRef}; use bevy_render::{ - alpha::AlphaMode, - batching::gpu_preprocessing::GpuPreprocessingSupport, + extract_component::{ExtractComponent, ExtractComponentPlugin}, globals::{GlobalsBuffer, GlobalsUniform}, - mesh::{allocator::MeshAllocator, RenderMesh}, - render_asset::{prepare_assets, RenderAssets}, render_phase::*, render_resource::{binding_types::uniform_buffer, *}, renderer::{RenderAdapter, RenderDevice, RenderQueue}, sync_world::RenderEntity, view::{ - ExtractedView, Msaa, RenderVisibilityRanges, RetainedViewEntity, ViewUniform, - ViewUniformOffset, ViewUniforms, VISIBILITY_RANGES_STORAGE_BUFFER_COUNT, + ExtractedView, Msaa, RenderVisibilityRanges, ViewUniform, ViewUniformOffset, ViewUniforms, + VISIBILITY_RANGES_STORAGE_BUFFER_COUNT, }, Extract, ExtractSchedule, Render, RenderApp, RenderDebugFlags, RenderStartup, RenderSystems, }; use bevy_shader::{load_shader_library, Shader, ShaderDefVal}; use bevy_transform::prelude::GlobalTransform; pub use prepass_bindings::*; -use tracing::{error, warn}; +use tracing::warn; #[cfg(feature = "meshlet")] use crate::meshlet::{ @@ -52,15 +40,8 @@ use crate::meshlet::{ }; use alloc::sync::Arc; -use bevy_derive::{Deref, DerefMut}; -use bevy_ecs::{change_detection::Tick, system::SystemChangeTick}; -use bevy_platform::collections::HashMap; -use bevy_render::{ - erased_render_asset::ErasedRenderAssets, - sync_world::MainEntityHashMap, - view::RenderVisibleEntities, - RenderSystems::{PrepareAssets, PrepareResources}, -}; +use bevy_ecs::system::SystemChangeTick; +use bevy_render::RenderSystems::{PrepareAssets, PrepareResources}; use bevy_utils::default; /// Sets up everything required to use the prepass pipeline. @@ -92,8 +73,7 @@ impl Plugin for PrepassPipelinePlugin { .add_systems( Render, prepare_prepass_view_bind_group.in_set(RenderSystems::PrepareBindGroups), - ) - .init_resource::>(); + ); } } @@ -119,8 +99,21 @@ impl Plugin for PrepassPlugin { .get_resource::() .is_none(); + // QUESTION: + // When will we want to add `PrepassPlugin` multiple times? + // Why don't these guards protect systems like `check_prepass_views_need_specialization`? if no_prepass_plugin_loaded { app.insert_resource(AnyPrepassPluginLoaded) + .register_required_components::() + .register_required_components::() + .add_plugins(( + ExtractComponentPlugin::::default(), + ExtractComponentPlugin::::default(), + ExtractComponentPlugin::::default(), + ExtractComponentPlugin::::default(), + MeshPassPlugin::::new(self.debug_flags), + MeshPassPlugin::::new(self.debug_flags), + )) // At the start of each frame, last frame's GlobalTransforms become this frame's PreviousGlobalTransforms // and last frame's view projection matrices become this frame's PreviousViewProjections .add_systems( @@ -129,13 +122,7 @@ impl Plugin for PrepassPlugin { update_mesh_previous_global_transforms, update_previous_view_data, ), - ) - .add_plugins(( - BinnedRenderPhasePlugin::::new(self.debug_flags), - BinnedRenderPhasePlugin::::new( - self.debug_flags, - ), - )); + ); } let Some(render_app) = app.get_sub_app_mut(RenderApp) else { @@ -151,26 +138,10 @@ impl Plugin for PrepassPlugin { ); } - render_app - .init_resource::() - .init_resource::() - .init_resource::() - .add_render_command::() - .add_render_command::() - .add_render_command::() - .add_render_command::() - .add_systems( - Render, - ( - check_prepass_views_need_specialization.in_set(PrepareAssets), - specialize_prepass_material_meshes - .in_set(RenderSystems::PrepareMeshes) - .after(prepare_assets::) - .after(collect_meshes_for_gpu_building) - .after(set_mesh_motion_vector_flags), - queue_prepass_material_meshes.in_set(RenderSystems::QueueMeshes), - ), - ); + render_app.add_systems( + Render, + check_prepass_views_need_specialization::.in_set(PrepareAssets), + ); #[cfg(feature = "meshlet")] render_app.add_systems( @@ -183,6 +154,27 @@ impl Plugin for PrepassPlugin { } } +#[derive(Clone, Copy, Default, Component, ExtractComponent)] +pub struct Prepass; + +impl MeshPass for Prepass { + type ViewKeySource = Self; + type Specializer = PrepassPipelineSpecializer; + type PhaseItems = (Opaque3dPrepass, AlphaMask3dPrepass); +} + +// Or `DeferredPrepass`? +#[derive(Clone, Copy, Default, Component, ExtractComponent)] +pub struct DeferredPass; + +// Currently we use `check_prepass_views_need_specialization` for both Prepass and DeferredPass. +// Maybe split it later. +impl MeshPass for DeferredPass { + type ViewKeySource = Prepass; + type Specializer = PrepassPipelineSpecializer; + type PhaseItems = (Opaque3dDeferred, AlphaMask3dDeferred); +} + #[derive(Resource)] struct AnyPrepassPluginLoaded; @@ -322,7 +314,7 @@ pub fn init_prepass_pipeline( view_layout_no_motion_vectors, mesh_layouts: mesh_pipeline.mesh_layouts.clone(), default_prepass_shader: load_embedded_asset!(asset_server.as_ref(), "prepass.wgsl"), - skins_use_uniform_buffers: skin::skins_use_uniform_buffers(&render_device.limits()), + skins_use_uniform_buffers: skins_use_uniform_buffers(&render_device.limits()), depth_clip_control_supported, binding_arrays_are_usable: binding_arrays_are_usable(&render_device, &render_adapter), empty_layout: BindGroupLayoutDescriptor::new("prepass_empty_layout", &[]), @@ -335,6 +327,79 @@ pub struct PrepassPipelineSpecializer { pub properties: Arc, } +impl MeshPassSpecializer for PrepassPipelineSpecializer { + type Pipeline = PrepassPipeline; + + fn create_key(context: &SpecializerKeyContext) -> Self::Key { + let mut mesh_key = + context.view_key | MeshPipelineKey::from_bits_retain(context.mesh_pipeline_key.bits()); + + mesh_key |= alpha_mode_pipeline_key( + context.material.properties.alpha_mode, + &Msaa::from_samples(context.view_key.msaa_samples()), + ); + + let forward = match context.material.properties.render_method { + OpaqueRendererMethod::Forward => true, + OpaqueRendererMethod::Deferred => false, + OpaqueRendererMethod::Auto => unreachable!(), + }; + + // view_key already contains the deferred prepass flag + let deferred = context.view_key.contains(MeshPipelineKey::DEFERRED_PREPASS) && !forward; + + if let Some(lightmap) = context.lightmap { + // Even though we don't use the lightmap in the forward prepass, the + // `SetMeshBindGroup` render command will bind the data for it. So + // we need to include the appropriate flag in the mesh pipeline key + // to ensure that the necessary bind group layout entries are + // present. + mesh_key |= MeshPipelineKey::LIGHTMAPPED; + + if lightmap.bicubic_sampling && deferred { + mesh_key |= MeshPipelineKey::LIGHTMAP_BICUBIC_SAMPLING; + } + } + + if context.has_crossfade { + mesh_key |= MeshPipelineKey::VISIBILITY_RANGE_DITHER; + } + + // If the previous frame has skins or morph targets, note that. + if context + .view_key + .contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) + { + if context + .mesh_instance_flags + .contains(RenderMeshInstanceFlags::HAS_PREVIOUS_SKIN) + { + mesh_key |= MeshPipelineKey::HAS_PREVIOUS_SKIN; + } + if context + .mesh_instance_flags + .contains(RenderMeshInstanceFlags::HAS_PREVIOUS_MORPH) + { + mesh_key |= MeshPipelineKey::HAS_PREVIOUS_MORPH; + } + } + + ErasedMaterialPipelineKey { + mesh_key, + material_key: context.material.properties.material_key.clone(), + type_id: context.material_asset_id, + pass_id: context.pass_id, + } + } + + fn new(pipeline: &Self::Pipeline, material: &PreparedMaterial) -> Self { + PrepassPipelineSpecializer { + pipeline: pipeline.clone(), + properties: material.properties.clone(), + } + } +} + impl SpecializedMeshPipeline for PrepassPipelineSpecializer { type Key = ErasedMaterialPipelineKey; @@ -347,9 +412,39 @@ impl SpecializedMeshPipeline for PrepassPipelineSpecializer { if self.properties.bindless { shader_defs.push("BINDLESS".into()); } - let mut descriptor = - self.pipeline - .specialize(key.mesh_key, shader_defs, layout, &self.properties)?; + + let mesh_key = key.mesh_key; + + let mut bind_group_layouts = vec![ + if mesh_key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) { + self.pipeline.view_layout_motion_vectors.clone() + } else { + self.pipeline.view_layout_no_motion_vectors.clone() + }, + self.pipeline.empty_layout.clone(), + ]; + + // NOTE: Eventually, it would be nice to only add this when the shaders are overloaded by the Material. + // The main limitation right now is that bind group order is hardcoded in shaders. + bind_group_layouts.push(self.properties.material_layout.as_ref().unwrap().clone()); + + // Setup prepass fragment targets - normals in slot 0 (or None if not needed), motion vectors in slot 1 + let targets = prepass_target_descriptors( + mesh_key.contains(MeshPipelineKey::NORMAL_PREPASS), + mesh_key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS), + mesh_key.contains(MeshPipelineKey::DEFERRED_PREPASS), + ); + + let mut descriptor = self.pipeline.specialize( + mesh_key, + shader_defs, + layout, + &self.properties, + key.pass_id, + 2, + bind_group_layouts, + targets, + )?; // This is a bit risky because it's possible to change something that would // break the prepass but be fine in the main pass. @@ -374,16 +469,12 @@ impl PrepassPipeline { shader_defs: Vec, layout: &MeshVertexBufferLayoutRef, material_properties: &MaterialProperties, + pass_id: PassId, + mesh_bind_group_index: usize, + mut bind_group_layouts: Vec, + mut targets: Vec>, ) -> Result { let mut shader_defs = shader_defs; - let mut bind_group_layouts = vec![ - if mesh_key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) { - self.view_layout_motion_vectors.clone() - } else { - self.view_layout_no_motion_vectors.clone() - }, - self.empty_layout.clone(), - ]; let mut vertex_attributes = Vec::new(); // Let the shader code know that it's running in a prepass pipeline. @@ -393,17 +484,9 @@ impl PrepassPipeline { shader_defs.push(ShaderDefVal::UInt( "MATERIAL_BIND_GROUP".into(), - crate::MATERIAL_BIND_GROUP_INDEX as u32, + MATERIAL_BIND_GROUP_INDEX as u32, )); - // NOTE: Eventually, it would be nice to only add this when the shaders are overloaded by the Material. - // The main limitation right now is that bind group order is hardcoded in shaders. - bind_group_layouts.push( - material_properties - .material_layout - .as_ref() - .unwrap() - .clone(), - ); + #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] shader_defs.push("WEBGL2".into()); shader_defs.push("VERTEX_OUTPUT_INSTANCE_INDEX".into()); @@ -518,14 +601,8 @@ impl PrepassPipeline { &mut vertex_attributes, self.skins_use_uniform_buffers, ); - bind_group_layouts.insert(2, bind_group); + bind_group_layouts.insert(mesh_bind_group_index, bind_group); let vertex_buffer_layout = layout.0.get_layout(&vertex_attributes)?; - // Setup prepass fragment targets - normals in slot 0 (or None if not needed), motion vectors in slot 1 - let mut targets = prepass_target_descriptors( - mesh_key.contains(MeshPipelineKey::NORMAL_PREPASS), - mesh_key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS), - mesh_key.contains(MeshPipelineKey::DEFERRED_PREPASS), - ); if targets.iter().all(Option::is_none) { // if no targets are required then clear the list, so that no fragment shader is required @@ -540,22 +617,14 @@ impl PrepassPipeline { || emulate_unclipped_depth || (mesh_key.contains(MeshPipelineKey::MAY_DISCARD) && material_properties - .get_shader(PrepassFragmentShader) + .get_shader(MaterialFragmentShader(pass_id)) .is_some()); let fragment = fragment_required.then(|| { // Use the fragment shader from the material - let frag_shader_handle = if mesh_key.contains(MeshPipelineKey::DEFERRED_PREPASS) { - match material_properties.get_shader(DeferredFragmentShader) { - Some(frag_shader_handle) => frag_shader_handle, - None => self.default_prepass_shader.clone(), - } - } else { - match material_properties.get_shader(PrepassFragmentShader) { - Some(frag_shader_handle) => frag_shader_handle, - None => self.default_prepass_shader.clone(), - } - }; + let frag_shader_handle = material_properties + .get_shader(MaterialFragmentShader(pass_id)) + .unwrap_or(self.default_prepass_shader.clone()); FragmentState { shader: frag_shader_handle, @@ -566,17 +635,10 @@ impl PrepassPipeline { }); // Use the vertex shader from the material if present - let vert_shader_handle = if mesh_key.contains(MeshPipelineKey::DEFERRED_PREPASS) { - if let Some(handle) = material_properties.get_shader(DeferredVertexShader) { - handle - } else { - self.default_prepass_shader.clone() - } - } else if let Some(handle) = material_properties.get_shader(PrepassVertexShader) { - handle - } else { - self.default_prepass_shader.clone() - }; + let vert_shader_handle = material_properties + .get_shader(MaterialVertexShader(pass_id)) + .unwrap_or(self.default_prepass_shader.clone()); + let descriptor = RenderPipelineDescriptor { vertex: VertexState { shader: vert_shader_handle, @@ -747,42 +809,22 @@ pub fn prepare_prepass_view_bind_group( } } -/// Stores the [`SpecializedPrepassMaterialViewPipelineCache`] for each view. -#[derive(Resource, Deref, DerefMut, Default)] -pub struct SpecializedPrepassMaterialPipelineCache { - // view_entity -> view pipeline cache - #[deref] - map: HashMap, -} - -/// Stores the cached render pipeline ID for each entity in a single view, as -/// well as the last time it was changed. -#[derive(Deref, DerefMut, Default)] -pub struct SpecializedPrepassMaterialViewPipelineCache { - // material entity -> (tick, pipeline_id) - #[deref] - map: MainEntityHashMap<(Tick, CachedRenderPipelineId)>, -} - -#[derive(Resource, Deref, DerefMut, Default, Clone)] -pub struct ViewKeyPrepassCache(HashMap); - -#[derive(Resource, Deref, DerefMut, Default, Clone)] -pub struct ViewPrepassSpecializationTicks(HashMap); - -pub fn check_prepass_views_need_specialization( - mut view_key_cache: ResMut, - mut view_specialization_ticks: ResMut, +pub fn check_prepass_views_need_specialization( + mut view_key_cache: ResMut>, + mut view_specialization_ticks: ResMut>, mut views: Query<( &ExtractedView, &Msaa, Option<&DepthPrepass>, Option<&NormalPrepass>, Option<&MotionVectorPrepass>, + Option<&DeferredPrepass>, )>, ticks: SystemChangeTick, ) { - for (view, msaa, depth_prepass, normal_prepass, motion_vector_prepass) in views.iter_mut() { + for (view, msaa, depth_prepass, normal_prepass, motion_vector_prepass, deferred_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; @@ -793,6 +835,11 @@ pub fn check_prepass_views_need_specialization( if motion_vector_prepass.is_some() { view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS; } + // NOTE: This parameter was moved from `specialize_prepass_material_meshes` to here, + // which means the specialization of prepass will be affected by `DeferredPrepass` now. + if deferred_prepass.is_some() { + view_key |= MeshPipelineKey::DEFERRED_PREPASS; + } if let Some(current_key) = view_key_cache.get_mut(&view.retained_view_entity) { if *current_key != view_key { @@ -806,408 +853,197 @@ pub fn check_prepass_views_need_specialization( } } -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, - view_key_cache: Res, - views: Query<( - &ExtractedView, - &RenderVisibleEntities, - &Msaa, - Option<&MotionVectorPrepass>, - Option<&DeferredPrepass>, - )>, - ( - opaque_prepass_render_phases, - alpha_mask_prepass_render_phases, - opaque_deferred_render_phases, - alpha_mask_deferred_render_phases, - ): ( - Res>, - Res>, - Res>, - Res>, - ), - ( - 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, - ), -) { - for (extracted_view, visible_entities, msaa, 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) - && !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 Some(view_key) = view_key_cache.get(&extracted_view.retained_view_entity) else { - continue; - }; +pub struct PrepassExtractCondition; - let view_tick = view_specialization_ticks - .get(&extracted_view.retained_view_entity) - .unwrap(); - let view_specialized_material_pipeline_cache = specialized_material_pipeline_cache - .entry(extracted_view.retained_view_entity) - .or_default(); - - for (_, visible_entity) in visible_entities.iter::() { - let Some(material_instance) = render_material_instances.instances.get(visible_entity) - else { - continue; - }; - let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity) - else { - continue; - }; - let entity_tick = entity_specialization_ticks - .get(visible_entity) - .unwrap() - .system_tick; - let last_specialized_tick = view_specialized_material_pipeline_cache - .get(visible_entity) - .map(|(tick, _)| *tick); - 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) = render_materials.get(material_instance.asset_id) else { - continue; - }; - if !material.properties.prepass_enabled { - // If the material was previously specialized for prepass, remove it - view_specialized_material_pipeline_cache.remove(visible_entity); - continue; - } - let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else { - continue; - }; - - 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); - } - AlphaMode::Blend - | AlphaMode::Premultiplied - | AlphaMode::Add - | AlphaMode::Multiply => { - // In case this material was previously in a valid alpha_mode, remove it to - // stop the queue system from assuming its retained cache to be valid. - view_specialized_material_pipeline_cache.remove(visible_entity); - continue; - } - } - - if material.properties.reads_view_transmission_texture { - // No-op: Materials reading from `ViewTransmissionTexture` are not rendered in the `Opaque3d` - // phase, and are therefore also excluded from the prepass much like alpha-blended materials. - view_specialized_material_pipeline_cache.remove(visible_entity); - continue; - } +impl ExtractCondition for PrepassExtractCondition { + type ViewQuery = ( + Has, + Has, + Has, + ); - let forward = match material.properties.render_method { - OpaqueRendererMethod::Forward => true, - OpaqueRendererMethod::Deferred => false, - OpaqueRendererMethod::Auto => unreachable!(), - }; + fn should_extract( + (depth_prepass, normal_prepass, motion_vector_prepass): QueryItem<'_, '_, Self::ViewQuery>, + ) -> bool { + depth_prepass || normal_prepass || motion_vector_prepass + } +} - let deferred = deferred_prepass.is_some() && !forward; +pub struct DeferredExtractCondition; - if deferred { - mesh_key |= MeshPipelineKey::DEFERRED_PREPASS; - } +impl ExtractCondition for DeferredExtractCondition { + type ViewQuery = Has; - if let Some(lightmap) = render_lightmaps.render_lightmaps.get(visible_entity) { - // Even though we don't use the lightmap in the forward prepass, the - // `SetMeshBindGroup` render command will bind the data for it. So - // we need to include the appropriate flag in the mesh pipeline key - // to ensure that the necessary bind group layout entries are - // present. - mesh_key |= MeshPipelineKey::LIGHTMAPPED; + #[inline] + fn should_extract(deferred_prepass: QueryItem<'_, '_, Self::ViewQuery>) -> bool { + deferred_prepass + } +} - if lightmap.bicubic_sampling && deferred { - mesh_key |= MeshPipelineKey::LIGHTMAP_BICUBIC_SAMPLING; - } - } +impl PhaseItemExt for Opaque3dPrepass { + type PhaseFamily = BinnedPhaseFamily; + type ExtractCondition = PrepassExtractCondition; + type RenderCommand = DrawPrepass; + const PHASE_TYPES: RenderPhaseType = RenderPhaseType::Opaque; +} - if render_visibility_ranges.entity_has_crossfading_visibility_ranges(*visible_entity) { - mesh_key |= MeshPipelineKey::VISIBILITY_RANGE_DITHER; - } +impl QueueBinnedPhaseItem for Opaque3dPrepass { + #[inline] + fn queue_item(context: &PhaseContext, render_phase: &mut BinnedRenderPhase) + where + BPI: BinnedPhaseItem, + { + if let OpaqueRendererMethod::Forward = context.material.properties.render_method { + let (vertex_slab, index_slab) = context + .mesh_allocator + .mesh_slabs(&context.mesh_instance.mesh_asset_id); + + render_phase.add( + OpaqueNoLightmap3dBatchSetKey { + draw_function: context.draw_function, + pipeline: context.pipeline_id, + material_bind_group_index: Some(context.material.binding.group.0), + vertex_slab: vertex_slab.unwrap_or_default(), + index_slab, + }, + OpaqueNoLightmap3dBinKey { + asset_id: context.mesh_instance.mesh_asset_id.into(), + }, + (context.entity, context.main_entity), + context.mesh_instance.current_uniform_index, + BinnedRenderPhaseType::mesh( + context.mesh_instance.should_batch(), + &context.gpu_preprocessing_support, + ), + context.current_change_tick, + ); + } + } +} - // If the previous frame has skins or morph targets, note that. - if motion_vector_prepass.is_some() { - if mesh_instance - .flags - .contains(RenderMeshInstanceFlags::HAS_PREVIOUS_SKIN) - { - mesh_key |= MeshPipelineKey::HAS_PREVIOUS_SKIN; - } - if mesh_instance - .flags - .contains(RenderMeshInstanceFlags::HAS_PREVIOUS_MORPH) - { - mesh_key |= MeshPipelineKey::HAS_PREVIOUS_MORPH; - } - } +impl PhaseItemExt for AlphaMask3dPrepass { + type PhaseFamily = BinnedPhaseFamily; + type ExtractCondition = PrepassExtractCondition; + type RenderCommand = DrawPrepass; + const PHASE_TYPES: RenderPhaseType = RenderPhaseType::AlphaMask; +} - let erased_key = ErasedMaterialPipelineKey { - mesh_key, - material_key: material.properties.material_key.clone(), - type_id: material_instance.asset_id.type_id(), +impl QueueBinnedPhaseItem for AlphaMask3dPrepass { + #[inline] + fn queue_item(context: &PhaseContext, render_phase: &mut BinnedRenderPhase) + where + BPI: BinnedPhaseItem, + { + if let OpaqueRendererMethod::Forward = context.material.properties.render_method { + let (vertex_slab, index_slab) = context + .mesh_allocator + .mesh_slabs(&context.mesh_instance.mesh_asset_id); + + let batch_set_key = OpaqueNoLightmap3dBatchSetKey { + draw_function: context.draw_function, + pipeline: context.pipeline_id, + material_bind_group_index: Some(context.material.binding.group.0), + vertex_slab: vertex_slab.unwrap_or_default(), + index_slab, }; - let prepass_pipeline_specializer = PrepassPipelineSpecializer { - pipeline: prepass_pipeline.clone(), - properties: material.properties.clone(), + let bin_key = OpaqueNoLightmap3dBinKey { + asset_id: context.mesh_instance.mesh_asset_id.into(), }; - let pipeline_id = pipelines.specialize( - &pipeline_cache, - &prepass_pipeline_specializer, - erased_key, - &mesh.layout, + render_phase.add( + batch_set_key, + bin_key, + (context.entity, context.main_entity), + context.mesh_instance.current_uniform_index, + BinnedRenderPhaseType::mesh( + context.mesh_instance.should_batch(), + &context.gpu_preprocessing_support, + ), + context.current_change_tick, ); - let pipeline_id = match pipeline_id { - Ok(id) => id, - Err(err) => { - error!("{}", err); - continue; - } - }; - - view_specialized_material_pipeline_cache - .insert(*visible_entity, (ticks.this_run(), pipeline_id)); } } } -pub fn queue_prepass_material_meshes( - render_mesh_instances: Res, - render_materials: Res>, - render_material_instances: Res, - mesh_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<(&ExtractedView, &RenderVisibleEntities)>, - specialized_material_pipeline_cache: Res, -) { - for (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), - ); - - let Some(view_specialized_material_pipeline_cache) = - specialized_material_pipeline_cache.get(&extracted_view.retained_view_entity) - else { - continue; - }; +impl PhaseItemExt for Opaque3dDeferred { + type PhaseFamily = BinnedPhaseFamily; + type ExtractCondition = DeferredExtractCondition; + type RenderCommand = DrawPrepass; + const PHASE_TYPES: RenderPhaseType = RenderPhaseType::Opaque; +} - // 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; +impl QueueBinnedPhaseItem for Opaque3dDeferred { + #[inline] + fn queue_item(context: &PhaseContext, render_phase: &mut BinnedRenderPhase) + where + BPI: BinnedPhaseItem, + { + if let OpaqueRendererMethod::Deferred = context.material.properties.render_method { + let (vertex_slab, index_slab) = context + .mesh_allocator + .mesh_slabs(&context.mesh_instance.mesh_asset_id); + + render_phase.add( + OpaqueNoLightmap3dBatchSetKey { + draw_function: context.draw_function, + pipeline: context.pipeline_id, + material_bind_group_index: Some(context.material.binding.group.0), + vertex_slab: vertex_slab.unwrap_or_default(), + index_slab, + }, + OpaqueNoLightmap3dBinKey { + asset_id: context.mesh_instance.mesh_asset_id.into(), + }, + (context.entity, context.main_entity), + context.mesh_instance.current_uniform_index, + BinnedRenderPhaseType::mesh( + context.mesh_instance.should_batch(), + &context.gpu_preprocessing_support, + ), + context.current_change_tick, + ); } + } +} - for (render_entity, visible_entity) in visible_entities.iter::() { - let Some((current_change_tick, pipeline_id)) = - view_specialized_material_pipeline_cache.get(visible_entity) - else { - continue; - }; - - // Skip the entity if it's cached in a bin and up to date. - if opaque_phase.as_mut().is_some_and(|phase| { - phase.validate_cached_entity(*visible_entity, *current_change_tick) - }) || alpha_mask_phase.as_mut().is_some_and(|phase| { - phase.validate_cached_entity(*visible_entity, *current_change_tick) - }) || opaque_deferred_phase.as_mut().is_some_and(|phase| { - phase.validate_cached_entity(*visible_entity, *current_change_tick) - }) || alpha_mask_deferred_phase.as_mut().is_some_and(|phase| { - phase.validate_cached_entity(*visible_entity, *current_change_tick) - }) { - continue; - } +impl PhaseItemExt for AlphaMask3dDeferred { + type PhaseFamily = BinnedPhaseFamily; + type ExtractCondition = DeferredExtractCondition; + type RenderCommand = DrawPrepass; + const PHASE_TYPES: RenderPhaseType = RenderPhaseType::AlphaMask; +} - let Some(material_instance) = render_material_instances.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_instance.asset_id) else { - continue; +impl QueueBinnedPhaseItem for AlphaMask3dDeferred { + #[inline] + fn queue_item(context: &PhaseContext, render_phase: &mut BinnedRenderPhase) + where + BPI: BinnedPhaseItem, + { + if let OpaqueRendererMethod::Deferred = context.material.properties.render_method { + let (vertex_slab, index_slab) = context + .mesh_allocator + .mesh_slabs(&context.mesh_instance.mesh_asset_id); + + let batch_set_key = OpaqueNoLightmap3dBatchSetKey { + draw_function: context.draw_function, + pipeline: context.pipeline_id, + material_bind_group_index: Some(context.material.binding.group.0), + vertex_slab: vertex_slab.unwrap_or_default(), + index_slab, }; - let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id); - - let deferred = match material.properties.render_method { - OpaqueRendererMethod::Forward => false, - OpaqueRendererMethod::Deferred => true, - OpaqueRendererMethod::Auto => unreachable!(), + let bin_key = OpaqueNoLightmap3dBinKey { + asset_id: context.mesh_instance.mesh_asset_id.into(), }; - - match material.properties.render_phase_type { - RenderPhaseType::Opaque => { - if deferred { - let Some(draw_function) = material - .properties - .get_draw_function(DeferredOpaqueDrawFunction) - else { - continue; - }; - opaque_deferred_phase.as_mut().unwrap().add( - OpaqueNoLightmap3dBatchSetKey { - draw_function, - pipeline: *pipeline_id, - material_bind_group_index: Some(material.binding.group.0), - vertex_slab: vertex_slab.unwrap_or_default(), - index_slab, - }, - OpaqueNoLightmap3dBinKey { - asset_id: mesh_instance.mesh_asset_id.into(), - }, - (*render_entity, *visible_entity), - mesh_instance.current_uniform_index, - BinnedRenderPhaseType::mesh( - mesh_instance.should_batch(), - &gpu_preprocessing_support, - ), - *current_change_tick, - ); - } else if let Some(opaque_phase) = opaque_phase.as_mut() { - let (vertex_slab, index_slab) = - mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id); - let Some(draw_function) = material - .properties - .get_draw_function(PrepassOpaqueDrawFunction) - else { - continue; - }; - opaque_phase.add( - OpaqueNoLightmap3dBatchSetKey { - draw_function, - pipeline: *pipeline_id, - material_bind_group_index: Some(material.binding.group.0), - vertex_slab: vertex_slab.unwrap_or_default(), - index_slab, - }, - OpaqueNoLightmap3dBinKey { - asset_id: mesh_instance.mesh_asset_id.into(), - }, - (*render_entity, *visible_entity), - mesh_instance.current_uniform_index, - BinnedRenderPhaseType::mesh( - mesh_instance.should_batch(), - &gpu_preprocessing_support, - ), - *current_change_tick, - ); - } - } - RenderPhaseType::AlphaMask => { - if deferred { - let (vertex_slab, index_slab) = - mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id); - let Some(draw_function) = material - .properties - .get_draw_function(DeferredAlphaMaskDrawFunction) - else { - continue; - }; - let batch_set_key = OpaqueNoLightmap3dBatchSetKey { - draw_function, - 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_deferred_phase.as_mut().unwrap().add( - batch_set_key, - bin_key, - (*render_entity, *visible_entity), - mesh_instance.current_uniform_index, - BinnedRenderPhaseType::mesh( - mesh_instance.should_batch(), - &gpu_preprocessing_support, - ), - *current_change_tick, - ); - } else if let Some(alpha_mask_phase) = alpha_mask_phase.as_mut() { - let (vertex_slab, index_slab) = - mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id); - let Some(draw_function) = material - .properties - .get_draw_function(PrepassAlphaMaskDrawFunction) - else { - continue; - }; - let batch_set_key = OpaqueNoLightmap3dBatchSetKey { - draw_function, - 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), - mesh_instance.current_uniform_index, - BinnedRenderPhaseType::mesh( - mesh_instance.should_batch(), - &gpu_preprocessing_support, - ), - *current_change_tick, - ); - } - } - _ => {} - } + render_phase.add( + batch_set_key, + bin_key, + (context.entity, context.main_entity), + context.mesh_instance.current_uniform_index, + BinnedRenderPhaseType::mesh( + context.mesh_instance.should_batch(), + &context.gpu_preprocessing_support, + ), + context.current_change_tick, + ); } } } diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 9c95f0711493d..a4dca18f6b148 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -723,12 +723,11 @@ pub fn prepare_lights( ambient_light: Res, point_light_shadow_map: Res, directional_light_shadow_map: Res, - mut shadow_render_phases: ResMut>, - ( - mut max_directional_lights_warning_emitted, - mut max_cascades_per_light_warning_emitted, - mut live_shadow_mapping_lights, - ): (Local, Local, Local>), + mut shadow_render_phases: Query<&mut BinnedRenderPhase>, + (mut max_directional_lights_warning_emitted, mut max_cascades_per_light_warning_emitted): ( + Local, + Local, + ), point_lights: Query<( Entity, &MainEntity, @@ -1016,8 +1015,6 @@ pub fn prepare_lights( .gpu_clusterable_objects .write_buffer(&render_device, &render_queue); - live_shadow_mapping_lights.clear(); - let mut point_light_depth_attachments = HashMap::::default(); let mut directional_light_depth_attachments = HashMap::::default(); @@ -1361,9 +1358,13 @@ pub fn prepare_lights( if first { // Subsequent views with the same light entity will reuse the same shadow map - shadow_render_phases - .prepare_for_new_frame(retained_view_entity, gpu_preprocessing_mode); - live_shadow_mapping_lights.insert(retained_view_entity); + if let Ok(mut phase) = shadow_render_phases.get_mut(view_light_entity) { + phase.prepare_for_new_frame(); + } else { + commands + .entity(view_light_entity) + .insert(BinnedRenderPhase::::new(gpu_preprocessing_mode)); + } } } } @@ -1461,9 +1462,13 @@ pub fn prepare_lights( if first { // Subsequent views with the same light entity will reuse the same shadow map - shadow_render_phases - .prepare_for_new_frame(retained_view_entity, gpu_preprocessing_mode); - live_shadow_mapping_lights.insert(retained_view_entity); + if let Ok(mut phase) = shadow_render_phases.get_mut(view_light_entity) { + phase.prepare_for_new_frame(); + } else { + commands + .entity(view_light_entity) + .insert(BinnedRenderPhase::::new(gpu_preprocessing_mode)); + } } } @@ -1626,9 +1631,13 @@ pub fn prepare_lights( // Subsequent views with the same light entity will **NOT** reuse the same shadow map // (Because the cascades are unique to each view) // TODO: Implement GPU culling for shadow passes. - shadow_render_phases - .prepare_for_new_frame(retained_view_entity, gpu_preprocessing_mode); - live_shadow_mapping_lights.insert(retained_view_entity); + if let Ok(mut phase) = shadow_render_phases.get_mut(view_light_entity) { + phase.prepare_for_new_frame(); + } else { + commands + .entity(view_light_entity) + .insert(BinnedRenderPhase::::new(gpu_preprocessing_mode)); + } } } @@ -1666,8 +1675,6 @@ pub fn prepare_lights( despawn_entities(&mut commands, light_view_entities); } } - - shadow_render_phases.retain(|entity, _| live_shadow_mapping_lights.contains(entity)); } fn despawn_entities(commands: &mut Commands, entities: Vec) { @@ -1722,8 +1729,7 @@ pub struct SpecializedShadowMaterialViewPipelineCache { pub fn check_views_lights_need_specialization( view_lights: Query<&ViewLightEntities, With>, - view_light_entities: Query<(&LightEntity, &ExtractedView)>, - shadow_render_phases: Res>, + view_light_entities: Query<(&LightEntity, &ExtractedView), With>>, mut light_key_cache: ResMut, mut light_specialization_ticks: ResMut, ticks: SystemChangeTick, @@ -1735,9 +1741,6 @@ pub fn check_views_lights_need_specialization( 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; @@ -1767,12 +1770,11 @@ pub fn specialize_shadows( Res>, Res, ), - shadow_render_phases: Res>, - mut pipelines: ResMut>, + mut pipelines: ResMut>, pipeline_cache: Res, render_lightmaps: Res, view_lights: Query<(Entity, &ViewLightEntities), With>, - view_light_entities: Query<(&LightEntity, &ExtractedView)>, + view_light_entities: Query<(&LightEntity, &ExtractedView), With>>, point_light_entities: Query<&RenderCubemapVisibleEntities, With>, directional_light_entities: Query< &RenderCascadesVisibleEntities, @@ -1799,9 +1801,6 @@ pub fn specialize_shadows( all_shadow_views.insert(extracted_view_light.retained_view_entity); - if !shadow_render_phases.contains_key(&extracted_view_light.retained_view_entity) { - continue; - } let Some(light_key) = light_key_cache.get(&extracted_view_light.retained_view_entity) else { continue; @@ -1910,6 +1909,7 @@ pub fn specialize_shadows( mesh_key, material_key: material.properties.material_key.clone(), type_id: material_instance.asset_id.type_id(), + pass_id: PassId::of::(), }; let material_pipeline_specializer = PrepassPipelineSpecializer { pipeline: prepass_pipeline.clone(), @@ -1946,11 +1946,10 @@ pub fn queue_shadows( render_mesh_instances: Res, render_materials: Res>, render_material_instances: Res, - mut shadow_render_phases: ResMut>, gpu_preprocessing_support: Res, mesh_allocator: Res, view_lights: Query<(Entity, &ViewLightEntities, Option<&RenderLayers>), With>, - view_light_entities: Query<(&LightEntity, &ExtractedView)>, + mut view_light_entities: Query<(&LightEntity, &ExtractedView, &mut BinnedRenderPhase)>, point_light_entities: Query<&RenderCubemapVisibleEntities, With>, directional_light_entities: Query< &RenderCascadesVisibleEntities, @@ -1961,13 +1960,8 @@ pub fn queue_shadows( ) { for (entity, view_lights, camera_layers) 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) + let Ok((light_entity, extracted_view_light, mut shadow_phase)) = + view_light_entities.get_mut(view_light_entity) else { continue; }; @@ -2221,7 +2215,11 @@ pub struct ShadowPassNode { /// The query that finds cameras in which shadows are visible. main_view_query: QueryState>, /// The query that finds shadow cascades. - view_light_query: QueryState<(Read, Read, Has)>, + view_light_query: QueryState<( + Read, + Has, + Read>, + )>, } impl FromWorld for EarlyShadowPassNode { @@ -2292,14 +2290,9 @@ impl ShadowPassNode { world: &'w World, is_late: bool, ) -> Result<(), NodeRunError> { - let Some(shadow_render_phases) = world.get_resource::>() - else { - return Ok(()); - }; - if let Ok(view_lights) = self.main_view_query.get_manual(world, graph.view_entity()) { for view_light_entity in view_lights.lights.iter().copied() { - let Ok((view_light, extracted_light_view, occlusion_culling)) = + let Ok((view_light, occlusion_culling, shadow_phase)) = self.view_light_query.get_manual(world, view_light_entity) else { continue; @@ -2311,12 +2304,6 @@ impl ShadowPassNode { continue; } - let Some(shadow_phase) = - shadow_render_phases.get(&extracted_light_view.retained_view_entity) - else { - continue; - }; - let depth_stencil_attachment = Some(view_light.depth_attachment.get_attachment(StoreOp::Store)); diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index e0243899f6b8d..dcaa63f6e3edb 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -6,11 +6,10 @@ use bevy_asset::{embedded_asset, load_embedded_asset, AssetId}; use bevy_camera::{ primitives::Aabb, visibility::{NoFrustumCulling, RenderLayers, ViewVisibility, VisibilityRange}, - Camera, Camera3d, Projection, + Camera, }; use bevy_core_pipeline::{ - core_3d::{AlphaMask3d, Opaque3d, Transmissive3d, Transparent3d, CORE_3D_DEPTH_FORMAT}, - deferred::{AlphaMask3dDeferred, Opaque3dDeferred}, + core_3d::CORE_3D_DEPTH_FORMAT, oit::{prepare_oit_buffers, OrderIndependentTransparencySettingsOffset}, prepass::MotionVectorPrepass, }; @@ -24,10 +23,7 @@ use bevy_ecs::{ system::{lifetimeless::*, SystemParamItem, SystemState}, }; use bevy_image::{BevyDefault, ImageSampler, TextureFormatPixelInfo}; -use bevy_light::{ - EnvironmentMapLight, IrradianceVolume, NotShadowCaster, NotShadowReceiver, - ShadowFilteringMethod, TransmittedShadowReceiver, -}; +use bevy_light::{NotShadowCaster, NotShadowReceiver, TransmittedShadowReceiver}; use bevy_math::{Affine3, Rect, UVec2, Vec3, Vec4}; use bevy_mesh::{ skinning::SkinnedMesh, BaseMeshPipelineKey, Mesh, Mesh3d, MeshTag, MeshVertexBufferLayoutRef, @@ -46,17 +42,14 @@ use bevy_render::{ mesh::{allocator::MeshAllocator, RenderMesh, RenderMeshBufferInfo}, render_asset::RenderAssets, render_phase::{ - BinnedRenderPhasePlugin, InputUniformIndex, PhaseItem, PhaseItemExtraIndex, RenderCommand, - RenderCommandResult, SortedRenderPhasePlugin, TrackedRenderPass, + InputUniformIndex, PhaseItem, PhaseItemExtraIndex, RenderCommand, RenderCommandResult, + TrackedRenderPass, }, render_resource::*, renderer::{RenderAdapter, RenderDevice, RenderQueue}, sync_world::MainEntityHashSet, texture::{DefaultImageSampler, GpuImage}, - view::{ - self, NoIndirectDrawing, RenderVisibilityRanges, RetainedViewEntity, ViewTarget, - ViewUniformOffset, - }, + view::{self, NoIndirectDrawing, RenderVisibilityRanges, ViewTarget, ViewUniformOffset}, Extract, }; use bevy_shader::{load_shader_library, Shader, ShaderDefVal, ShaderSettings}; @@ -78,17 +71,7 @@ use crate::{ }, *, }; -use bevy_core_pipeline::oit::OrderIndependentTransparencySettings; -use bevy_core_pipeline::prepass::{DeferredPrepass, DepthPrepass, NormalPrepass}; -use bevy_core_pipeline::tonemapping::{DebandDither, Tonemapping}; -use bevy_ecs::change_detection::Tick; -use bevy_ecs::system::SystemChangeTick; -use bevy_render::camera::TemporalJitter; -use bevy_render::prelude::Msaa; use bevy_render::sync_world::{MainEntity, MainEntityHashMap}; -use bevy_render::view::ExtractedView; -use bevy_render::RenderSystems::PrepareAssets; - use bytemuck::{Pod, Zeroable}; use nonmax::{NonMaxU16, NonMaxU32}; use smallvec::{smallvec, SmallVec}; @@ -158,16 +141,7 @@ impl Plugin for MeshRenderPlugin { app.add_systems( PostUpdate, (no_automatic_skin_batching, no_automatic_morph_batching), - ) - .add_plugins(( - BinnedRenderPhasePlugin::::new(self.debug_flags), - BinnedRenderPhasePlugin::::new(self.debug_flags), - BinnedRenderPhasePlugin::::new(self.debug_flags), - BinnedRenderPhasePlugin::::new(self.debug_flags), - BinnedRenderPhasePlugin::::new(self.debug_flags), - SortedRenderPhasePlugin::::new(self.debug_flags), - SortedRenderPhasePlugin::::new(self.debug_flags), - )); + ); if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app @@ -214,14 +188,8 @@ impl Plugin for MeshRenderPlugin { if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app - .init_resource::() - .init_resource::() .init_resource::() - .init_resource::() - .add_systems( - Render, - check_views_need_specialization.in_set(PrepareAssets), - ); + .init_resource::(); let gpu_preprocessing_support = render_app.world().resource::(); @@ -300,151 +268,6 @@ impl Plugin for MeshRenderPlugin { } } -#[derive(Resource, Deref, DerefMut, Default, Debug, Clone)] -pub struct ViewKeyCache(HashMap); - -#[derive(Resource, Deref, DerefMut, Default, Debug, Clone)] -pub struct ViewSpecializationTicks(HashMap); - -pub fn check_views_need_specialization( - mut view_key_cache: ResMut, - mut view_specialization_ticks: ResMut, - mut views: Query<( - &ExtractedView, - &Msaa, - Option<&Tonemapping>, - Option<&DebandDither>, - Option<&ShadowFilteringMethod>, - Has, - ( - Has, - Has, - Has, - Has, - ), - Option<&Camera3d>, - Has, - Option<&Projection>, - Has, - ( - Has>, - Has>, - ), - Has, - Has, - )>, - ticks: SystemChangeTick, -) { - for ( - 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, - has_atmosphere, - ) in views.iter_mut() - { - 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 has_atmosphere { - view_key |= MeshPipelineKey::ATMOSPHERE; - } - - if view.invert_culling { - view_key |= MeshPipelineKey::INVERT_CULLING; - } - - 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, - ); - } - if !view_key_cache - .get_mut(&view.retained_view_entity) - .is_some_and(|current_key| *current_key == view_key) - { - view_key_cache.insert(view.retained_view_entity, view_key); - view_specialization_ticks.insert(view.retained_view_entity, ticks.this_run()); - } - } -} - #[derive(Component)] pub struct MeshTransforms { pub world_from_local: Affine3, @@ -2763,7 +2586,7 @@ pub enum MeshBindGroups { /// The bind groups for the meshes for the entire scene, if GPU mesh /// preprocessing isn't in use. CpuPreprocessing(MeshPhaseBindGroups), - /// A mapping from the type ID of a phase (e.g. [`Opaque3d`]) to the mesh + /// A mapping from the type ID of a phase (e.g. [`bevy_core_pipeline::core_3d::Opaque3d`]) to the mesh /// bind groups for that phase. GpuPreprocessing(TypeIdMap), } diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index c4b19df63f09f..f4884b8c5fa4b 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -1,5 +1,5 @@ use crate::{ - DrawMesh, MeshPipeline, MeshPipelineKey, RenderLightmaps, RenderMeshInstanceFlags, + DrawMesh, MainPass, MeshPipeline, MeshPipelineKey, RenderLightmaps, RenderMeshInstanceFlags, RenderMeshInstances, SetMeshBindGroup, SetMeshViewBindGroup, SetMeshViewBindingArrayBindGroup, ViewKeyCache, ViewSpecializationTicks, }; @@ -39,14 +39,14 @@ use bevy_render::{ }, render_graph::{NodeRunError, RenderGraphContext, RenderGraphExt, ViewNode, ViewNodeRunner}, render_phase::{ - AddRenderCommand, BinnedPhaseItem, BinnedRenderPhasePlugin, BinnedRenderPhaseType, - CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, PhaseItem, - PhaseItemBatchSetKey, PhaseItemExtraIndex, RenderCommand, RenderCommandResult, - SetItemPipeline, TrackedRenderPass, ViewBinnedRenderPhases, + AddRenderCommand, BinnedPhaseItem, BinnedRenderPhase, BinnedRenderPhasePlugin, + BinnedRenderPhaseType, CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, + PhaseItem, PhaseItemBatchSetKey, PhaseItemExtraIndex, RenderCommand, RenderCommandResult, + SetItemPipeline, TrackedRenderPass, }, render_resource::*, renderer::{RenderContext, RenderDevice}, - sync_world::{MainEntity, MainEntityHashMap}, + sync_world::{MainEntity, MainEntityHashMap, RenderEntity}, view::{ ExtractedView, NoIndirectDrawing, RenderVisibilityRanges, RenderVisibleEntities, RetainedViewEntity, ViewDepthTexture, ViewTarget, @@ -372,27 +372,18 @@ struct Wireframe3dNode; impl ViewNode for Wireframe3dNode { type ViewQuery = ( &'static ExtractedCamera, - &'static ExtractedView, &'static ViewTarget, &'static ViewDepthTexture, + &'static BinnedRenderPhase, ); fn run<'w>( &self, graph: &mut RenderGraphContext, render_context: &mut RenderContext<'w>, - (camera, view, target, depth): QueryItem<'w, '_, Self::ViewQuery>, + (camera, target, depth, wireframe_phase): QueryItem<'w, '_, Self::ViewQuery>, world: &'w World, ) -> Result<(), NodeRunError> { - let Some(wireframe_phase) = world.get_resource::>() - else { - return Ok(()); - }; - - let Some(wireframe_phase) = wireframe_phase.get(&view.retained_view_entity) else { - return Ok(()); - }; - let diagnostics = render_context.diagnostic_recorder(); let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { @@ -676,29 +667,35 @@ fn get_wireframe_material( } fn extract_wireframe_3d_camera( - mut wireframe_3d_phases: ResMut>, - cameras: Extract), With>>, - mut live_entities: Local>, + mut commands: Commands, + cameras: Extract), With>>, + mut phases: Query<&mut BinnedRenderPhase>, gpu_preprocessing_support: Res, ) { - live_entities.clear(); - for (main_entity, camera, no_indirect_drawing) in &cameras { + for (entity, camera, no_indirect_drawing) in &cameras { if !camera.is_active { + commands + .entity(entity) + .remove::>(); continue; } - let gpu_preprocessing_mode = gpu_preprocessing_support.min(if !no_indirect_drawing { - GpuPreprocessingMode::Culling + + if let Ok(mut phase) = phases.get_mut(entity) { + phase.prepare_for_new_frame(); } else { - GpuPreprocessingMode::PreprocessingOnly - }); + let gpu_preprocessing_mode = gpu_preprocessing_support.min(if !no_indirect_drawing { + GpuPreprocessingMode::Culling + } else { + GpuPreprocessingMode::PreprocessingOnly + }); - let retained_view_entity = RetainedViewEntity::new(main_entity.into(), None, 0); - wireframe_3d_phases.prepare_for_new_frame(retained_view_entity, gpu_preprocessing_mode); - live_entities.insert(retained_view_entity); + commands + .entity(entity) + .insert(BinnedRenderPhase::::new( + gpu_preprocessing_mode, + )); + } } - - // Clear out all dead views. - wireframe_3d_phases.retain(|camera_entity, _| live_entities.contains(camera_entity)); } pub fn extract_wireframe_entities_needing_specialization( @@ -748,11 +745,10 @@ pub fn specialize_wireframes( render_mesh_instances: Res, render_wireframe_instances: Res, render_visibility_ranges: Res, - wireframe_phases: Res>, - views: Query<(&ExtractedView, &RenderVisibleEntities)>, - view_key_cache: Res, + views: Query<(&ExtractedView, &RenderVisibleEntities), With>>, + view_key_cache: Res>, entity_specialization_ticks: Res, - view_specialization_ticks: Res, + view_specialization_ticks: Res>, mut specialized_material_pipeline_cache: ResMut, mut pipelines: ResMut>, pipeline: Res, @@ -767,10 +763,6 @@ pub fn specialize_wireframes( for (view, visible_entities) in &views { all_views.insert(view.retained_view_entity); - if !wireframe_phases.contains_key(&view.retained_view_entity) { - continue; - } - let Some(view_key) = view_key_cache.get(&view.retained_view_entity) else { continue; }; @@ -867,13 +859,13 @@ fn queue_wireframes( mesh_allocator: Res, specialized_wireframe_pipeline_cache: Res, render_wireframe_instances: Res, - mut wireframe_3d_phases: ResMut>, - mut views: Query<(&ExtractedView, &RenderVisibleEntities)>, + mut views: Query<( + &ExtractedView, + &RenderVisibleEntities, + &mut BinnedRenderPhase, + )>, ) { - for (view, visible_entities) in &mut views { - let Some(wireframe_phase) = wireframe_3d_phases.get_mut(&view.retained_view_entity) else { - continue; - }; + for (view, visible_entities, mut wireframe_phase) in &mut views { let draw_wireframe = custom_draw_functions.read().id::(); let Some(view_specialized_material_pipeline_cache) = diff --git a/crates/bevy_render/src/batching/gpu_preprocessing.rs b/crates/bevy_render/src/batching/gpu_preprocessing.rs index 21780b222797d..f20a2b2e82568 100644 --- a/crates/bevy_render/src/batching/gpu_preprocessing.rs +++ b/crates/bevy_render/src/batching/gpu_preprocessing.rs @@ -26,11 +26,10 @@ use wgpu::{BindingResource, BufferUsages, DownlevelFlags, Features}; use crate::{ experimental::occlusion_culling::OcclusionCulling, render_phase::{ - BinnedPhaseItem, BinnedRenderPhaseBatch, BinnedRenderPhaseBatchSet, + BinnedPhaseItem, BinnedRenderPhase, BinnedRenderPhaseBatch, BinnedRenderPhaseBatchSet, BinnedRenderPhaseBatchSets, CachedRenderPipelinePhaseItem, PhaseItem, PhaseItemBatchSetKey as _, PhaseItemExtraIndex, RenderBin, SortedPhaseItem, - SortedRenderPhase, UnbatchableBinnedEntityIndices, ViewBinnedRenderPhases, - ViewSortedRenderPhases, + SortedRenderPhase, UnbatchableBinnedEntityIndices, }, render_resource::{Buffer, GpuArrayBufferable, RawBufferVec, UninitBufferVec}, renderer::{RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue, WgpuWrapper}, @@ -1335,9 +1334,9 @@ pub fn delete_old_work_item_buffers( pub fn batch_and_prepare_sorted_render_phase( mut phase_batched_instance_buffers: ResMut>, mut phase_indirect_parameters_buffers: ResMut>, - mut sorted_render_phases: ResMut>, mut views: Query<( &ExtractedView, + &mut SortedRenderPhase, Has, Has, )>, @@ -1354,11 +1353,8 @@ pub fn batch_and_prepare_sorted_render_phase( ref mut late_non_indexed_indirect_parameters_buffer, } = phase_batched_instance_buffers.buffers; - for (extracted_view, no_indirect_drawing, gpu_occlusion_culling) in &mut views { - let Some(phase) = sorted_render_phases.get_mut(&extracted_view.retained_view_entity) else { - continue; - }; - + for (extracted_view, mut phase, no_indirect_drawing, gpu_occlusion_culling) in &mut views { + let phase = phase.as_mut(); // Create the work item buffer if necessary. let work_item_buffer = get_or_create_work_item_buffer::( work_item_buffers, @@ -1504,10 +1500,10 @@ pub fn batch_and_prepare_sorted_render_phase( pub fn batch_and_prepare_binned_render_phase( mut phase_batched_instance_buffers: ResMut>, phase_indirect_parameters_buffers: ResMut>, - mut binned_render_phases: ResMut>, mut views: Query< ( &ExtractedView, + &mut BinnedRenderPhase, Has, Has, ), @@ -1529,11 +1525,8 @@ pub fn batch_and_prepare_binned_render_phase( ref mut late_non_indexed_indirect_parameters_buffer, } = phase_batched_instance_buffers.buffers; - for (extracted_view, no_indirect_drawing, gpu_occlusion_culling) in &mut views { - let Some(phase) = binned_render_phases.get_mut(&extracted_view.retained_view_entity) else { - continue; - }; - + for (extracted_view, mut phase, no_indirect_drawing, gpu_occlusion_culling) in &mut views { + let phase = phase.as_mut(); // Create the work item buffer if necessary; otherwise, just mark it as // used this frame. let work_item_buffer = get_or_create_work_item_buffer::( diff --git a/crates/bevy_render/src/batching/mod.rs b/crates/bevy_render/src/batching/mod.rs index bceca626417cd..afa21768a5615 100644 --- a/crates/bevy_render/src/batching/mod.rs +++ b/crates/bevy_render/src/batching/mod.rs @@ -1,7 +1,7 @@ use bevy_ecs::{ component::Component, entity::Entity, - system::{ResMut, SystemParam, SystemParamItem}, + system::{Query, SystemParam, SystemParamItem}, }; use bytemuck::Pod; use gpu_preprocessing::UntypedPhaseIndirectParametersBuffers; @@ -9,8 +9,8 @@ use nonmax::NonMaxU32; use crate::{ render_phase::{ - BinnedPhaseItem, CachedRenderPipelinePhaseItem, DrawFunctionId, PhaseItemExtraIndex, - SortedPhaseItem, SortedRenderPhase, ViewBinnedRenderPhases, + BinnedPhaseItem, BinnedRenderPhase, CachedRenderPipelinePhaseItem, DrawFunctionId, + PhaseItemExtraIndex, SortedPhaseItem, SortedRenderPhase, }, render_resource::{CachedRenderPipelineId, GpuArrayBufferable}, sync_world::MainEntity, @@ -179,11 +179,11 @@ pub trait GetFullBatchData: GetBatchData { } /// Sorts a render phase that uses bins. -pub fn sort_binned_render_phase(mut phases: ResMut>) +pub fn sort_binned_render_phase(mut views: Query<&mut BinnedRenderPhase>) where BPI: BinnedPhaseItem, { - for phase in phases.values_mut() { + for mut phase in views.iter_mut() { phase.multidrawable_meshes.sort_unstable_keys(); phase.batchable_meshes.sort_unstable_keys(); phase.unbatchable_meshes.sort_unstable_keys(); diff --git a/crates/bevy_render/src/batching/no_gpu_preprocessing.rs b/crates/bevy_render/src/batching/no_gpu_preprocessing.rs index 66ba0783758d7..4fcf224e2bb08 100644 --- a/crates/bevy_render/src/batching/no_gpu_preprocessing.rs +++ b/crates/bevy_render/src/batching/no_gpu_preprocessing.rs @@ -3,16 +3,16 @@ use bevy_derive::{Deref, DerefMut}; use bevy_ecs::entity::Entity; use bevy_ecs::resource::Resource; -use bevy_ecs::system::{Res, ResMut, StaticSystemParam}; +use bevy_ecs::system::{Query, Res, ResMut, StaticSystemParam}; use smallvec::{smallvec, SmallVec}; use tracing::error; use wgpu::{BindingResource, Limits}; +use crate::render_phase::{BinnedRenderPhase, SortedRenderPhase}; use crate::{ render_phase::{ BinnedPhaseItem, BinnedRenderPhaseBatch, BinnedRenderPhaseBatchSets, CachedRenderPipelinePhaseItem, PhaseItemExtraIndex, SortedPhaseItem, - ViewBinnedRenderPhases, ViewSortedRenderPhases, }, render_resource::{GpuArrayBuffer, GpuArrayBufferable}, renderer::{RenderDevice, RenderQueue}, @@ -65,7 +65,7 @@ pub fn clear_batched_cpu_instance_buffers( /// and trying to combine the draws into a batch. pub fn batch_and_prepare_sorted_render_phase( batched_instance_buffer: ResMut>, - mut phases: ResMut>, + mut views: Query<&mut SortedRenderPhase>, param: StaticSystemParam, ) where I: CachedRenderPipelinePhaseItem + SortedPhaseItem, @@ -76,8 +76,8 @@ pub fn batch_and_prepare_sorted_render_phase( // We only process CPU-built batch data in this function. let batched_instance_buffer = batched_instance_buffer.into_inner(); - for phase in phases.values_mut() { - super::batch_and_prepare_sorted_render_phase::(phase, |item| { + for mut phase in views.iter_mut() { + super::batch_and_prepare_sorted_render_phase::(&mut phase, |item| { let (buffer_data, compare_data) = GBD::get_batch_data(&system_param_item, (item.entity(), item.main_entity()))?; let buffer_index = batched_instance_buffer.push(buffer_data); @@ -96,7 +96,7 @@ pub fn batch_and_prepare_sorted_render_phase( /// building isn't in use. pub fn batch_and_prepare_binned_render_phase( gpu_array_buffer: ResMut>, - mut phases: ResMut>, + mut views: Query<&mut BinnedRenderPhase>, param: StaticSystemParam, ) where BPI: BinnedPhaseItem, @@ -105,9 +105,9 @@ pub fn batch_and_prepare_binned_render_phase( let gpu_array_buffer = gpu_array_buffer.into_inner(); let system_param_item = param.into_inner(); - for phase in phases.values_mut() { + for mut phase in views.iter_mut() { + let phase = phase.as_mut(); // Prepare batchables. - for bin in phase.batchable_meshes.values_mut() { let mut batch_set: SmallVec<[BinnedRenderPhaseBatch; 1]> = smallvec![]; for main_entity in bin.entities().keys() { diff --git a/crates/bevy_render/src/render_phase/draw.rs b/crates/bevy_render/src/render_phase/draw.rs index 494074df4e43b..7ca3daa986480 100644 --- a/crates/bevy_render/src/render_phase/draw.rs +++ b/crates/bevy_render/src/render_phase/draw.rs @@ -54,6 +54,12 @@ pub enum DrawError { #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)] pub struct DrawFunctionId(u32); +impl DrawFunctionId { + /// An draw function ID with a placeholder value. This may or may not correspond to an actual draw function, + /// and should be overwritten by a new value before being used. + pub const PLACEHOLDER: Self = Self(u32::MAX); +} + /// Stores all [`Draw`] functions for the [`PhaseItem`] type. /// /// For retrieval, the [`Draw`] functions are mapped to their respective [`TypeId`]s. diff --git a/crates/bevy_render/src/render_phase/mod.rs b/crates/bevy_render/src/render_phase/mod.rs index ea9deb540284a..3470449803a28 100644 --- a/crates/bevy_render/src/render_phase/mod.rs +++ b/crates/bevy_render/src/render_phase/mod.rs @@ -32,7 +32,6 @@ use bevy_app::{App, Plugin}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::change_detection::Tick; use bevy_ecs::entity::EntityHash; -use bevy_platform::collections::{hash_map::Entry, HashMap}; use bevy_utils::default; pub use draw::*; pub use draw_state::*; @@ -49,7 +48,6 @@ use crate::batching::gpu_preprocessing::{ }; use crate::renderer::RenderDevice; use crate::sync_world::{MainEntity, MainEntityHashMap}; -use crate::view::RetainedViewEntity; use crate::RenderDebugFlags; use crate::{ batching::{ @@ -98,16 +96,6 @@ define_label!( pub type InternedDrawFunctionLabel = Interned; -/// Stores the rendering instructions for a single phase that uses bins in all -/// views. -/// -/// They're cleared out every frame, but storing them in a resource like this -/// allows us to reuse allocations. -#[derive(Resource, Deref, DerefMut)] -pub struct ViewBinnedRenderPhases(pub HashMap>) -where - BPI: BinnedPhaseItem; - /// A collection of all rendering instructions, that will be executed by the GPU, for a /// single render phase for a single view. /// @@ -122,6 +110,7 @@ where /// This flavor of render phase is used for phases in which the ordering is less /// critical: for example, `Opaque3d`. It's generally faster than the /// alternative [`SortedRenderPhase`]. +#[derive(Component)] pub struct BinnedRenderPhase where BPI: BinnedPhaseItem, @@ -444,33 +433,6 @@ where } } -impl Default for ViewBinnedRenderPhases -where - BPI: BinnedPhaseItem, -{ - fn default() -> Self { - Self(default()) - } -} - -impl ViewBinnedRenderPhases -where - BPI: BinnedPhaseItem, -{ - pub fn prepare_for_new_frame( - &mut self, - retained_view_entity: RetainedViewEntity, - gpu_preprocessing: GpuPreprocessingMode, - ) { - match self.entry(retained_view_entity) { - Entry::Occupied(mut entry) => entry.get_mut().prepare_for_new_frame(), - Entry::Vacant(entry) => { - entry.insert(BinnedRenderPhase::::new(gpu_preprocessing)); - } - } - } -} - /// The index of the uniform describing this object in the GPU buffer, when GPU /// preprocessing is enabled. /// @@ -1052,7 +1014,7 @@ impl BinnedRenderPhase where BPI: BinnedPhaseItem, { - fn new(gpu_preprocessing: GpuPreprocessingMode) -> Self { + pub fn new(gpu_preprocessing: GpuPreprocessingMode) -> Self { Self { multidrawable_meshes: IndexMap::default(), batchable_meshes: IndexMap::default(), @@ -1156,7 +1118,6 @@ where }; render_app - .init_resource::>() .init_resource::>() .insert_resource(PhaseIndirectParametersBuffers::::new( self.debug_flags @@ -1190,39 +1151,6 @@ where } } -/// Stores the rendering instructions for a single phase that sorts items in all -/// views. -/// -/// They're cleared out every frame, but storing them in a resource like this -/// allows us to reuse allocations. -#[derive(Resource, Deref, DerefMut)] -pub struct ViewSortedRenderPhases(pub HashMap>) -where - SPI: SortedPhaseItem; - -impl Default for ViewSortedRenderPhases -where - SPI: SortedPhaseItem, -{ - fn default() -> Self { - Self(default()) - } -} - -impl ViewSortedRenderPhases -where - SPI: SortedPhaseItem, -{ - pub fn insert_or_clear(&mut self, retained_view_entity: RetainedViewEntity) { - match self.entry(retained_view_entity) { - Entry::Occupied(mut entry) => entry.get_mut().clear(), - Entry::Vacant(entry) => { - entry.insert(default()); - } - } - } -} - /// A convenient abstraction for adding all the systems necessary for a sorted /// render phase to the render app. /// @@ -1262,7 +1190,6 @@ where }; render_app - .init_resource::>() .init_resource::>() .insert_resource(PhaseIndirectParametersBuffers::::new( self.debug_flags @@ -1404,6 +1331,7 @@ impl UnbatchableBinnedEntityIndexSet { /// This flavor of render phase is used only for meshes that need to be sorted /// back-to-front, such as transparent meshes. For items that don't need strict /// sorting, [`BinnedRenderPhase`] is preferred, for performance. +#[derive(Component)] pub struct SortedRenderPhase where I: SortedPhaseItem, @@ -1751,11 +1679,11 @@ impl RenderCommand

for SetItemPipeline { /// This system sorts the [`PhaseItem`]s of all [`SortedRenderPhase`]s of this /// type. -pub fn sort_phase_system(mut render_phases: ResMut>) +pub fn sort_phase_system(mut views: Query<&mut SortedRenderPhase>) where I: SortedPhaseItem, { - for phase in render_phases.values_mut() { + for mut phase in views.iter_mut() { phase.sort(); } } @@ -1763,11 +1691,11 @@ where /// Removes entities that became invisible or changed phases from the bins. /// /// This must run after queuing. -pub fn sweep_old_entities(mut render_phases: ResMut>) +pub fn sweep_old_entities(mut views: Query<&mut BinnedRenderPhase>) where BPI: BinnedPhaseItem, { - for phase in render_phases.0.values_mut() { + for mut phase in views.iter_mut() { phase.sweep_old_entities(); } } diff --git a/crates/bevy_sprite_render/src/mesh2d/material.rs b/crates/bevy_sprite_render/src/mesh2d/material.rs index d4f64fa87acc0..e00b6a4746476 100644 --- a/crates/bevy_sprite_render/src/mesh2d/material.rs +++ b/crates/bevy_sprite_render/src/mesh2d/material.rs @@ -24,6 +24,7 @@ use bevy_math::FloatOrd; use bevy_mesh::MeshVertexBufferLayoutRef; use bevy_platform::collections::HashMap; use bevy_reflect::{prelude::ReflectDefault, Reflect}; +use bevy_render::render_phase::{BinnedRenderPhase, SortedRenderPhase}; use bevy_render::render_resource::BindGroupLayoutDescriptor; use bevy_render::{ camera::extract_cameras, @@ -34,7 +35,7 @@ use bevy_render::{ render_phase::{ AddRenderCommand, BinnedRenderPhaseType, DrawFunctionId, DrawFunctions, InputUniformIndex, PhaseItem, PhaseItemExtraIndex, RenderCommand, RenderCommandResult, SetItemPipeline, - TrackedRenderPass, ViewBinnedRenderPhases, ViewSortedRenderPhases, + TrackedRenderPass, }, render_resource::{ AsBindGroup, AsBindGroupError, BindGroup, BindGroupId, BindingResources, @@ -698,10 +699,15 @@ pub fn specialize_material2d_meshes( ), mut render_mesh_instances: ResMut, render_material_instances: Res>, - transparent_render_phases: Res>, - opaque_render_phases: Res>, - alpha_mask_render_phases: Res>, - views: Query<(&MainEntity, &ExtractedView, &RenderVisibleEntities)>, + views: Query< + (&MainEntity, &RenderVisibleEntities), + ( + With, + With>, + With>, + With>, + ), + >, view_key_cache: Res, entity_specialization_ticks: Res>, view_specialization_ticks: Res, @@ -714,14 +720,7 @@ pub fn specialize_material2d_meshes( return; } - 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) - { - continue; - } - + for (view_entity, visible_entities) in &views { let Some(view_key) = view_key_cache.get(view_entity) else { continue; }; @@ -793,10 +792,13 @@ pub fn queue_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)>, + mut views: Query<( + &MainEntity, + &RenderVisibleEntities, + &mut BinnedRenderPhase, + &mut BinnedRenderPhase, + &mut SortedRenderPhase, + )>, specialized_material_pipeline_cache: ResMut>, ) where M::Data: PartialEq + Eq + Hash + Clone, @@ -805,25 +807,20 @@ pub fn queue_material2d_meshes( return; } - for (view_entity, view, visible_entities) in &views { + for ( + view_entity, + visible_entities, + mut opaque_phase, + mut alpha_mask_phase, + mut transparent_phase, + ) in views.iter_mut() + { let Some(view_specialized_material_pipeline_cache) = specialized_material_pipeline_cache.get(view_entity) else { continue; }; - 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((current_change_tick, pipeline_id)) = view_specialized_material_pipeline_cache .get(visible_entity) diff --git a/crates/bevy_sprite_render/src/mesh2d/wireframe2d.rs b/crates/bevy_sprite_render/src/mesh2d/wireframe2d.rs index 5f9a0a0e6e54d..c8b69cf3f88c1 100644 --- a/crates/bevy_sprite_render/src/mesh2d/wireframe2d.rs +++ b/crates/bevy_sprite_render/src/mesh2d/wireframe2d.rs @@ -38,14 +38,14 @@ use bevy_render::{ }, render_graph::{NodeRunError, RenderGraphContext, RenderGraphExt, ViewNode, ViewNodeRunner}, render_phase::{ - AddRenderCommand, BinnedPhaseItem, BinnedRenderPhasePlugin, BinnedRenderPhaseType, - CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, InputUniformIndex, PhaseItem, - PhaseItemBatchSetKey, PhaseItemExtraIndex, RenderCommand, RenderCommandResult, - SetItemPipeline, TrackedRenderPass, ViewBinnedRenderPhases, + AddRenderCommand, BinnedPhaseItem, BinnedRenderPhase, BinnedRenderPhasePlugin, + BinnedRenderPhaseType, CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, + InputUniformIndex, PhaseItem, PhaseItemBatchSetKey, PhaseItemExtraIndex, RenderCommand, + RenderCommandResult, SetItemPipeline, TrackedRenderPass, }, render_resource::*, renderer::RenderContext, - sync_world::{MainEntity, MainEntityHashMap}, + sync_world::{MainEntity, MainEntityHashMap, RenderEntity}, view::{ ExtractedView, RenderVisibleEntities, RetainedViewEntity, ViewDepthTexture, ViewTarget, }, @@ -360,28 +360,18 @@ struct Wireframe2dNode; impl ViewNode for Wireframe2dNode { type ViewQuery = ( &'static ExtractedCamera, - &'static ExtractedView, &'static ViewTarget, &'static ViewDepthTexture, + &'static BinnedRenderPhase, ); fn run<'w>( &self, graph: &mut RenderGraphContext, render_context: &mut RenderContext<'w>, - (camera, view, target, depth): QueryItem<'w, '_, Self::ViewQuery>, + (camera, target, depth, wireframe_phase): QueryItem<'w, '_, Self::ViewQuery>, world: &'w World, ) -> Result<(), NodeRunError> { - let Some(wireframe_phase) = - world.get_resource::>() - else { - return Ok(()); - }; - - let Some(wireframe_phase) = wireframe_phase.get(&view.retained_view_entity) else { - return Ok(()); - }; - let diagnostics = render_context.diagnostic_recorder(); let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { @@ -665,22 +655,27 @@ fn get_wireframe_material( } fn extract_wireframe_2d_camera( - mut wireframe_2d_phases: ResMut>, - cameras: Extract>>, - mut live_entities: Local>, + mut commands: Commands, + cameras: Extract>>, + mut phases: Query<&mut BinnedRenderPhase>, ) { - live_entities.clear(); - for (main_entity, camera) in &cameras { + for (entity, camera) in &cameras { if !camera.is_active { + commands + .entity(entity) + .remove::>(); continue; } - let retained_view_entity = RetainedViewEntity::new(main_entity.into(), None, 0); - wireframe_2d_phases.prepare_for_new_frame(retained_view_entity, GpuPreprocessingMode::None); - live_entities.insert(retained_view_entity); + if let Ok(mut phase) = phases.get_mut(entity) { + phase.prepare_for_new_frame(); + } else { + commands + .entity(entity) + .insert(BinnedRenderPhase::::new( + GpuPreprocessingMode::None, + )); + } } - - // Clear out all dead views. - wireframe_2d_phases.retain(|camera_entity, _| live_entities.contains(camera_entity)); } pub fn extract_wireframe_entities_needing_specialization( @@ -729,8 +724,10 @@ pub fn specialize_wireframes( render_meshes: Res>, render_mesh_instances: Res, render_wireframe_instances: Res, - wireframe_phases: Res>, - views: Query<(&ExtractedView, &RenderVisibleEntities)>, + views: Query< + (&ExtractedView, &RenderVisibleEntities), + With>, + >, view_key_cache: Res, entity_specialization_ticks: Res, view_specialization_ticks: Res, @@ -747,10 +744,6 @@ pub fn specialize_wireframes( for (view, visible_entities) in &views { all_views.insert(view.retained_view_entity); - if !wireframe_phases.contains_key(&view.retained_view_entity) { - continue; - } - let Some(view_key) = view_key_cache.get(&view.retained_view_entity.main_entity) else { continue; }; @@ -813,13 +806,13 @@ fn queue_wireframes( mesh_allocator: Res, specialized_wireframe_pipeline_cache: Res, render_wireframe_instances: Res, - mut wireframe_2d_phases: ResMut>, - mut views: Query<(&ExtractedView, &RenderVisibleEntities)>, + mut views: Query<( + &ExtractedView, + &RenderVisibleEntities, + &mut BinnedRenderPhase, + )>, ) { - for (view, visible_entities) in &mut views { - let Some(wireframe_phase) = wireframe_2d_phases.get_mut(&view.retained_view_entity) else { - continue; - }; + for (view, visible_entities, mut wireframe_phase) in &mut views { let draw_wireframe = custom_draw_functions.read().id::(); let Some(view_specialized_material_pipeline_cache) = diff --git a/crates/bevy_sprite_render/src/render/mod.rs b/crates/bevy_sprite_render/src/render/mod.rs index 8bcbba16048e9..5bd475b1b8a69 100644 --- a/crates/bevy_sprite_render/src/render/mod.rs +++ b/crates/bevy_sprite_render/src/render/mod.rs @@ -21,12 +21,11 @@ use bevy_image::{BevyDefault, Image, TextureAtlasLayout}; use bevy_math::{Affine3A, FloatOrd, Quat, Rect, Vec2, Vec4}; use bevy_mesh::VertexBufferLayout; use bevy_platform::collections::HashMap; -use bevy_render::view::{RenderVisibleEntities, RetainedViewEntity}; use bevy_render::{ render_asset::RenderAssets, render_phase::{ DrawFunctions, PhaseItem, PhaseItemExtraIndex, RenderCommand, RenderCommandResult, - SetItemPipeline, TrackedRenderPass, ViewSortedRenderPhases, + SetItemPipeline, TrackedRenderPass, }, render_resource::{ binding_types::{sampler, texture_2d, uniform_buffer}, @@ -38,6 +37,10 @@ use bevy_render::{ view::{ExtractedView, Msaa, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms}, Extract, }; +use bevy_render::{ + render_phase::SortedRenderPhase, + view::{RenderVisibleEntities, RetainedViewEntity}, +}; use bevy_shader::{Shader, ShaderDefVal}; use bevy_sprite::{Anchor, Sprite, SpriteScalingMode}; use bevy_transform::components::GlobalTransform; @@ -475,23 +478,18 @@ pub fn queue_sprites( mut pipelines: ResMut>, pipeline_cache: Res, extracted_sprites: Res, - mut transparent_render_phases: ResMut>, mut views: Query<( &RenderVisibleEntities, &ExtractedView, &Msaa, + &mut SortedRenderPhase, Option<&Tonemapping>, Option<&DebandDither>, )>, ) { let draw_sprite_function = draw_functions.read().id::(); - for (visible_entities, view, msaa, tonemapping, dither) in &mut views { - let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity) - else { - continue; - }; - + for (visible_entities, view, msaa, mut transparent_phase, tonemapping, dither) in &mut views { let msaa_key = SpritePipelineKey::from_msaa_samples(msaa.samples()); let mut view_key = SpritePipelineKey::from_hdr(view.hdr) | msaa_key; @@ -600,7 +598,7 @@ pub fn prepare_sprite_image_bind_groups( gpu_images: Res>, extracted_sprites: Res, extracted_slices: Res, - mut phases: ResMut>, + mut views: Query<(&ExtractedView, &mut SortedRenderPhase)>, events: Res, mut batches: ResMut, ) { @@ -626,11 +624,12 @@ pub fn prepare_sprite_image_bind_groups( let image_bind_groups = &mut *image_bind_groups; - for (retained_view, transparent_phase) in phases.iter_mut() { + for (view, mut transparent_phase) in views.iter_mut() { let mut current_batch = None; let mut batch_item_index = 0; let mut batch_image_size = Vec2::ZERO; let mut batch_image_handle = AssetId::invalid(); + let retained_view = &view.retained_view_entity; // Iterate through the phase items and detect when successive sprites that can be batched. // Spawn an entity with a `SpriteBatch` component for each possible batch. diff --git a/crates/bevy_ui_render/src/box_shadow.rs b/crates/bevy_ui_render/src/box_shadow.rs index 44eeb9d92f6cd..c832bc0018b6a 100644 --- a/crates/bevy_ui_render/src/box_shadow.rs +++ b/crates/bevy_ui_render/src/box_shadow.rs @@ -297,17 +297,12 @@ pub fn extract_shadows( } } -#[expect( - clippy::too_many_arguments, - reason = "it's a system that needs a lot of them" -)] pub fn queue_shadows( extracted_box_shadows: ResMut, box_shadow_pipeline: Res, mut pipelines: ResMut>, - mut transparent_render_phases: ResMut>, mut render_views: Query<(&UiCameraView, Option<&BoxShadowSamples>), With>, - camera_views: Query<&ExtractedView>, + mut camera_views: Query<(&ExtractedView, &mut SortedRenderPhase)>, pipeline_cache: Res, draw_functions: Res>, ) { @@ -320,12 +315,7 @@ pub fn queue_shadows( continue; }; - let Ok(view) = camera_views.get(default_camera_view.0) else { - continue; - }; - - let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity) - else { + let Ok((view, mut transparent_phase)) = camera_views.get_mut(default_camera_view.0) else { continue; }; @@ -361,7 +351,7 @@ pub fn prepare_shadows( mut extracted_shadows: ResMut, view_uniforms: Res, box_shadow_pipeline: Res, - mut phases: ResMut>, + mut phases: Query<&mut SortedRenderPhase>, mut previous_len: Local, ) { if let Some(view_binding) = view_uniforms.uniforms.binding() { @@ -379,7 +369,7 @@ pub fn prepare_shadows( let mut vertices_index = 0; let mut indices_index = 0; - for ui_phase in phases.values_mut() { + for mut ui_phase in phases.iter_mut() { for item_index in 0..ui_phase.items.len() { let item = &mut ui_phase.items[item_index]; let Some(box_shadow) = extracted_shadows diff --git a/crates/bevy_ui_render/src/gradient.rs b/crates/bevy_ui_render/src/gradient.rs index f6070d34f328c..b3f9d95f89462 100644 --- a/crates/bevy_ui_render/src/gradient.rs +++ b/crates/bevy_ui_render/src/gradient.rs @@ -575,17 +575,12 @@ pub fn extract_gradients( } } -#[expect( - clippy::too_many_arguments, - reason = "it's a system that needs a lot of them" -)] pub fn queue_gradient( extracted_gradients: ResMut, gradients_pipeline: Res, mut pipelines: ResMut>, - mut transparent_render_phases: ResMut>, mut render_views: Query<(&UiCameraView, Option<&UiAntiAlias>), With>, - camera_views: Query<&ExtractedView>, + mut camera_views: Query<(&ExtractedView, &mut SortedRenderPhase)>, pipeline_cache: Res, draw_functions: Res>, ) { @@ -597,12 +592,7 @@ pub fn queue_gradient( continue; }; - let Ok(view) = camera_views.get(default_camera_view.0) else { - continue; - }; - - let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity) - else { + let Ok((view, mut transparent_phase)) = camera_views.get_mut(default_camera_view.0) else { continue; }; @@ -698,7 +688,7 @@ pub fn prepare_gradient( mut extracted_color_stops: ResMut, view_uniforms: Res, gradients_pipeline: Res, - mut phases: ResMut>, + mut phases: Query<&mut SortedRenderPhase>, mut previous_len: Local, ) { if let Some(view_binding) = view_uniforms.uniforms.binding() { @@ -716,7 +706,7 @@ pub fn prepare_gradient( let mut vertices_index = 0; let mut indices_index = 0; - for ui_phase in phases.values_mut() { + for mut ui_phase in phases.iter_mut() { for item_index in 0..ui_phase.items.len() { let item = &mut ui_phase.items[item_index]; if let Some(gradient) = extracted_gradients diff --git a/crates/bevy_ui_render/src/lib.rs b/crates/bevy_ui_render/src/lib.rs index fb8b5c9b51af5..2232ad769faaf 100644 --- a/crates/bevy_ui_render/src/lib.rs +++ b/crates/bevy_ui_render/src/lib.rs @@ -23,6 +23,7 @@ use bevy_camera::visibility::InheritedVisibility; use bevy_camera::{Camera, Camera2d, Camera3d, RenderTarget}; use bevy_reflect::prelude::ReflectDefault; use bevy_reflect::Reflect; +use bevy_render::render_phase::SortedRenderPhase; use bevy_shader::load_shader_library; use bevy_sprite_render::SpriteAssetEvents; use bevy_ui::widget::{ImageNode, TextShadow, ViewportNode}; @@ -45,7 +46,6 @@ use bevy_render::{ render_graph::{Node as RenderGraphNode, NodeRunError, RenderGraph, RenderGraphContext}, render_phase::{ sort_phase_system, AddRenderCommand, DrawFunctions, PhaseItem, PhaseItemExtraIndex, - ViewSortedRenderPhases, }, render_resource::*, renderer::{RenderContext, RenderDevice, RenderQueue}, @@ -218,7 +218,6 @@ impl Plugin for UiRenderPlugin { .init_resource::() .allow_ambiguous_resource::() .init_resource::>() - .init_resource::>() .add_render_command::() .configure_sets( ExtractSchedule, @@ -748,7 +747,6 @@ pub struct UiViewTarget(pub Entity); /// Extracts all UI elements associated with a camera into the render world. pub fn extract_ui_camera_view( mut commands: Commands, - mut transparent_render_phases: ResMut>, query: Extract< Query< ( @@ -812,6 +810,7 @@ pub fn extract_ui_camera_view( }, // Link to the main camera view. UiViewTarget(render_entity), + SortedRenderPhase::::default(), TemporaryRenderEntity, )) .id(); @@ -827,13 +826,8 @@ pub fn extract_ui_camera_view( if let Some(shadow_samples) = shadow_samples { entity_commands.insert(*shadow_samples); } - transparent_render_phases.insert_or_clear(retained_view_entity); - - live_entities.insert(retained_view_entity); } } - - transparent_render_phases.retain(|entity, _| live_entities.contains(entity)); } pub fn extract_viewport_nodes( @@ -1381,9 +1375,8 @@ pub fn queue_uinodes( extracted_uinodes: Res, ui_pipeline: Res, mut pipelines: ResMut>, - mut transparent_render_phases: ResMut>, render_views: Query<(&UiCameraView, Option<&UiAntiAlias>), With>, - camera_views: Query<&ExtractedView>, + mut camera_views: Query<(&ExtractedView, &mut SortedRenderPhase)>, pipeline_cache: Res, draw_functions: Res>, ) { @@ -1398,18 +1391,14 @@ pub fn queue_uinodes( .ok() .and_then(|(default_camera_view, ui_anti_alias)| { camera_views - .get(default_camera_view.0) + .get_mut(default_camera_view.0) .ok() - .and_then(|view| { - transparent_render_phases - .get_mut(&view.retained_view_entity) - .map(|transparent_phase| (view, ui_anti_alias, transparent_phase)) - }) + .map(|(view, transparent_phase)| (view, ui_anti_alias, transparent_phase)) }); current_camera_entity = extracted_uinode.extracted_camera_entity; } - let Some((view, ui_anti_alias, transparent_phase)) = current_phase.as_mut() else { + let Some((view, ui_anti_alias, ref mut transparent_phase)) = current_phase else { continue; }; @@ -1452,7 +1441,7 @@ pub fn prepare_uinodes( ui_pipeline: Res, mut image_bind_groups: ResMut, gpu_images: Res>, - mut phases: ResMut>, + mut phases: Query<&mut SortedRenderPhase>, events: Res, mut previous_len: Local, ) { @@ -1484,7 +1473,7 @@ pub fn prepare_uinodes( let mut vertices_index = 0; let mut indices_index = 0; - for ui_phase in phases.values_mut() { + for mut ui_phase in phases.iter_mut() { let mut batch_item_index = 0; let mut batch_image_handle = AssetId::invalid(); diff --git a/crates/bevy_ui_render/src/render_pass.rs b/crates/bevy_ui_render/src/render_pass.rs index 642f69d9350e8..d9416a5d6796b 100644 --- a/crates/bevy_ui_render/src/render_pass.rs +++ b/crates/bevy_ui_render/src/render_pass.rs @@ -21,7 +21,10 @@ use bevy_render::{ use tracing::error; pub struct UiPassNode { - ui_view_query: QueryState<(&'static ExtractedView, &'static UiViewTarget)>, + ui_view_query: QueryState<( + &'static UiViewTarget, + &'static SortedRenderPhase, + )>, ui_view_target_query: QueryState<(&'static ViewTarget, &'static ExtractedCamera)>, ui_camera_view_query: QueryState<&'static UiCameraView>, } @@ -52,17 +55,16 @@ impl Node for UiPassNode { // Extract the UI view. let input_view_entity = graph.view_entity(); - let Some(transparent_render_phases) = - world.get_resource::>() + // Query the UI view components. + let Ok((ui_view_target, transparent_phase)) = + self.ui_view_query.get_manual(world, input_view_entity) else { return Ok(()); }; - // Query the UI view components. - let Ok((view, ui_view_target)) = self.ui_view_query.get_manual(world, input_view_entity) - else { + if transparent_phase.items.is_empty() { return Ok(()); - }; + } let Ok((target, camera)) = self .ui_view_target_query @@ -71,15 +73,6 @@ impl Node for UiPassNode { return Ok(()); }; - let Some(transparent_phase) = transparent_render_phases.get(&view.retained_view_entity) - else { - return Ok(()); - }; - - if transparent_phase.items.is_empty() { - return Ok(()); - } - let diagnostics = render_context.diagnostic_recorder(); // use the UI view entity if it is defined diff --git a/crates/bevy_ui_render/src/ui_material_pipeline.rs b/crates/bevy_ui_render/src/ui_material_pipeline.rs index 5398dba2f08f7..6adb00d6125b9 100644 --- a/crates/bevy_ui_render/src/ui_material_pipeline.rs +++ b/crates/bevy_ui_render/src/ui_material_pipeline.rs @@ -386,7 +386,7 @@ pub fn prepare_uimaterial_nodes( view_uniforms: Res, globals_buffer: Res, ui_material_pipeline: Res>, - mut phases: ResMut>, + mut phases: Query<&mut SortedRenderPhase>, mut previous_len: Local, ) { if let (Some(view_binding), Some(globals_binding)) = ( @@ -403,7 +403,7 @@ pub fn prepare_uimaterial_nodes( )); let mut index = 0; - for ui_phase in phases.values_mut() { + for mut ui_phase in phases.iter_mut() { let mut batch_item_index = 0; let mut batch_shader_handle = AssetId::invalid(); @@ -591,9 +591,8 @@ pub fn queue_ui_material_nodes( mut pipelines: ResMut>>, pipeline_cache: Res, render_materials: Res>>, - mut transparent_render_phases: ResMut>, mut render_views: Query<&UiCameraView, With>, - camera_views: Query<&ExtractedView>, + mut camera_views: Query<(&ExtractedView, &mut SortedRenderPhase)>, ) where M::Data: PartialEq + Eq + Hash + Clone, { @@ -610,14 +609,10 @@ pub fn queue_ui_material_nodes( continue; }; - let Ok(view) = camera_views.get(default_camera_view.0) else { - continue; - }; - - let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity) - else { + let Ok((view, mut transparent_phase)) = camera_views.get_mut(default_camera_view.0) else { continue; }; + let transparent_phase = transparent_phase.as_mut(); let pipeline = pipelines.specialize( &pipeline_cache, diff --git a/crates/bevy_ui_render/src/ui_texture_slice_pipeline.rs b/crates/bevy_ui_render/src/ui_texture_slice_pipeline.rs index 14eeb5c35cac2..4606525308bde 100644 --- a/crates/bevy_ui_render/src/ui_texture_slice_pipeline.rs +++ b/crates/bevy_ui_render/src/ui_texture_slice_pipeline.rs @@ -302,17 +302,12 @@ pub fn extract_ui_texture_slices( } } -#[expect( - clippy::too_many_arguments, - reason = "it's a system that needs a lot of them" -)] pub fn queue_ui_slices( extracted_ui_slicers: ResMut, ui_slicer_pipeline: Res, mut pipelines: ResMut>, - mut transparent_render_phases: ResMut>, mut render_views: Query<&UiCameraView, With>, - camera_views: Query<&ExtractedView>, + mut camera_views: Query<(&ExtractedView, &mut SortedRenderPhase)>, pipeline_cache: Res, draw_functions: Res>, ) { @@ -324,12 +319,7 @@ pub fn queue_ui_slices( continue; }; - let Ok(view) = camera_views.get(default_camera_view.0) else { - continue; - }; - - let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity) - else { + let Ok((view, mut transparent_phase)) = camera_views.get_mut(default_camera_view.0) else { continue; }; @@ -363,7 +353,7 @@ pub fn prepare_ui_slices( texture_slicer_pipeline: Res, mut image_bind_groups: ResMut, gpu_images: Res>, - mut phases: ResMut>, + mut phases: Query<&mut SortedRenderPhase>, events: Res, mut previous_len: Local, ) { @@ -395,7 +385,7 @@ pub fn prepare_ui_slices( let mut vertices_index = 0; let mut indices_index = 0; - for ui_phase in phases.values_mut() { + for mut ui_phase in phases.iter_mut() { let mut batch_item_index = 0; let mut batch_image_handle = AssetId::invalid(); let mut batch_image_size = Vec2::ZERO; diff --git a/examples/2d/mesh2d_manual.rs b/examples/2d/mesh2d_manual.rs index 79913288767a8..ec752b8ffd861 100644 --- a/examples/2d/mesh2d_manual.rs +++ b/examples/2d/mesh2d_manual.rs @@ -17,7 +17,7 @@ use bevy::{ render_asset::RenderAssets, render_phase::{ AddRenderCommand, DrawFunctions, PhaseItemExtraIndex, SetItemPipeline, - ViewSortedRenderPhases, + SortedRenderPhase, }, render_resource::{ BlendState, ColorTargetState, ColorWrites, CompareFunction, DepthBiasState, @@ -381,19 +381,18 @@ pub fn queue_colored_mesh2d( pipeline_cache: Res, render_meshes: Res>, render_mesh_instances: Res, - mut transparent_render_phases: ResMut>, - views: Query<(&RenderVisibleEntities, &ExtractedView, &Msaa)>, + mut views: Query<( + &RenderVisibleEntities, + &ExtractedView, + &Msaa, + &mut SortedRenderPhase, + )>, ) { if render_mesh_instances.is_empty() { return; } // Iterate each view (a camera is a view) - for (visible_entities, view, msaa) in &views { - let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity) - else { - continue; - }; - + for (visible_entities, view, msaa, mut transparent_phase) in views.iter_mut() { let draw_colored_mesh2d = transparent_draw_functions.read().id::(); let mesh_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples()) diff --git a/examples/README.md b/examples/README.md index 06e451d4f5c91..ea2f33dd79cd7 100644 --- a/examples/README.md +++ b/examples/README.md @@ -467,6 +467,7 @@ Example | Description [Animated](../examples/shader/animate_shader.rs) | A shader that uses dynamic data like the time since startup [Array Texture](../examples/shader/array_texture.rs) | A shader that shows how to reuse the core bevy PBR shading functionality in a custom material that obtains the base color from an array texture. [Compute - Game of Life](../examples/shader/compute_shader_game_of_life.rs) | A compute shader that simulates Conway's Game of Life +[Custom Mesh Pass](../examples/shader_advanced/custom_mesh_pass.rs) | Demonstrates how to write a custom mesh pass [Custom Render Phase](../examples/shader_advanced/custom_render_phase.rs) | Shows how to make a complete render phase [Custom Vertex Attribute](../examples/shader_advanced/custom_vertex_attribute.rs) | A shader that reads a mesh's custom vertex attribute [Custom phase item](../examples/shader_advanced/custom_phase_item.rs) | Demonstrates how to enqueue custom draw commands in a render phase diff --git a/examples/shader_advanced/custom_mesh_pass.rs b/examples/shader_advanced/custom_mesh_pass.rs new file mode 100644 index 0000000000000..11424e3671e9d --- /dev/null +++ b/examples/shader_advanced/custom_mesh_pass.rs @@ -0,0 +1,315 @@ +//! Demonstrates how to write a custom mesh pass +//! +//! The `MeshPass` in Bevy is designed for creating render passes that draw meshes with a set of new shaders in `Material`. +//! +//! This is useful for creating custom prepasses or implementing techniques like Inverted Hull Outline. + +use bevy::{ + camera::{MainPassResolutionOverride, Viewport}, + core_pipeline::core_3d::{ + graph::{Core3d, Node3d}, + Opaque3d, Transparent3d, + }, + ecs::query::QueryItem, + mesh::MeshVertexBufferLayoutRef, + pbr::{ + BinnedPhaseItem, DrawMaterial, ExtendedMaterial, MainPass, MaterialExtension, + MaterialExtensionKey, MaterialExtensionPipeline, MaterialPipelineSpecializer, MeshPass, + MeshPassPlugin, NoExtractCondition, PassShaders, PhaseContext, PhaseItemExt, + QueueSortedPhaseItem, RenderPhaseType, ShaderSet, SortedPhaseFamily, SortedPhaseItem, + }, + prelude::*, + render::{ + camera::ExtractedCamera, + diagnostic::RecordDiagnostics, + extract_component::ExtractComponent, + render_graph::{RenderGraphContext, RenderGraphExt, RenderLabel, ViewNode, ViewNodeRunner}, + render_phase::{BinnedRenderPhase, SortedRenderPhase, TrackedRenderPass}, + render_resource::{ + AsBindGroup, CommandEncoderDescriptor, Face, RenderPassDescriptor, + RenderPipelineDescriptor, SpecializedMeshPipelineError, StoreOp, + }, + renderer::RenderContext, + view::{ViewDepthTexture, ViewTarget}, + RenderApp, + }, +}; + +const SHADER_ASSET_PATH: &str = "shaders/custom_mesh_pass_material.wgsl"; + +fn main() { + let mut app = App::new(); + app.add_plugins(( + DefaultPlugins, + MaterialPlugin::>::default(), + MaterialPlugin::>::default( + ), + MeshPassPlugin::::default(), + )) + // You can use `register_required_components` to add our `OutlinePass` to all cameras. + // Example: .register_required_components::() + .add_systems(Startup, setup); + + let Some(render_app) = app.get_sub_app_mut(RenderApp) else { + return; + }; + render_app + .add_render_graph_node::>(Core3d, OutlinePassLabel) + .add_render_graph_edges( + Core3d, + ( + Node3d::MainOpaquePass, + OutlinePassLabel, + Node3d::MainTransmissivePass, + ), + ); + + app.run(); +} + +#[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)] +struct OutlinePassLabel; + +#[derive(Clone, Copy, Default, Component, ExtractComponent)] +struct OutlinePass; + +impl MeshPass for OutlinePass { + type ViewKeySource = MainPass; + type Specializer = MaterialPipelineSpecializer; + type PhaseItems = (OutlineOpaque3d, OutlineTransparent3d); +} + +// Single-field tuple structs can automatically forward all phase item traits from the inner type. +#[derive(BinnedPhaseItem)] +struct OutlineOpaque3d(Opaque3d); + +// Other struct forms have some limitations: +// - `#[derive(BinnedPhaseItem)]` cannot derive `BinnedPhaseItem` automatically +// - `#[derive(SortedPhaseItem)]` cannot derive `QueueSortedPhaseItem` automatically +// - We have to skip those unsupported traits explicitly. +#[derive(SortedPhaseItem)] +struct OutlineTransparent3d { + // For demonstration, we also skip `PhaseItemExt`. + #[phase_item(skip(QueueSortedPhaseItem, PhaseItemExt))] + inner: Transparent3d, +} + +// The APIs of `QueueBinnedPhaseItem` and `QueueSortedPhaseItem` are quite different. +// For more details, check their definitions. +impl QueueSortedPhaseItem for OutlineTransparent3d { + fn get_item(context: &PhaseContext) -> Option { + Transparent3d::get_item(context).map(|inner| Self { inner }) + } +} + +impl PhaseItemExt for OutlineTransparent3d { + type PhaseFamily = SortedPhaseFamily; + type ExtractCondition = NoExtractCondition; + type RenderCommand = DrawMaterial; + const PHASE_TYPES: RenderPhaseType = RenderPhaseType::Transparent; +} + +#[derive(Asset, TypePath, AsBindGroup, Clone, Default)] +struct OutlineExtension { + #[uniform(100)] + outline_color: LinearRgba, +} + +impl MaterialExtension for OutlineExtension { + fn shaders() -> PassShaders { + let mut pass_shaders = PassShaders::default(); + pass_shaders.extend([ + (MainPass::id(), ShaderSet::default()), + ( + OutlinePass::id(), + ShaderSet { + vertex: SHADER_ASSET_PATH.into(), + fragment: SHADER_ASSET_PATH.into(), + }, + ), + ]); + pass_shaders + } + + fn specialize( + _pipeline: &MaterialExtensionPipeline, + descriptor: &mut RenderPipelineDescriptor, + _layout: &MeshVertexBufferLayoutRef, + key: MaterialExtensionKey, + ) -> Result<(), SpecializedMeshPipelineError> { + if key.pass_id == OutlinePass::id() { + descriptor.primitive.cull_mode = Some(Face::Front); + } + Ok(()) + } +} + +#[derive(Asset, TypePath, AsBindGroup, Clone, Default)] +struct TransparentOutlineExtension { + #[uniform(100)] + outline_color: LinearRgba, +} + +impl MaterialExtension for TransparentOutlineExtension { + fn shaders() -> PassShaders { + let mut pass_shaders = PassShaders::default(); + pass_shaders.extend([ + (MainPass::id(), ShaderSet::default()), + ( + OutlinePass::id(), + // For simplicity, we are using the same shader for both opaque and transparent passes. + ShaderSet { + vertex: SHADER_ASSET_PATH.into(), + fragment: SHADER_ASSET_PATH.into(), + }, + ), + ]); + pass_shaders + } + + fn alpha_mode() -> Option { + Some(AlphaMode::Blend) + } +} + +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>>, + mut transparent_materials: ResMut< + Assets>, + >, +) { + // Cube + commands.spawn(( + MeshMaterial3d(materials.add(ExtendedMaterial { + base: StandardMaterial { + base_color: Color::srgb(1.0, 0.75, 0.75), + ..default() + }, + extension: OutlineExtension { + outline_color: Color::srgb(0.6, 0.9, 0.70).to_linear(), + }, + })), + Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 1.0).mesh())), + Transform::from_xyz(0.0, 0.5, -0.5), + )); + + // Sphere + commands.spawn(( + MeshMaterial3d(transparent_materials.add(ExtendedMaterial { + base: StandardMaterial { + base_color: Color::srgba(0.75, 0.75, 1.0, 0.5), + alpha_mode: AlphaMode::Blend, + ..default() + }, + extension: TransparentOutlineExtension { + outline_color: Color::srgb(0.75, 0.6, 0.2).to_linear(), + }, + })), + Mesh3d(meshes.add(Sphere::new(0.5).mesh())), + Transform::from_xyz(0.0, 0.5, 1.0), + )); + + // Camera + commands.spawn(( + Camera3d::default(), + Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), + // We are not using `register_required_components`, so let's manually + // mark the camera for rendering the custom pass. + OutlinePass, + )); + + // Light + commands.spawn(( + SpotLight::default(), + Transform::from_xyz(4.0, 5.0, 4.0).looking_at(Vec3::ZERO, Vec3::Y), + )); +} + +#[derive(Default)] +struct OutlinePassNode; + +impl ViewNode for OutlinePassNode { + type ViewQuery = ( + &'static ExtractedCamera, + &'static ViewTarget, + &'static ViewDepthTexture, + &'static BinnedRenderPhase, + &'static SortedRenderPhase, + Option<&'static MainPassResolutionOverride>, + ); + + fn run<'w>( + &self, + graph: &mut RenderGraphContext, + render_context: &mut RenderContext<'w>, + (camera, target, depth, opaque_phase, transparent_phase, resolution_override): QueryItem< + 'w, + '_, + Self::ViewQuery, + >, + world: &'w World, + ) -> Result<(), bevy_render::render_graph::NodeRunError> { + let diagnostics = render_context.diagnostic_recorder(); + + let color_attachments = [Some(target.get_color_attachment())]; + let depth_stencil_attachment = Some(depth.get_attachment(StoreOp::Store)); + + let view_entity = graph.view_entity(); + render_context.add_command_buffer_generation_task(move |render_device| { + #[cfg(feature = "trace")] + let _main_opaque_pass_3d_span = info_span!("outline_opaque_pass_3d").entered(); + + // Command encoder setup + let mut command_encoder = + render_device.create_command_encoder(&CommandEncoderDescriptor { + label: Some("outline_opaque_pass_3d_command_encoder"), + }); + + // Render pass setup + let render_pass = command_encoder.begin_render_pass(&RenderPassDescriptor { + label: Some("outline_opaque_pass_3d"), + color_attachments: &color_attachments, + depth_stencil_attachment, + timestamp_writes: None, + occlusion_query_set: None, + }); + let mut render_pass = TrackedRenderPass::new(&render_device, render_pass); + let pass_span = diagnostics.pass_span(&mut render_pass, "outline_pass"); + + if let Some(viewport) = + Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) + { + render_pass.set_camera_viewport(&viewport); + } + + // Opaque draws + if !opaque_phase.is_empty() { + #[cfg(feature = "trace")] + let _opaque_main_pass_3d_span = info_span!("opaque_outline_pass_3d").entered(); + if let Err(err) = opaque_phase.render(&mut render_pass, world, view_entity) { + error!("Error encountered while rendering the outline opaque phase {err:?}"); + } + } + + // Transparent draws + if !transparent_phase.items.is_empty() { + #[cfg(feature = "trace")] + let _transparent_main_pass_3d_span = + info_span!("transparent_outline_pass_3d").entered(); + if let Err(err) = transparent_phase.render(&mut render_pass, world, view_entity) { + error!( + "Error encountered while rendering the outline transparent phase {err:?}" + ); + } + } + + pass_span.end(&mut render_pass); + drop(render_pass); + command_encoder.finish() + }); + + Ok(()) + } +} diff --git a/examples/shader_advanced/custom_phase_item.rs b/examples/shader_advanced/custom_phase_item.rs index 32ad10146dd18..a1e845eddc944 100644 --- a/examples/shader_advanced/custom_phase_item.rs +++ b/examples/shader_advanced/custom_phase_item.rs @@ -23,9 +23,9 @@ use bevy::{ render::{ extract_component::{ExtractComponent, ExtractComponentPlugin}, render_phase::{ - AddRenderCommand, BinnedRenderPhaseType, DrawFunctions, InputUniformIndex, PhaseItem, - RenderCommand, RenderCommandResult, SetItemPipeline, TrackedRenderPass, - ViewBinnedRenderPhases, + AddRenderCommand, BinnedRenderPhase, BinnedRenderPhaseType, DrawFunctions, + InputUniformIndex, PhaseItem, RenderCommand, RenderCommandResult, SetItemPipeline, + TrackedRenderPass, }, render_resource::{ BufferUsages, Canonical, ColorTargetState, ColorWrites, CompareFunction, @@ -34,7 +34,7 @@ use bevy::{ Variants, VertexAttribute, VertexFormat, VertexState, VertexStepMode, }, renderer::{RenderDevice, RenderQueue}, - view::{ExtractedView, RenderVisibleEntities}, + view::RenderVisibleEntities, Render, RenderApp, RenderSystems, }, }; @@ -214,9 +214,12 @@ fn prepare_custom_phase_item_buffers(mut commands: Commands) { fn queue_custom_phase_item( pipeline_cache: Res, mut pipeline: ResMut, - mut opaque_render_phases: ResMut>, opaque_draw_functions: Res>, - views: Query<(&ExtractedView, &RenderVisibleEntities, &Msaa)>, + mut views: Query<( + &RenderVisibleEntities, + &Msaa, + &mut BinnedRenderPhase, + )>, mut next_tick: Local, ) { let draw_custom_phase_item = opaque_draw_functions @@ -226,11 +229,7 @@ fn queue_custom_phase_item( // Render phases are per-view, so we need to iterate over all views so that // the entity appears in them. (In this example, we have only one view, but // it's good practice to loop over all views anyway.) - for (view, view_visible_entities, msaa) in views.iter() { - let Some(opaque_phase) = opaque_render_phases.get_mut(&view.retained_view_entity) else { - continue; - }; - + for (view_visible_entities, msaa, mut opaque_phase) in views.iter_mut() { // Find all the custom rendered entities that are visible from this // view. for &entity in view_visible_entities.get::().iter() { diff --git a/examples/shader_advanced/custom_render_phase.rs b/examples/shader_advanced/custom_render_phase.rs index 8fd1cdc837539..d1855730bc702 100644 --- a/examples/shader_advanced/custom_render_phase.rs +++ b/examples/shader_advanced/custom_render_phase.rs @@ -27,7 +27,6 @@ use bevy::{ DrawMesh, MeshInputUniform, MeshPipeline, MeshPipelineKey, MeshPipelineViewLayoutKey, MeshUniform, RenderMeshInstances, SetMeshBindGroup, SetMeshViewBindGroup, }, - platform::collections::HashSet, prelude::*, render::{ batching::{ @@ -47,7 +46,7 @@ use bevy::{ render_phase::{ sort_phase_system, AddRenderCommand, CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, PhaseItem, PhaseItemExtraIndex, SetItemPipeline, SortedPhaseItem, - SortedRenderPhasePlugin, ViewSortedRenderPhases, + SortedRenderPhase, SortedRenderPhasePlugin, }, render_resource::{ CachedRenderPipelineId, ColorTargetState, ColorWrites, Face, FragmentState, @@ -56,8 +55,8 @@ use bevy::{ TextureFormat, VertexState, }, renderer::RenderContext, - sync_world::MainEntity, - view::{ExtractedView, RenderVisibleEntities, RetainedViewEntity, ViewTarget}, + sync_world::{MainEntity, RenderEntity}, + view::{ExtractedView, RenderVisibleEntities, ViewTarget}, Extract, Render, RenderApp, RenderDebugFlags, RenderStartup, RenderSystems, }, }; @@ -129,7 +128,6 @@ impl Plugin for MeshStencilPhasePlugin { .init_resource::>() .init_resource::>() .add_render_command::() - .init_resource::>() .add_systems(RenderStartup, init_stencil_pipeline) .add_systems(ExtractSchedule, extract_camera_phases) .add_systems( @@ -470,24 +468,26 @@ impl GetFullBatchData for StencilPipeline { // that will be used by the render world. We need to give that resource all views that will use // that phase fn extract_camera_phases( - mut stencil_phases: ResMut>, - cameras: Extract>>, - mut live_entities: Local>, + mut commands: Commands, + mut phases: Query<&mut SortedRenderPhase>, + cameras: Extract>>, ) { - live_entities.clear(); - for (main_entity, camera) in &cameras { + for (entity, camera) in &cameras { if !camera.is_active { + commands + .entity(entity) + .remove::>(); continue; } - // This is the main camera, so we use the first subview index (0) - let retained_view_entity = RetainedViewEntity::new(main_entity.into(), None, 0); - stencil_phases.insert_or_clear(retained_view_entity); - live_entities.insert(retained_view_entity); + if let Ok(mut phase) = phases.get_mut(entity) { + phase.clear(); + } else { + commands + .entity(entity) + .insert(SortedRenderPhase::::default()); + } } - - // Clear out all dead views. - stencil_phases.retain(|camera_entity, _| live_entities.contains(camera_entity)); } // This is a very important step when writing a custom phase. @@ -500,14 +500,15 @@ fn queue_custom_meshes( custom_draw_pipeline: Res, render_meshes: Res>, render_mesh_instances: Res, - mut custom_render_phases: ResMut>, - mut views: Query<(&ExtractedView, &RenderVisibleEntities, &Msaa)>, + mut views: Query<( + &ExtractedView, + &RenderVisibleEntities, + &Msaa, + &mut SortedRenderPhase, + )>, has_marker: Query<(), With>, ) { - for (view, visible_entities, msaa) in &mut views { - let Some(custom_phase) = custom_render_phases.get_mut(&view.retained_view_entity) else { - continue; - }; + for (view, visible_entities, msaa, mut custom_phase) in &mut views { let draw_custom = custom_draw_functions.read().id::(); // Create the key based on the view. @@ -576,8 +577,8 @@ struct CustomDrawNode; impl ViewNode for CustomDrawNode { type ViewQuery = ( &'static ExtractedCamera, - &'static ExtractedView, &'static ViewTarget, + &'static SortedRenderPhase, Option<&'static MainPassResolutionOverride>, ); @@ -585,22 +586,12 @@ impl ViewNode for CustomDrawNode { &self, graph: &mut RenderGraphContext, render_context: &mut RenderContext<'w>, - (camera, view, target, resolution_override): QueryItem<'w, '_, Self::ViewQuery>, + (camera, target, stencil_phase, resolution_override): QueryItem<'w, '_, Self::ViewQuery>, world: &'w World, ) -> Result<(), NodeRunError> { - // First, we need to get our phases resource - let Some(stencil_phases) = world.get_resource::>() else { - return Ok(()); - }; - // Get the view entity from the graph let view_entity = graph.view_entity(); - // Get the phase for the current view running our node - let Some(stencil_phase) = stencil_phases.get(&view.retained_view_entity) else { - return Ok(()); - }; - // Render pass setup let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { label: Some("stencil pass"), diff --git a/examples/shader_advanced/custom_shader_instancing.rs b/examples/shader_advanced/custom_shader_instancing.rs index b9d01fdf481e7..1ab55e5f507ef 100644 --- a/examples/shader_advanced/custom_shader_instancing.rs +++ b/examples/shader_advanced/custom_shader_instancing.rs @@ -26,7 +26,7 @@ use bevy::{ render_asset::RenderAssets, render_phase::{ AddRenderCommand, DrawFunctions, PhaseItem, PhaseItemExtraIndex, RenderCommand, - RenderCommandResult, SetItemPipeline, TrackedRenderPass, ViewSortedRenderPhases, + RenderCommandResult, SetItemPipeline, SortedRenderPhase, TrackedRenderPass, }, render_resource::*, renderer::RenderDevice, @@ -129,17 +129,11 @@ fn queue_custom( meshes: Res>, render_mesh_instances: Res, material_meshes: Query<(Entity, &MainEntity), With>, - mut transparent_render_phases: ResMut>, - views: Query<(&ExtractedView, &Msaa)>, + mut views: Query<(&ExtractedView, &Msaa, &mut SortedRenderPhase)>, ) { let draw_custom = transparent_3d_draw_functions.read().id::(); - for (view, msaa) in &views { - let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity) - else { - continue; - }; - + for (view, msaa, mut transparent_phase) in views.iter_mut() { let msaa_key = MeshPipelineKey::from_msaa_samples(msaa.samples()); let view_key = msaa_key | MeshPipelineKey::from_hdr(view.hdr); diff --git a/examples/shader_advanced/manual_material.rs b/examples/shader_advanced/manual_material.rs index f7ac7c66cdec7..eae7decadcc87 100644 --- a/examples/shader_advanced/manual_material.rs +++ b/examples/shader_advanced/manual_material.rs @@ -9,11 +9,12 @@ use bevy::{ }, pbr::{ late_sweep_material_instances, DrawMaterial, EntitiesNeedingSpecialization, - EntitySpecializationTickPair, EntitySpecializationTicks, MainPassOpaqueDrawFunction, + EntitySpecializationTickPair, EntitySpecializationTicks, MainPass, MaterialBindGroupAllocator, MaterialBindGroupAllocators, MaterialExtractEntitiesNeedingSpecializationSystems, MaterialExtractionSystems, - MaterialFragmentShader, MaterialProperties, PreparedMaterial, RenderMaterialBindings, - RenderMaterialInstance, RenderMaterialInstances, SpecializedMaterialPipelineCache, + MaterialFragmentShader, MaterialProperties, MeshPass, MeshPassDrawFunction, + PreparedMaterial, RenderMaterialBindings, RenderMaterialInstance, RenderMaterialInstances, + SpecializedMaterialPipelineCache, }, platform::collections::hash_map::Entry, prelude::*, @@ -200,8 +201,17 @@ impl ErasedRenderAsset for ImageMaterial { material_layout: Some(material_layout), ..Default::default() }; - properties.add_draw_function(MainPassOpaqueDrawFunction, draw_function_id); - properties.add_shader(MaterialFragmentShader, asset_server.load(SHADER_ASSET_PATH)); + properties.add_draw_function( + MeshPassDrawFunction { + pass_id: MainPass::id(), + phase_idx: 0, + }, + draw_function_id, + ); + properties.add_shader( + MaterialFragmentShader(MainPass::id()), + asset_server.load(SHADER_ASSET_PATH), + ); Ok(PreparedMaterial { binding, @@ -297,7 +307,7 @@ fn extract_image_materials_needing_specialization( entities_needing_specialization: Extract>>, mut entity_specialization_ticks: ResMut, mut removed_mesh_material_components: Extract>, - mut specialized_material_pipeline_cache: ResMut, + mut specialized_material_pipeline_cache: ResMut>, render_material_instances: Res, views: Query<&ExtractedView>, ticks: SystemChangeTick, @@ -331,7 +341,7 @@ fn extract_image_materials_needing_specialization( fn sweep_image_materials_needing_specialization( mut entity_specialization_ticks: ResMut, mut removed_mesh_material_components: Extract>, - mut specialized_material_pipeline_cache: ResMut, + mut specialized_material_pipeline_cache: ResMut>, render_material_instances: Res, views: Query<&ExtractedView>, ) { diff --git a/examples/shader_advanced/specialized_mesh_pipeline.rs b/examples/shader_advanced/specialized_mesh_pipeline.rs index ee5abdb54d647..30632b7287f81 100644 --- a/examples/shader_advanced/specialized_mesh_pipeline.rs +++ b/examples/shader_advanced/specialized_mesh_pipeline.rs @@ -24,8 +24,8 @@ use bevy::{ mesh::{allocator::MeshAllocator, RenderMesh}, render_asset::RenderAssets, render_phase::{ - AddRenderCommand, BinnedRenderPhaseType, DrawFunctions, SetItemPipeline, - ViewBinnedRenderPhases, + AddRenderCommand, BinnedRenderPhase, BinnedRenderPhaseType, DrawFunctions, + SetItemPipeline, }, render_resource::{ ColorTargetState, ColorWrites, CompareFunction, DepthStencilState, Face, FragmentState, @@ -267,12 +267,14 @@ impl SpecializedMeshPipeline for CustomMeshPipeline { fn queue_custom_mesh_pipeline( pipeline_cache: Res, custom_mesh_pipeline: Res, - (mut opaque_render_phases, opaque_draw_functions): ( - ResMut>, - Res>, - ), + opaque_draw_functions: Res>, mut specialized_mesh_pipelines: ResMut>, - views: Query<(&RenderVisibleEntities, &ExtractedView, &Msaa)>, + mut views: Query<( + &RenderVisibleEntities, + &ExtractedView, + &Msaa, + &mut BinnedRenderPhase, + )>, (render_meshes, render_mesh_instances): ( Res>, Res, @@ -289,11 +291,7 @@ fn queue_custom_mesh_pipeline( // Render phases are per-view, so we need to iterate over all views so that // the entity appears in them. (In this example, we have only one view, but // it's good practice to loop over all views anyway.) - for (view_visible_entities, view, msaa) in views.iter() { - let Some(opaque_phase) = opaque_render_phases.get_mut(&view.retained_view_entity) else { - continue; - }; - + for (view_visible_entities, view, msaa, mut opaque_phase) in views.iter_mut() { // Create the key based on the view. In this case we only care about MSAA and HDR let view_key = MeshPipelineKey::from_msaa_samples(msaa.samples()) | MeshPipelineKey::from_hdr(view.hdr);