diff --git a/Cargo.toml b/Cargo.toml index d039936d83779..fe52969268806 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4877,3 +4877,14 @@ name = "Pan Camera" description = "Example Pan-Camera Styled Camera Controller for 2D scenes" category = "Camera" 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 = "Example Pan-Camera Styled Camera Controller for 2D scenes" +category = "Shaders" +wasm = true \ No newline at end of file diff --git a/assets/shaders/custom_mesh_pass_material.wgsl b/assets/shaders/custom_mesh_pass_material.wgsl new file mode 100644 index 0000000000000..eec42e9206ed9 --- /dev/null +++ b/assets/shaders/custom_mesh_pass_material.wgsl @@ -0,0 +1,28 @@ +#import bevy_pbr::mesh_functions::{get_world_from_local, mesh_position_local_to_clip} + +@group(#{MATERIAL_BIND_GROUP}) @binding(0) var color: vec4; +@group(#{MATERIAL_BIND_GROUP}) @binding(1) var outline_color: vec4; + +struct Vertex { + @builtin(instance_index) instance_index: u32, + @location(0) position: vec3, +}; + +struct VertexOutput { + @builtin(position) clip_position: vec4, +}; + +@vertex +fn vertex(vertex: Vertex) -> VertexOutput { + var out: VertexOutput; + out.clip_position = mesh_position_local_to_clip( + get_world_from_local(vertex.instance_index), + vec4(vertex.position, 1.0), + ); + return out; +} + +@fragment +fn fragment(input: VertexOutput) -> @location(0) vec4 { + return color; +} \ No newline at end of file diff --git a/assets/shaders/custom_mesh_pass_outline_material.wgsl b/assets/shaders/custom_mesh_pass_outline_material.wgsl new file mode 100644 index 0000000000000..3d9a6fed704ee --- /dev/null +++ b/assets/shaders/custom_mesh_pass_outline_material.wgsl @@ -0,0 +1,28 @@ +#import bevy_pbr::mesh_functions::{get_world_from_local, mesh_position_local_to_clip} + +@group(#{MATERIAL_BIND_GROUP}) @binding(0) var color: vec4; +@group(#{MATERIAL_BIND_GROUP}) @binding(1) var outline_color: vec4; + +struct Vertex { + @builtin(instance_index) instance_index: u32, + @location(0) position: vec3, +}; + +struct VertexOutput { + @builtin(position) clip_position: vec4, +}; + +@vertex +fn vertex(vertex: Vertex) -> VertexOutput { + var out: VertexOutput; + out.clip_position = mesh_position_local_to_clip( + get_world_from_local(vertex.instance_index), + vec4(vertex.position, 1.0), + ); + return out; +} + +@fragment +fn fragment(input: VertexOutput) -> @location(0) vec4 { + return outline_color; +} \ No newline at end of file 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/src/dummy_phase.rs b/crates/bevy_pbr/src/dummy_phase.rs new file mode 100644 index 0000000000000..73d25a245d403 --- /dev/null +++ b/crates/bevy_pbr/src/dummy_phase.rs @@ -0,0 +1,173 @@ +use core::{marker::PhantomData, ops::Range}; + +use bevy_core_pipeline::core_3d::{Opaque3dBatchSetKey, Opaque3dBinKey}; +use bevy_ecs::entity::Entity; +use bevy_render::{ + render_phase::{ + BinnedPhaseItem, BinnedRenderPhase, BinnedRenderPhasePlugin, CachedRenderPipelinePhaseItem, + DrawFunctionId, PhaseItem, PhaseItemExtraIndex, ViewBinnedRenderPhases, + }, + render_resource::CachedRenderPipelineId, + sync_world::MainEntity, +}; + +use crate::{MeshPass, MeshPipeline, PhaseContext, PhaseItemExt, PhaseItems, RenderPhaseType}; + +const DUMMY_PHASE_ERROR: &str = "Dummy phase should never be instantiated."; + +macro_rules! define_dummy_phase { + ($name:ident) => { + pub struct $name

(PhantomData

); + + impl PhaseItemExt for $name

{ + // Important: It must be empty to ensure it does not match any material. + const PHASE_TYPES: RenderPhaseType = RenderPhaseType::empty(); + + type RenderPhase = BinnedRenderPhase; + type RenderPhases = ViewBinnedRenderPhases; + type PhasePlugin = BinnedRenderPhasePlugin; + + fn queue(_render_phase: &mut Self::RenderPhase, _context: &PhaseContext) { + panic!("{}", DUMMY_PHASE_ERROR) + } + } + + impl PhaseItem for $name

{ + fn entity(&self) -> Entity { + panic!("{}", DUMMY_PHASE_ERROR) + } + + fn main_entity(&self) -> MainEntity { + panic!("{}", DUMMY_PHASE_ERROR) + } + + fn draw_function(&self) -> DrawFunctionId { + panic!("{}", DUMMY_PHASE_ERROR) + } + + fn batch_range(&self) -> &Range { + panic!("{}", DUMMY_PHASE_ERROR) + } + + fn batch_range_mut(&mut self) -> &mut Range { + panic!("{}", DUMMY_PHASE_ERROR) + } + + fn extra_index(&self) -> PhaseItemExtraIndex { + panic!("{}", DUMMY_PHASE_ERROR) + } + + fn batch_range_and_extra_index_mut( + &mut self, + ) -> (&mut Range, &mut PhaseItemExtraIndex) { + panic!("{}", DUMMY_PHASE_ERROR) + } + } + + impl BinnedPhaseItem for $name

{ + type BatchSetKey = Opaque3dBatchSetKey; + type BinKey = Opaque3dBinKey; + + fn new( + _batch_set_key: Self::BatchSetKey, + _bin_key: Self::BinKey, + _representative_entity: (Entity, MainEntity), + _batch_range: Range, + _extra_index: PhaseItemExtraIndex, + ) -> Self { + panic!("{}", DUMMY_PHASE_ERROR) + } + } + + impl CachedRenderPipelinePhaseItem for $name

{ + fn cached_pipeline(&self) -> CachedRenderPipelineId { + panic!("{}", DUMMY_PHASE_ERROR) + } + } + }; +} + +define_dummy_phase!(DummyPhase2); +define_dummy_phase!(DummyPhase3); +define_dummy_phase!(DummyPhase4); + +impl PhaseItems

for PIE +where + P: MeshPass, + PIE: PhaseItemExt, +{ + type Phase1 = PIE; + type Phase2 = DummyPhase2

; + type Phase3 = DummyPhase3

; + type Phase4 = DummyPhase4

; + + fn count() -> usize { + 1 + } +} + +impl PhaseItems

for (PIE1,) +where + P: MeshPass, + PIE1: PhaseItemExt, +{ + type Phase1 = PIE1; + type Phase2 = DummyPhase2

; + type Phase3 = DummyPhase3

; + type Phase4 = DummyPhase4

; + + fn count() -> usize { + 1 + } +} + +impl PhaseItems

for (PIE1, PIE2) +where + P: MeshPass, + PIE1: PhaseItemExt, + PIE2: PhaseItemExt, +{ + type Phase1 = PIE1; + type Phase2 = PIE2; + type Phase3 = DummyPhase3

; + type Phase4 = DummyPhase4

; + + fn count() -> usize { + 2 + } +} + +impl PhaseItems

for (PIE1, PIE2, PIE3) +where + P: MeshPass, + PIE1: PhaseItemExt, + PIE2: PhaseItemExt, + PIE3: PhaseItemExt, +{ + type Phase1 = PIE1; + type Phase2 = PIE2; + type Phase3 = PIE3; + type Phase4 = DummyPhase4

; + + fn count() -> usize { + 3 + } +} + +impl PhaseItems

for (PIE1, PIE2, PIE3, PIE4) +where + P: MeshPass, + PIE1: PhaseItemExt, + PIE2: PhaseItemExt, + PIE3: PhaseItemExt, + PIE4: PhaseItemExt, +{ + type Phase1 = PIE1; + type Phase2 = PIE2; + type Phase3 = PIE3; + type Phase4 = PIE4; + + fn count() -> usize { + 4 + } +} diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index 79163cb648134..3a6270553c0c1 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -30,10 +30,12 @@ mod components; pub mod decal; pub mod deferred; pub mod diagnostic; +mod dummy_phase; mod extended_material; mod fog; mod light_probe; mod lightmap; +mod main_pass; mod material; mod material_bind_groups; mod medium; @@ -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..b48e590131938 --- /dev/null +++ b/crates/bevy_pbr/src/main_pass.rs @@ -0,0 +1,498 @@ +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, + 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, BinnedRenderPhase, BinnedRenderPhasePlugin, BinnedRenderPhaseType, + DrawFunctions, PhaseItemExtraIndex, SortedRenderPhase, SortedRenderPhasePlugin, + ViewBinnedRenderPhases, ViewSortedRenderPhases, + }, + 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); + type RenderCommand = DrawMaterial; +} + +// TODO: Redesign +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, + pub(crate) pass_id: PassId, +} + +impl PipelineSpecializer 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, + } + } + + fn new(pipeline: &Self::Pipeline, material: &PreparedMaterial, pass_id: PassId) -> Self { + MaterialPipelineSpecializer { + pipeline: pipeline.clone(), + properties: material.properties.clone(), + pass_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(self.pass_id)) + { + descriptor.vertex.shader = vertex_shader.clone(); + } + + if let Some(fragment_shader) = self + .properties + .get_shader(MaterialFragmentShader(self.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)?; // , self.pass_id)?; + } + + // 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) + } +} + +impl PhaseItemExt for Opaque3d { + type RenderPhase = BinnedRenderPhase; + type RenderPhases = ViewBinnedRenderPhases; + type PhasePlugin = BinnedRenderPhasePlugin; + const PHASE_TYPES: RenderPhaseType = RenderPhaseType::Opaque; + + fn queue(render_phase: &mut Self::RenderPhase, context: &PhaseContext) { + 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 RenderPhase = BinnedRenderPhase; + type RenderPhases = ViewBinnedRenderPhases; + type PhasePlugin = BinnedRenderPhasePlugin; + const PHASE_TYPES: RenderPhaseType = RenderPhaseType::AlphaMask; + + fn queue(render_phase: &mut Self::RenderPhase, context: &PhaseContext) { + 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 RenderPhase = SortedRenderPhase; + type RenderPhases = ViewSortedRenderPhases; + type PhasePlugin = SortedRenderPhasePlugin; + const PHASE_TYPES: RenderPhaseType = RenderPhaseType::Transmissive; + + fn queue(render_phase: &mut Self::RenderPhase, context: &PhaseContext) { + let (_, index_slab) = context + .mesh_allocator + .mesh_slabs(&context.mesh_instance.mesh_asset_id); + let distance = context + .rangefinder + .distance_translation(&context.mesh_instance.translation) + + context.material.properties.depth_bias; + + render_phase.add(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 RenderPhase = SortedRenderPhase; + type RenderPhases = ViewSortedRenderPhases; + type PhasePlugin = SortedRenderPhasePlugin; + const PHASE_TYPES: RenderPhaseType = RenderPhaseType::Transparent; + + fn queue(render_phase: &mut Self::RenderPhase, context: &PhaseContext) { + let (_, index_slab) = context + .mesh_allocator + .mesh_slabs(&context.mesh_instance.mesh_asset_id); + let distance = context + .rangefinder + .distance_translation(&context.mesh_instance.translation) + + context.material.properties.depth_bias; + + render_phase.add(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 c49619c5179ee..b5f4dbb6befdb 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -1,24 +1,14 @@ -use crate::material_bind_groups::{ - FallbackBindlessResources, MaterialBindGroupAllocator, MaterialBindingId, -}; -use crate::*; use alloc::sync::Arc; + +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_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 +17,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 +48,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 +58,8 @@ use tracing::error; pub const MATERIAL_BIND_GROUP_INDEX: usize = 3; +pub const 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 +131,18 @@ 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.insert(Prepass::id(), ShaderSet::default()); + pass_shaders.insert(DeferredPass::id(), ShaderSet::default()); + pass_shaders.insert(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 +180,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 } @@ -267,73 +274,241 @@ pub trait Material: Asset + AsBindGroup + Clone + Sized { descriptor: &mut RenderPipelineDescriptor, layout: &MeshVertexBufferLayoutRef, key: MaterialPipelineKey, + // pass_id: PassId, ) -> Result<(), SpecializedMeshPipelineError> { Ok(()) } } +/// A set of shaders for a rasterization pass, +/// containing a vertex shader and fragment shader. +#[derive(Default, Clone)] +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 [`bevy_camera::Camera3d`]s to mark views for [`MeshPassPlugin`] rendering. +pub trait MeshPass +where + Self: ExtractComponent + Default, + ::Key: Sync + Send, + >>::Param: ReadOnlySystemParam, + >>::Param: ReadOnlySystemParam, + >>::Param: ReadOnlySystemParam, + >>::Param: ReadOnlySystemParam, +{ + /// 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: PipelineSpecializer; + + /// The [`PhaseItem`]s processed by this pass. + /// + /// Each `PhaseItem` must implement [`PhaseItemExt`]. + /// Currently, the maximum number of `PhaseItem`s in a pass is 4. + /// + /// ## Example + /// ```ignore + /// type PhaseItems = (Opaque3d, AlphaMask3d, Transmissive3d, Transparent3d); + /// ``` + type PhaseItems: PhaseItems; + + // NOTE AFTER #21021: Maybe we can move this to `PhaseItemExt`. + // + // NOTE BEFORE #21021: Theoretically, the relationship between PhaseItem and RenderCommand + // is many-to-many, but I haven't seen many use cases of this. For simplicity + // of implementation, I’m keeping it this way for now. + // + /// The `RenderCommand`s used for rendering [`PhaseItem`]s. + /// See [`RenderCommand`] for more details. + type RenderCommand: Send + + Sync + + RenderCommand> + + RenderCommand> + + RenderCommand> + + RenderCommand>; + + /// The identifier for this pass. + fn id() -> PassId { + PassId::of::() + } +} + +/// A trait for converting a tuple of [`PhaseItem`]s into associated types. +pub trait PhaseItems

{ + type Phase1: PhaseItemExt; + type Phase2: PhaseItemExt; + type Phase3: PhaseItemExt; + type Phase4: PhaseItemExt; + + fn count() -> usize; +} + +// 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 MaterialsPlugin { - /// Debugging flags that can optionally be set when constructing the renderer. +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

{ 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), + add_pass_phase_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 PassPlugin + 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), + ); + } +} + +pub fn add_pass_phase_plugins(app: &mut App, debug_flags: RenderDebugFlags) { + let valid_phase_count = P::PhaseItems::count(); + + if valid_phase_count > 0 { + app.add_plugins(MeshPassPhasePlugin::>::new(0, debug_flags)); + } + if valid_phase_count > 1 { + app.add_plugins(MeshPassPhasePlugin::>::new(1, debug_flags)); + } + if valid_phase_count > 2 { + app.add_plugins(MeshPassPhasePlugin::>::new(2, debug_flags)); + } + if valid_phase_count > 3 { + app.add_plugins(MeshPassPhasePlugin::>::new(3, debug_flags)); + } +} + +struct MeshPassPhasePlugin { + phase_index: usize, + debug_flags: RenderDebugFlags, + _marker: PhantomData<(P, 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 + P: MeshPass, + PIE: PhaseItemExt, + P::RenderCommand: RenderCommand, + >::Param: ReadOnlySystemParam, +{ + fn build(&self, app: &mut App) { + // NOTE: The resource `PIE::RenderPhases` is indirectly initialized `by PIE::PhasePlugin`. + if !app.is_plugin_added::() { + app.add_plugins(PIE::PhasePlugin::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_pass_phase_draw_functions::.with_input(self.phase_index), + ); } } +#[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 +534,7 @@ where app.init_asset::() .register_type::>() .init_resource::>() + .init_resource::() .add_plugins((ErasedRenderAssetPlugin::>::default(),)) .add_systems( PostUpdate, @@ -377,29 +553,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); } } @@ -448,67 +636,29 @@ pub struct MaterialPipeline { pub mesh_pipeline: MeshPipeline, } -pub struct MaterialPipelineSpecializer { - pub(crate) pipeline: MaterialPipeline, - pub(crate) properties: Arc, -} - -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(); - } - - 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) +/// Inserts `PhaseItem`'s `DrawFunction`s into [`PassPhaseDrawFunctions`] by their index in [`PhaseItems`]. +/// +/// This should be called per `PhaseItem`, and the corresponding `RenderCommand` should be registered before calling this. +pub fn insert_pass_phase_draw_functions( + InMut(phase_index): InMut, + mut pass_phase_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) = pass_phase_draw_functions + .entry(P::id()) + .or_default() + .get_mut(*phase_index) + { + *draw = Some(draw_function_id); } } -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 +766,20 @@ 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; + +/// Deprecated alias for [`MaterialExtractionSystems`]. +#[deprecated(since = "0.17.0", note = "Renamed to `MaterialExtractionSystems`.")] +pub type ExtractMaterialsSet = MaterialExtractionSystems; + 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 +830,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 +992,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 +1028,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 `execute_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(&P::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 +1121,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 +1130,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 +1143,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,46 +1196,111 @@ 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, +} + +/// A trait for creating specializer used by [`specialize_material_meshes`]. +pub trait PipelineSpecializer: 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, pass_id: PassId) -> 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

, +); + +type Phase1

= <

::PhaseItems as PhaseItems

>::Phase1; +type Phase2

= <

::PhaseItems as PhaseItems

>::Phase2; +type Phase3

= <

::PhaseItems as PhaseItems

>::Phase3; +type Phase4

= <

::PhaseItems as PhaseItems

>::Phase4; + +type RenderPhase1

= as PhaseItemExt>::RenderPhase; +type RenderPhase2

= as PhaseItemExt>::RenderPhase; +type RenderPhase3

= as PhaseItemExt>::RenderPhase; +type RenderPhase4

= as PhaseItemExt>::RenderPhase; + +type RenderPhases1

= as PhaseItemExt>::RenderPhases; +type RenderPhases2

= as PhaseItemExt>::RenderPhases; +type RenderPhases3

= as PhaseItemExt>::RenderPhases; +type RenderPhases4

= as PhaseItemExt>::RenderPhases; + +pub fn specialize_material_meshes( + (view_render_phases_1, view_render_phases_2, view_render_phases_3, view_render_phases_4): ( + Option>>, + Option>>, + Option>>, + Option>>, + ), 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, -) { +) where + ::Key: Send + Sync, +{ // Record the retained IDs of all shadow views so that we can expire old // pipeline IDs. let mut all_views: HashSet = HashSet::default(); + // All the valid render phase types this pass supports, Dummy phase types are none. + let valid_render_phase_types = Phase1::

::PHASE_TYPES + | Phase2::

::PHASE_TYPES + | Phase3::

::PHASE_TYPES + | Phase4::

::PHASE_TYPES; + 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) - { + // TODO: Move this part to a separate system + let mut all_invalid = true; + if let Some(view_render_phases_1) = view_render_phases_1.as_ref() { + all_invalid &= !view_render_phases_1.contains_key(&view.retained_view_entity); + } + if let Some(view_render_phases_2) = view_render_phases_2.as_ref() { + all_invalid &= !view_render_phases_2.contains_key(&view.retained_view_entity); + } + if let Some(view_render_phases_3) = view_render_phases_3.as_ref() { + all_invalid &= !view_render_phases_3.contains_key(&view.retained_view_entity); + } + if let Some(view_render_phases_4) = view_render_phases_4.as_ref() { + all_invalid &= !view_render_phases_4.contains_key(&view.retained_view_entity); + } + if all_invalid { continue; } @@ -1077,56 +1345,55 @@ 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; - } + // Current status: + // - We iterate over all entities even if they are not valid for this pass, which wastes performance. + // - If an entity's material was valid for this pass previously but becomes invalid in this frame, + // we should remove it from `SpecializedMaterialViewPipelineCache`. However we currently lack a way to + // detect whether a material is valid for this pass. + // + // Two Solutions: + // - Add pass markers to entities so we can filter them here using `visible_entities.iter::>()`. + // - Add `material.is_pass_enabled(P::id())` for checking whether the material is valid for this pass. + + // if material + // .properties + // .get_draw_function(PhaseDrawFunction(P::id())) + // .is_none() + // { + // // Prevent cases where the material was valid previously but switched pass during this frame. + // view_specialized_material_pipeline_cache.remove(visible_entity); + // continue; + // } + + if !valid_render_phase_types.contains(material.properties.render_phase_type) { + // Prevent cases where the material was valid previously but switched phase 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; - } + let lightmap = render_lightmaps.render_lightmaps.get(visible_entity); - 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 has_crossfade = + render_visibility_ranges.entity_has_crossfading_visibility_ranges(*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 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(), }; + + let key = P::Specializer::create_key(&key_context); + + let material_pipeline_specializer = P::Specializer::new(&pipeline, material, P::id()); + let pipeline_id = pipelines.specialize( &pipeline_cache, &material_pipeline_specializer, - erased_key, + key, &mesh.layout, ); let pipeline_id = match pipeline_id { @@ -1147,36 +1414,181 @@ pub fn specialize_material_meshes( .retain(|retained_view_entity, _| all_views.contains(retained_view_entity)); } +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 trait PhaseFamily { +// type Phase: RenderPhase; +// type Phases: ViewRenderPhases + Resource; +// type Plugin: RenderPhasePlugin + Plugin; +// } + +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 PhaseItemExt: PhaseItem { + type RenderPhase: RenderPhase; + type RenderPhases: ViewRenderPhases + Resource; + type PhasePlugin: RenderPhasePlugin + Plugin; + + const PHASE_TYPES: RenderPhaseType; + + fn queue(render_phase: &mut Self::RenderPhase, context: &PhaseContext); +} + +pub trait RenderPhase { + fn add(&mut self, context: &PhaseContext); + + fn validate_cached_entity( + &mut self, + visible_entity: MainEntity, + current_change_tick: Tick, + ) -> bool; +} + +pub trait ViewRenderPhases { + type Phase: RenderPhase; + + fn contains_key(&self, view_entity: &RetainedViewEntity) -> bool; + + fn get_mut(&mut self, view_entity: &RetainedViewEntity) -> Option<&mut Self::Phase>; +} + +impl RenderPhase for BinnedRenderPhase +where + BPI: BinnedPhaseItem + PhaseItemExt>, +{ + #[inline] + fn add(&mut self, context: &PhaseContext) { + BPI::queue(self, context); + } + + #[inline] + fn validate_cached_entity( + &mut self, + visible_entity: MainEntity, + current_change_tick: Tick, + ) -> bool { + self.validate_cached_entity(visible_entity, current_change_tick) + } +} + +impl RenderPhase for SortedRenderPhase +where + SPI: SortedPhaseItem + PhaseItemExt>, +{ + #[inline] + fn add(&mut self, context: &PhaseContext) { + SPI::queue(self, context); + } + + #[inline] + fn validate_cached_entity( + &mut self, + _visible_entity: MainEntity, + _current_change_tick: Tick, + ) -> bool { + false + } +} + +impl ViewRenderPhases for ViewBinnedRenderPhases +where + BPI: BinnedPhaseItem + PhaseItemExt>, +{ + type Phase = BinnedRenderPhase; + + #[inline] + fn contains_key(&self, view_entity: &RetainedViewEntity) -> bool { + self.0.contains_key(view_entity) + } + + #[inline] + fn get_mut(&mut self, view_entity: &RetainedViewEntity) -> Option<&mut Self::Phase> { + self.0.get_mut(view_entity) + } +} + +impl ViewRenderPhases for ViewSortedRenderPhases +where + SPI: SortedPhaseItem + PhaseItemExt>, +{ + type Phase = SortedRenderPhase; + + #[inline] + fn contains_key(&self, view_entity: &RetainedViewEntity) -> bool { + self.0.contains_key(view_entity) + } + + #[inline] + fn get_mut(&mut self, view_entity: &RetainedViewEntity) -> Option<&mut Self::Phase> { + self.0.get_mut(view_entity) + } +} + /// 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( + mut view_render_phases_1: Option>>, + mut view_render_phases_2: Option>>, + mut view_render_phases_3: Option>>, + mut view_render_phases_4: Option>>, 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, + views: Query<(&ExtractedView, &RenderVisibleEntities), With

>, + 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 { + let mut phase1: Option<&mut RenderPhase1

> = view_render_phases_1 + .as_mut() + .and_then(|view_render_phases| view_render_phases.get_mut(&view.retained_view_entity)); + let mut phase2: Option<&mut RenderPhase2

> = view_render_phases_2 + .as_mut() + .and_then(|view_render_phases| view_render_phases.get_mut(&view.retained_view_entity)); + let mut phase3: Option<&mut RenderPhase3

> = view_render_phases_3 + .as_mut() + .and_then(|view_render_phases| view_render_phases.get_mut(&view.retained_view_entity)); + let mut phase4: Option<&mut RenderPhase4

> = view_render_phases_4 + .as_mut() + .and_then(|view_render_phases| view_render_phases.get_mut(&view.retained_view_entity)); + + if phase1.is_none() && phase2.is_none() && phase3.is_none() && phase4.is_none() { continue; - }; + } let Some(view_specialized_material_pipeline_cache) = specialized_material_pipeline_cache.get(&view.retained_view_entity) @@ -1185,6 +1597,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 +1607,21 @@ 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. + let mut any_cached = false; + if let Some(phase1) = phase1.as_mut() { + any_cached |= phase1.validate_cached_entity(*visible_entity, current_change_tick); + } + if let Some(phase2) = phase2.as_mut() { + any_cached |= phase2.validate_cached_entity(*visible_entity, current_change_tick); + } + if let Some(phase3) = phase3.as_mut() { + any_cached |= phase3.validate_cached_entity(*visible_entity, current_change_tick); + } + if let Some(phase4) = phase4.as_mut() { + any_cached |= phase4.validate_cached_entity(*visible_entity, current_change_tick); + } + if any_cached { continue; } @@ -1212,117 +1637,89 @@ 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_translation(&mesh_instance.translation) - + 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_translation(&mesh_instance.translation) - + 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(), - }); - } + // NOTE: Because one pass could have phases with same phase types, + // we don't use mutual exclusion conditions here. + let phase_type = material.properties.render_phase_type; + if Phase1::

::PHASE_TYPES.contains(phase_type) + && let Some(phase1) = phase1.as_mut() + && let Some(draw_function) = material + .properties + .get_draw_function(Phase1DrawFunction(P::id())) + { + let context = PhaseContext { + mesh_instance: &mesh_instance, + material, + mesh_allocator: &mesh_allocator, + entity: *render_entity, + main_entity: *visible_entity, + draw_function, + pipeline_id, + current_change_tick, + gpu_preprocessing_support: *gpu_preprocessing_support, + rangefinder: &rangefinder, + }; + phase1.add(&context); + } + if Phase2::

::PHASE_TYPES.contains(phase_type) + && let Some(phase2) = phase2.as_mut() + && let Some(draw_function) = material + .properties + .get_draw_function(Phase2DrawFunction(P::id())) + { + let context = PhaseContext { + mesh_instance: &mesh_instance, + material, + mesh_allocator: &mesh_allocator, + entity: *render_entity, + main_entity: *visible_entity, + draw_function, + pipeline_id, + current_change_tick, + gpu_preprocessing_support: *gpu_preprocessing_support, + rangefinder: &rangefinder, + }; + phase2.add(&context); } + if Phase3::

::PHASE_TYPES.contains(phase_type) + && let Some(phase3) = phase3.as_mut() + && let Some(draw_function) = material + .properties + .get_draw_function(Phase3DrawFunction(P::id())) + { + let context = PhaseContext { + mesh_instance: &mesh_instance, + material, + mesh_allocator: &mesh_allocator, + entity: *render_entity, + main_entity: *visible_entity, + draw_function, + pipeline_id, + current_change_tick, + gpu_preprocessing_support: *gpu_preprocessing_support, + rangefinder: &rangefinder, + }; + phase3.add(&context); + } + if Phase4::

::PHASE_TYPES.contains(phase_type) + && let Some(phase4) = phase4.as_mut() + && let Some(draw_function) = material + .properties + .get_draw_function(Phase4DrawFunction(P::id())) + { + let context = PhaseContext { + mesh_instance: &mesh_instance, + material, + mesh_allocator: &mesh_allocator, + entity: *render_entity, + main_entity: *visible_entity, + draw_function, + pipeline_id, + current_change_tick, + gpu_preprocessing_support: *gpu_preprocessing_support, + rangefinder: &rangefinder, + }; + phase4.add(&context); + }; } } } @@ -1377,23 +1774,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)] +pub struct MaterialVertexShader(pub PassId); -#[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, 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,24 +1789,17 @@ 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)] +pub struct Phase1DrawFunction(pub PassId); -#[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)] +pub struct Phase2DrawFunction(pub PassId); -#[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)] +pub struct Phase3DrawFunction(pub PassId); + +#[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone)] +pub struct Phase4DrawFunction(pub PassId); #[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone, Default)] pub struct ShadowsDrawFunction; @@ -1524,8 +1902,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, @@ -1543,6 +1921,7 @@ pub struct MaterialProperties { &mut RenderPipelineDescriptor, &MeshVertexBufferLayoutRef, ErasedMaterialPipelineKey, + // PassId, ) -> Result<(), SpecializedMeshPipelineError>, >, /// The key for this material, typically a bitfield of flags that are used to modify @@ -1584,15 +1963,34 @@ impl MaterialProperties { } } -#[derive(Clone, Copy, Default)] -pub enum RenderPhaseType { - #[default] - Opaque, - AlphaMask, - Transmissive, - Transparent, +bitflags::bitflags! { + // 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. + + /// 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 PassPhaseDrawFunctions(HashMap); + +/// Stores the draw functions for each `PhaseItem` of a `MeshPass`. +#[derive(Default, Deref, DerefMut)] +pub struct PhaseDrawFunctions([Option; 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 +2019,8 @@ where SRes, SResMut, SResMut, - SRes>, - SRes>, - SRes>, - SRes>, - SRes>, - SRes>, - SRes>, - SRes>, SRes>, + SRes, SRes, M::Param, ); @@ -1643,15 +2034,8 @@ 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, + pass_phase_draw_functions, asset_server, material_param, ): &mut SystemParamItem, @@ -1659,41 +2043,10 @@ where 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 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 +2063,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 +2077,89 @@ 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) = pass_phase_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((Phase1DrawFunction(pass_id).intern(), *draw_function)); + }; + + if let Some(draw_function) = phase_draw_functions + .get(1) + .expect(PHASE_DRAW_FUNCTIONS_ERROR) + { + draw_functions.push((Phase2DrawFunction(pass_id).intern(), *draw_function)); + }; + + if let Some(draw_function) = phase_draw_functions + .get(2) + .expect(PHASE_DRAW_FUNCTIONS_ERROR) + { + draw_functions.push((Phase3DrawFunction(pass_id).intern(), *draw_function)); + }; + + if let Some(draw_function) = phase_draw_functions + .get(3) + .expect(PHASE_DRAW_FUNCTIONS_ERROR) + { + draw_functions.push((Phase4DrawFunction(pass_id).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); + } #[cfg(feature = "meshlet")] { @@ -1764,6 +2185,7 @@ where descriptor: &mut RenderPipelineDescriptor, mesh_layout: &MeshVertexBufferLayoutRef, erased_key: ErasedMaterialPipelineKey, + // pass_id: PassId, ) -> Result<(), SpecializedMeshPipelineError> where M::Data: Hash + Clone, @@ -1777,6 +2199,7 @@ where mesh_key: erased_key.mesh_key, bind_group_data: material_key, }, + // pass_id, ) } diff --git a/crates/bevy_pbr/src/meshlet/material_pipeline_prepare.rs b/crates/bevy_pbr/src/meshlet/material_pipeline_prepare.rs index e00c41900146d..f0e978cac184f 100644 --- a/crates/bevy_pbr/src/meshlet/material_pipeline_prepare.rs +++ b/crates/bevy_pbr/src/meshlet/material_pipeline_prepare.rs @@ -166,6 +166,7 @@ pub fn prepare_material_meshlet_meshes_main_opaque_pass( let material_pipeline_specializer = MaterialPipelineSpecializer { pipeline: material_pipeline.clone(), properties: material.properties.clone(), + pass_id: MainPass::id(), }; let Ok(material_pipeline_descriptor) = material_pipeline_specializer.specialize(erased_key, fake_vertex_buffer_layout) @@ -338,6 +339,7 @@ pub fn prepare_material_meshlet_meshes_prepass( let material_pipeline_specializer = PrepassPipelineSpecializer { pipeline: prepass_pipeline.clone(), properties: material.properties.clone(), + _pass_id: Prepass::id(), }; let Ok(material_pipeline_descriptor) = material_pipeline_specializer.specialize(erased_key, fake_vertex_buffer_layout) diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index dcffe3d295377..ea6dbcac96f1b 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -1,16 +1,6 @@ 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}; @@ -25,25 +15,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, 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 +39,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 +72,7 @@ impl Plugin for PrepassPipelinePlugin { .add_systems( Render, prepare_prepass_view_bind_group.in_set(RenderSystems::PrepareBindGroups), - ) - .init_resource::>(); + ); } } @@ -119,8 +98,15 @@ 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(MeshPassPlugin::::new(self.debug_flags)) + .add_plugins(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 +115,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 +131,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 +147,29 @@ 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); + type RenderCommand = DrawPrepass; +} + +// 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); + type RenderCommand = DrawPrepass; +} + #[derive(Resource)] struct AnyPrepassPluginLoaded; @@ -322,7 +309,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", &[]), @@ -330,9 +317,278 @@ pub fn init_prepass_pipeline( }); } +// Extract the render phases for the prepass +pub fn extract_camera_previous_view_data( + mut commands: Commands, + cameras_3d: Extract), With>>, +) { + for (entity, camera, maybe_previous_view_data) in cameras_3d.iter() { + let mut entity = commands + .get_entity(entity) + .expect("Camera entity wasn't synced."); + if camera.is_active { + if let Some(previous_view_data) = maybe_previous_view_data { + entity.insert(previous_view_data.clone()); + } + } else { + entity.remove::(); + } + } +} + +pub fn prepare_previous_view_uniforms( + mut commands: Commands, + render_device: Res, + render_queue: Res, + mut previous_view_uniforms: ResMut, + views: Query< + (Entity, &ExtractedView, Option<&PreviousViewData>), + Or<(With, With)>, + >, +) { + let views_iter = views.iter(); + let view_count = views_iter.len(); + let Some(mut writer) = + previous_view_uniforms + .uniforms + .get_writer(view_count, &render_device, &render_queue) + else { + return; + }; + + for (entity, camera, maybe_previous_view_uniforms) in views_iter { + let prev_view_data = match maybe_previous_view_uniforms { + Some(previous_view) => previous_view.clone(), + None => { + let world_from_view = camera.world_from_view.affine(); + let view_from_world = Mat4::from(world_from_view.inverse()); + let view_from_clip = camera.clip_from_view.inverse(); + + PreviousViewData { + view_from_world, + clip_from_world: camera.clip_from_view * view_from_world, + clip_from_view: camera.clip_from_view, + world_from_clip: Mat4::from(world_from_view) * view_from_clip, + view_from_clip, + } + } + }; + + commands.entity(entity).insert(PreviousViewUniformOffset { + offset: writer.write(&prev_view_data), + }); + } +} + +#[derive(Resource)] +pub struct PrepassViewBindGroup { + pub motion_vectors: Option, + pub no_motion_vectors: Option, + pub empty_bind_group: BindGroup, +} + +pub fn init_prepass_view_bind_group( + mut commands: Commands, + render_device: Res, + pipeline_cache: Res, + pipeline: Res, +) { + let empty_bind_group = render_device.create_bind_group( + "prepass_view_empty_bind_group", + &pipeline_cache.get_bind_group_layout(&pipeline.empty_layout), + &[], + ); + commands.insert_resource(PrepassViewBindGroup { + motion_vectors: None, + no_motion_vectors: None, + empty_bind_group, + }); +} + +pub fn prepare_prepass_view_bind_group( + render_device: Res, + pipeline_cache: Res, + prepass_pipeline: Res, + view_uniforms: Res, + globals_buffer: Res, + previous_view_uniforms: Res, + visibility_ranges: Res, + mut prepass_view_bind_group: ResMut, +) { + if let (Some(view_binding), Some(globals_binding), Some(visibility_ranges_buffer)) = ( + view_uniforms.uniforms.binding(), + globals_buffer.buffer.binding(), + visibility_ranges.buffer().buffer(), + ) { + prepass_view_bind_group.no_motion_vectors = Some(render_device.create_bind_group( + "prepass_view_no_motion_vectors_bind_group", + &pipeline_cache.get_bind_group_layout(&prepass_pipeline.view_layout_no_motion_vectors), + &BindGroupEntries::with_indices(( + (0, view_binding.clone()), + (1, globals_binding.clone()), + (14, visibility_ranges_buffer.as_entire_binding()), + )), + )); + + if let Some(previous_view_uniforms_binding) = previous_view_uniforms.uniforms.binding() { + prepass_view_bind_group.motion_vectors = Some(render_device.create_bind_group( + "prepass_view_motion_vectors_bind_group", + &pipeline_cache.get_bind_group_layout(&prepass_pipeline.view_layout_motion_vectors), + &BindGroupEntries::with_indices(( + (0, view_binding), + (1, globals_binding), + (2, previous_view_uniforms_binding), + (14, visibility_ranges_buffer.as_entire_binding()), + )), + )); + } + } +} + +// /// 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>, + 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, 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; + } + if normal_prepass.is_some() { + view_key |= MeshPipelineKey::NORMAL_PREPASS; + } + 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 { + view_key_cache.insert(view.retained_view_entity, view_key); + view_specialization_ticks.insert(view.retained_view_entity, ticks.this_run()); + } + } else { + view_key_cache.insert(view.retained_view_entity, view_key); + view_specialization_ticks.insert(view.retained_view_entity, ticks.this_run()); + } + } +} + pub struct PrepassPipelineSpecializer { pub(crate) pipeline: PrepassPipeline, pub(crate) properties: Arc, + pub(crate) _pass_id: PassId, +} + +impl PipelineSpecializer 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, + } + } + + fn new(pipeline: &Self::Pipeline, material: &PreparedMaterial, _pass_id: PassId) -> Self { + PrepassPipelineSpecializer { + pipeline: pipeline.clone(), + properties: material.properties.clone(), + _pass_id, + } + } } impl SpecializedMeshPipeline for PrepassPipelineSpecializer { @@ -367,6 +623,9 @@ impl SpecializedMeshPipeline for PrepassPipelineSpecializer { } } +// NOTE: For simplicity of implementation, we directly hardcode the PassId in `ShaderLabel`s. +// However, if we want to make it more general, we need a method to pass the PassId from +// the outside. impl PrepassPipeline { fn specialize( &self, @@ -393,7 +652,7 @@ 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. @@ -540,18 +799,18 @@ impl PrepassPipeline { || emulate_unclipped_depth || (mesh_key.contains(MeshPipelineKey::MAY_DISCARD) && material_properties - .get_shader(PrepassFragmentShader) + .get_shader(MaterialFragmentShader(Prepass::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) { + match material_properties.get_shader(MaterialFragmentShader(DeferredPass::id())) { Some(frag_shader_handle) => frag_shader_handle, None => self.default_prepass_shader.clone(), } } else { - match material_properties.get_shader(PrepassFragmentShader) { + match material_properties.get_shader(MaterialFragmentShader(Prepass::id())) { Some(frag_shader_handle) => frag_shader_handle, None => self.default_prepass_shader.clone(), } @@ -567,12 +826,16 @@ 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) { + if let Some(handle) = + material_properties.get_shader(MaterialVertexShader(DeferredPass::id())) + { handle } else { self.default_prepass_shader.clone() } - } else if let Some(handle) = material_properties.get_shader(PrepassVertexShader) { + } else if let Some(handle) = + material_properties.get_shader(MaterialVertexShader(Prepass::id())) + { handle } else { self.default_prepass_shader.clone() @@ -619,595 +882,146 @@ impl PrepassPipeline { } } -// Extract the render phases for the prepass -pub fn extract_camera_previous_view_data( - mut commands: Commands, - cameras_3d: Extract), With>>, -) { - for (entity, camera, maybe_previous_view_data) in cameras_3d.iter() { - let mut entity = commands - .get_entity(entity) - .expect("Camera entity wasn't synced."); - if camera.is_active { - if let Some(previous_view_data) = maybe_previous_view_data { - entity.insert(previous_view_data.clone()); - } - } else { - entity.remove::(); - } - } -} - -pub fn prepare_previous_view_uniforms( - mut commands: Commands, - render_device: Res, - render_queue: Res, - mut previous_view_uniforms: ResMut, - views: Query< - (Entity, &ExtractedView, Option<&PreviousViewData>), - Or<(With, With)>, - >, -) { - let views_iter = views.iter(); - let view_count = views_iter.len(); - let Some(mut writer) = - previous_view_uniforms - .uniforms - .get_writer(view_count, &render_device, &render_queue) - else { - return; - }; - - for (entity, camera, maybe_previous_view_uniforms) in views_iter { - let prev_view_data = match maybe_previous_view_uniforms { - Some(previous_view) => previous_view.clone(), - None => { - let world_from_view = camera.world_from_view.affine(); - let view_from_world = Mat4::from(world_from_view.inverse()); - let view_from_clip = camera.clip_from_view.inverse(); - - PreviousViewData { - view_from_world, - clip_from_world: camera.clip_from_view * view_from_world, - clip_from_view: camera.clip_from_view, - world_from_clip: Mat4::from(world_from_view) * view_from_clip, - view_from_clip, - } - } - }; - - commands.entity(entity).insert(PreviousViewUniformOffset { - offset: writer.write(&prev_view_data), - }); - } -} - -#[derive(Resource)] -pub struct PrepassViewBindGroup { - pub motion_vectors: Option, - pub no_motion_vectors: Option, - pub empty_bind_group: BindGroup, -} - -pub fn init_prepass_view_bind_group( - mut commands: Commands, - render_device: Res, - pipeline_cache: Res, - pipeline: Res, -) { - let empty_bind_group = render_device.create_bind_group( - "prepass_view_empty_bind_group", - &pipeline_cache.get_bind_group_layout(&pipeline.empty_layout), - &[], - ); - commands.insert_resource(PrepassViewBindGroup { - motion_vectors: None, - no_motion_vectors: None, - empty_bind_group, - }); -} - -pub fn prepare_prepass_view_bind_group( - render_device: Res, - pipeline_cache: Res, - prepass_pipeline: Res, - view_uniforms: Res, - globals_buffer: Res, - previous_view_uniforms: Res, - visibility_ranges: Res, - mut prepass_view_bind_group: ResMut, -) { - if let (Some(view_binding), Some(globals_binding), Some(visibility_ranges_buffer)) = ( - view_uniforms.uniforms.binding(), - globals_buffer.buffer.binding(), - visibility_ranges.buffer().buffer(), - ) { - prepass_view_bind_group.no_motion_vectors = Some(render_device.create_bind_group( - "prepass_view_no_motion_vectors_bind_group", - &pipeline_cache.get_bind_group_layout(&prepass_pipeline.view_layout_no_motion_vectors), - &BindGroupEntries::with_indices(( - (0, view_binding.clone()), - (1, globals_binding.clone()), - (14, visibility_ranges_buffer.as_entire_binding()), - )), - )); - - if let Some(previous_view_uniforms_binding) = previous_view_uniforms.uniforms.binding() { - prepass_view_bind_group.motion_vectors = Some(render_device.create_bind_group( - "prepass_view_motion_vectors_bind_group", - &pipeline_cache.get_bind_group_layout(&prepass_pipeline.view_layout_motion_vectors), - &BindGroupEntries::with_indices(( - (0, view_binding), - (1, globals_binding), - (2, previous_view_uniforms_binding), - (14, visibility_ranges_buffer.as_entire_binding()), - )), - )); +impl PhaseItemExt for Opaque3dPrepass { + type RenderPhase = BinnedRenderPhase; + type RenderPhases = ViewBinnedRenderPhases; + type PhasePlugin = BinnedRenderPhasePlugin; + const PHASE_TYPES: RenderPhaseType = RenderPhaseType::Opaque; + + fn queue(render_phase: &mut Self::RenderPhase, context: &PhaseContext) { + 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, + ); } } } -/// 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, - mut views: Query<( - &ExtractedView, - &Msaa, - Option<&DepthPrepass>, - Option<&NormalPrepass>, - Option<&MotionVectorPrepass>, - )>, - ticks: SystemChangeTick, -) { - for (view, msaa, depth_prepass, normal_prepass, motion_vector_prepass) in views.iter_mut() { - let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples()); - if depth_prepass.is_some() { - view_key |= MeshPipelineKey::DEPTH_PREPASS; - } - if normal_prepass.is_some() { - view_key |= MeshPipelineKey::NORMAL_PREPASS; - } - if motion_vector_prepass.is_some() { - view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS; - } - - if let Some(current_key) = view_key_cache.get_mut(&view.retained_view_entity) { - if *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()); - } - } else { - view_key_cache.insert(view.retained_view_entity, view_key); - view_specialization_ticks.insert(view.retained_view_entity, ticks.this_run()); +impl PhaseItemExt for Opaque3dDeferred { + type RenderPhase = BinnedRenderPhase; + type RenderPhases = ViewBinnedRenderPhases; + type PhasePlugin = BinnedRenderPhasePlugin; + const PHASE_TYPES: RenderPhaseType = RenderPhaseType::Opaque; + + fn queue(render_phase: &mut Self::RenderPhase, context: &PhaseContext) { + 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, + ); } } } -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; - }; - - 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; +impl PhaseItemExt for AlphaMask3dPrepass { + type RenderPhase = BinnedRenderPhase; + type RenderPhases = ViewBinnedRenderPhases; + type PhasePlugin = BinnedRenderPhasePlugin; + const PHASE_TYPES: RenderPhaseType = RenderPhaseType::AlphaMask; + + fn queue(render_phase: &mut Self::RenderPhase, context: &PhaseContext) { + 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 Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity) - else { - continue; + let bin_key = OpaqueNoLightmap3dBinKey { + asset_id: context.mesh_instance.mesh_asset_id.into(), }; - 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 && !material.properties.shadows_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; - } - - let forward = match material.properties.render_method { - OpaqueRendererMethod::Forward => true, - OpaqueRendererMethod::Deferred => false, - OpaqueRendererMethod::Auto => unreachable!(), - }; - - let deferred = deferred_prepass.is_some() && !forward; - - if deferred { - mesh_key |= MeshPipelineKey::DEFERRED_PREPASS; - } - - 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; - - if lightmap.bicubic_sampling && deferred { - mesh_key |= MeshPipelineKey::LIGHTMAP_BICUBIC_SAMPLING; - } - } - - if render_visibility_ranges.entity_has_crossfading_visibility_ranges(*visible_entity) { - mesh_key |= MeshPipelineKey::VISIBILITY_RANGE_DITHER; - } - - // 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; - } - } - - let erased_key = ErasedMaterialPipelineKey { - mesh_key, - material_key: material.properties.material_key.clone(), - type_id: material_instance.asset_id.type_id(), - }; - let prepass_pipeline_specializer = PrepassPipelineSpecializer { - pipeline: prepass_pipeline.clone(), - properties: material.properties.clone(), - }; - 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; - }; - - // Skip if there's no place to put the mesh. - if opaque_phase.is_none() - && alpha_mask_phase.is_none() - && opaque_deferred_phase.is_none() - && alpha_mask_deferred_phase.is_none() - { - continue; - } - - for (render_entity, visible_entity) in visible_entities.iter::() { - let Some((current_change_tick, pipeline_id)) = - view_specialized_material_pipeline_cache.get(visible_entity) - else { - continue; +impl PhaseItemExt for AlphaMask3dDeferred { + type RenderPhase = BinnedRenderPhase; + type RenderPhases = ViewBinnedRenderPhases; + type PhasePlugin = BinnedRenderPhasePlugin; + const PHASE_TYPES: RenderPhaseType = RenderPhaseType::AlphaMask; + + fn queue(render_phase: &mut Self::RenderPhase, context: &PhaseContext) { + 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, }; - - // 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; - } - - 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; - }; - 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/prepass/prepass.rs b/crates/bevy_pbr/src/prepass/prepass.rs new file mode 100644 index 0000000000000..33aa4cb4499d9 --- /dev/null +++ b/crates/bevy_pbr/src/prepass/prepass.rs @@ -0,0 +1,10 @@ +use bevy_ecs::prelude::*; +use bevy_app::{App, Plugin}; +use bevy_ecs::system::command; +use bevy_render::extract_component::ExtractComponent; +use bevy_transform::commands; + +use crate::Pass; + + + diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index f6640a63985e7..6bed39be17c3a 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -1765,7 +1765,7 @@ pub fn specialize_shadows( Res, ), shadow_render_phases: Res>, - mut pipelines: ResMut>, + mut pipelines: ResMut>, pipeline_cache: Res, render_lightmaps: Res, view_lights: Query<(Entity, &ViewLightEntities), With>, @@ -1911,6 +1911,7 @@ pub fn specialize_shadows( let material_pipeline_specializer = PrepassPipelineSpecializer { pipeline: prepass_pipeline.clone(), properties: material.properties.clone(), + _pass_id: Prepass::id(), }; let pipeline_id = pipelines.specialize( &pipeline_cache, diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index a4b4e07c62747..43329c903e6dc 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, }; @@ -22,10 +21,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, @@ -44,17 +40,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}; @@ -76,17 +69,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}; @@ -156,16 +139,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 @@ -212,14 +186,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::(); @@ -298,147 +266,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 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, @@ -2681,7 +2508,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 0468b7c0681ad..041d50fb8a755 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -1,7 +1,7 @@ use crate::{ - DrawMesh, MeshPipeline, MeshPipelineKey, RenderMeshInstanceFlags, RenderMeshInstances, - SetMeshBindGroup, SetMeshViewBindGroup, SetMeshViewBindingArrayBindGroup, ViewKeyCache, - ViewSpecializationTicks, + DrawMesh, MainPass, MeshPipeline, MeshPipelineKey, RenderMeshInstanceFlags, + RenderMeshInstances, SetMeshBindGroup, SetMeshViewBindGroup, SetMeshViewBindingArrayBindGroup, + ViewKeyCache, ViewSpecializationTicks, }; use bevy_app::{App, Plugin, PostUpdate, Startup, Update}; use bevy_asset::{ @@ -750,9 +750,9 @@ pub fn specialize_wireframes( render_visibility_ranges: Res, wireframe_phases: Res>, views: Query<(&ExtractedView, &RenderVisibleEntities)>, - view_key_cache: Res, + 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, diff --git a/crates/bevy_shader/src/shader.rs b/crates/bevy_shader/src/shader.rs index 577fb06ee2e95..9dde3857fbee1 100644 --- a/crates/bevy_shader/src/shader.rs +++ b/crates/bevy_shader/src/shader.rs @@ -435,7 +435,7 @@ impl ShaderImport { } /// A reference to a shader asset. -#[derive(Default)] +#[derive(Default, Clone)] pub enum ShaderRef { /// Use the "default" shader for the current context. #[default] diff --git a/examples/shader_advanced/custom_mesh_pass.rs b/examples/shader_advanced/custom_mesh_pass.rs new file mode 100644 index 0000000000000..a3cb3fc7e6e0b --- /dev/null +++ b/examples/shader_advanced/custom_mesh_pass.rs @@ -0,0 +1,105 @@ +//! Demonstrates how to use `MeshPass`. +//! +//! TODO: Documentation + +use bevy::{ + core_pipeline::core_3d::{AlphaMask3d, Opaque3d}, + pbr::{ + DrawMesh, MainPass, MaterialPipelineSpecializer, MeshPass, MeshPassPlugin, PassShaders, + SetMaterialBindGroup, SetMeshBindGroup, SetMeshViewBindGroup, + SetMeshViewBindingArrayBindGroup, ShaderSet, MATERIAL_BIND_GROUP_INDEX, + }, + prelude::*, +}; +use bevy_render::{ + extract_component::ExtractComponent, render_phase::SetItemPipeline, + render_resource::AsBindGroup, +}; + +const SHADER_ASSET_PATH: &str = "shaders/custom_mesh_pass_material.wgsl"; +const OUTLINE_SHADER_ASSET_PATH: &str = "shaders/custom_mesh_pass_outline_material.wgsl"; + +fn main() { + let mut app = App::new(); + app.add_plugins((DefaultPlugins, MaterialPlugin::::default())) + // You can use `register_required_components` to add our `HullOutlinePass` to all cameras. + // .register_required_components::() + .add_plugins(MeshPassPlugin::::default()) + .add_systems(Startup, setup) + .run(); +} + +type DrawMaterial2 = ( + SetItemPipeline, + SetMeshViewBindGroup<0>, + SetMeshViewBindingArrayBindGroup<1>, + SetMeshBindGroup<2>, + SetMaterialBindGroup, + DrawMesh, +); + +#[derive(Clone, Copy, Default, Component, ExtractComponent)] +struct HullOutlinePass; + +impl MeshPass for HullOutlinePass { + type ViewKeySource = MainPass; + type Specializer = MaterialPipelineSpecializer; + type PhaseItems = (Opaque3d, AlphaMask3d); + type RenderCommand = DrawMaterial2; +} + +#[derive(Asset, TypePath, AsBindGroup, Clone)] +struct CustomMaterial { + #[uniform(0)] + color: LinearRgba, + #[uniform(1)] + outline_color: LinearRgba, +} + +impl Material for CustomMaterial { + fn shaders() -> PassShaders { + let mut pass_shaders = PassShaders::default(); + // Add HullOutlinePass shaders + pass_shaders.insert( + HullOutlinePass::id(), + ShaderSet { + vertex: OUTLINE_SHADER_ASSET_PATH.into(), + fragment: OUTLINE_SHADER_ASSET_PATH.into(), + }, + ); + // This won't work! We can't reuse the same `PhaseItem`s in one material. + // pass_shaders.insert( + // MainPass::id(), + // ShaderSet { + // vertex: SHADER_ASSET_PATH.into(), + // fragment: SHADER_ASSET_PATH.into(), + // }, + // ); + pass_shaders + } +} + +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + // Spawn the cube. + commands.spawn(( + MeshMaterial3d(materials.add(CustomMaterial { + color: LinearRgba::new(0.8, 0.5, 0.6, 1.0), + outline_color: LinearRgba::new(1.0, 1.0, 1.0, 1.0), + })), + Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 1.0))), + Transform::from_xyz(0.0, 0.5, 0.0), + )); + + // Spawn the 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. + HullOutlinePass, + )); +} diff --git a/examples/shader_advanced/manual_material.rs b/examples/shader_advanced/manual_material.rs index f7ac7c66cdec7..da9ee1ecef216 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, Phase1DrawFunction, PreparedMaterial, + RenderMaterialBindings, RenderMaterialInstance, RenderMaterialInstances, + SpecializedMaterialPipelineCache, }, platform::collections::hash_map::Entry, prelude::*, @@ -200,8 +201,11 @@ 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(Phase1DrawFunction(MainPass::id()), draw_function_id); + properties.add_shader( + MaterialFragmentShader(MainPass::id()), + asset_server.load(SHADER_ASSET_PATH), + ); Ok(PreparedMaterial { binding, @@ -297,7 +301,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 +335,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>, ) {