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, DeferredDrawFunction, DeferredFragmentShader, DeferredVertexShader, DrawMesh, EntitySpecializationTicks, ErasedMaterialPipelineKey, Material, MaterialPipeline, MaterialProperties, MeshLayouts, MeshPipeline, MeshPipelineKey, OpaqueRendererMethod, PreparedMaterial, PrepassDrawFunction, PrepassFragmentShader, PrepassVertexShader, RenderLightmaps, RenderMaterialInstances, RenderMeshInstanceFlags, RenderMeshInstances, RenderPhaseType, SetMaterialBindGroup, SetMeshBindGroup, ShadowView, }; use bevy_app::{App, Plugin, PreUpdate}; use bevy_render::{ alpha::AlphaMode, batching::gpu_preprocessing::GpuPreprocessingSupport, load_shader_library, mesh::{allocator::MeshAllocator, Mesh3d, MeshVertexBufferLayoutRef, RenderMesh}, render_asset::prepare_assets, render_resource::binding_types::uniform_buffer, renderer::RenderAdapter, sync_world::RenderEntity, view::{RenderVisibilityRanges, RetainedViewEntity, VISIBILITY_RANGES_STORAGE_BUFFER_COUNT}, ExtractSchedule, Render, RenderApp, RenderDebugFlags, RenderStartup, RenderSystems, }; pub use prepass_bindings::*; use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle}; use bevy_core_pipeline::{ core_3d::CORE_3D_DEPTH_FORMAT, deferred::*, prelude::Camera3d, prepass::*, }; use bevy_ecs::{ prelude::*, system::{ lifetimeless::{Read, SRes}, SystemParamItem, }, }; use bevy_math::{Affine3A, Vec4}; use bevy_render::{ globals::{GlobalsBuffer, GlobalsUniform}, prelude::{Camera, Mesh}, render_phase::*, render_resource::*, renderer::{RenderDevice, RenderQueue}, view::{ExtractedView, Msaa, ViewUniform, ViewUniformOffset, ViewUniforms}, Extract, }; use bevy_transform::prelude::GlobalTransform; use tracing::{error, warn}; #[cfg(feature = "meshlet")] use crate::meshlet::{ prepare_material_meshlet_meshes_prepass, queue_material_meshlet_meshes, InstanceManager, MeshletMesh3d, }; use alloc::sync::Arc; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{component::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_utils::default; use core::marker::PhantomData; /// Sets up everything required to use the prepass pipeline. /// /// This does not add the actual prepasses, see [`PrepassPlugin`] for that. pub struct PrepassPipelinePlugin; impl Plugin for PrepassPipelinePlugin { fn build(&self, app: &mut App) { embedded_asset!(app, "prepass.wgsl"); load_shader_library!(app, "prepass_bindings.wgsl"); load_shader_library!(app, "prepass_utils.wgsl"); load_shader_library!(app, "prepass_io.wgsl"); let Some(render_app) = app.get_sub_app_mut(RenderApp) else { return; }; render_app .add_systems( RenderStartup, ( init_prepass_pipeline.after(init_material_pipeline), init_prepass_view_bind_group, ) .chain(), ) .add_systems( Render, prepare_prepass_view_bind_group.in_set(RenderSystems::PrepareBindGroups), ) .init_resource::>(); } } /// Sets up the prepasses for a material. /// /// This depends on the [`PrepassPipelinePlugin`]. pub struct PrepassPlugin { /// Debugging flags that can optionally be set when constructing the renderer. pub debug_flags: RenderDebugFlags, } impl PrepassPlugin { /// Creates a new [`PrepassPlugin`] with the given debug flags. pub fn new(debug_flags: RenderDebugFlags) -> Self { PrepassPlugin { debug_flags } } } impl Plugin for PrepassPlugin { fn build(&self, app: &mut App) { let no_prepass_plugin_loaded = app .world() .get_resource::() .is_none(); if no_prepass_plugin_loaded { app.insert_resource(AnyPrepassPluginLoaded) // 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( PreUpdate, ( 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 { return; }; if no_prepass_plugin_loaded { render_app .add_systems(ExtractSchedule, extract_camera_previous_view_data) .add_systems( Render, prepare_previous_view_uniforms.in_set(PrepareResources), ); } 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), ), ); #[cfg(feature = "meshlet")] render_app.add_systems( Render, prepare_material_meshlet_meshes_prepass .in_set(RenderSystems::QueueMeshes) .before(queue_material_meshlet_meshes) .run_if(resource_exists::), ); } } /// Marker resource for whether prepass is enabled globally for this material type #[derive(Resource, Debug)] pub struct PrepassEnabled(PhantomData); impl Default for PrepassEnabled { fn default() -> Self { PrepassEnabled(PhantomData) } } #[derive(Resource)] struct AnyPrepassPluginLoaded; pub fn update_previous_view_data( mut commands: Commands, query: Query<(Entity, &Camera, &GlobalTransform), Or<(With, With)>>, ) { for (entity, camera, camera_transform) in &query { let world_from_view = camera_transform.to_matrix(); let view_from_world = world_from_view.inverse(); let view_from_clip = camera.clip_from_view().inverse(); commands.entity(entity).try_insert(PreviousViewData { view_from_world, clip_from_world: camera.clip_from_view() * view_from_world, clip_from_view: camera.clip_from_view(), world_from_clip: world_from_view * view_from_clip, view_from_clip, }); } } #[derive(Component, PartialEq, Default)] pub struct PreviousGlobalTransform(pub Affine3A); #[cfg(not(feature = "meshlet"))] type PreviousMeshFilter = With; #[cfg(feature = "meshlet")] type PreviousMeshFilter = Or<(With, With)>; pub fn update_mesh_previous_global_transforms( mut commands: Commands, views: Query<&Camera, Or<(With, With)>>, new_meshes: Query< (Entity, &GlobalTransform), (PreviousMeshFilter, Without), >, mut meshes: Query<(&GlobalTransform, &mut PreviousGlobalTransform), PreviousMeshFilter>, ) { let should_run = views.iter().any(|camera| camera.is_active); if should_run { for (entity, transform) in &new_meshes { let new_previous_transform = PreviousGlobalTransform(transform.affine()); commands.entity(entity).try_insert(new_previous_transform); } meshes.par_iter_mut().for_each(|(transform, mut previous)| { previous.set_if_neq(PreviousGlobalTransform(transform.affine())); }); } } #[derive(Resource, Clone)] pub struct PrepassPipeline { pub view_layout_motion_vectors: BindGroupLayout, pub view_layout_no_motion_vectors: BindGroupLayout, pub mesh_layouts: MeshLayouts, pub empty_layout: BindGroupLayout, pub default_prepass_shader: Handle, /// Whether skins will use uniform buffers on account of storage buffers /// being unavailable on this platform. pub skins_use_uniform_buffers: bool, pub depth_clip_control_supported: bool, /// Whether binding arrays (a.k.a. bindless textures) are usable on the /// current render device. pub binding_arrays_are_usable: bool, pub material_pipeline: MaterialPipeline, } pub fn init_prepass_pipeline( mut commands: Commands, render_device: Res, render_adapter: Res, mesh_pipeline: Res, material_pipeline: Res, asset_server: Res, ) { let visibility_ranges_buffer_binding_type = render_device.get_supported_read_only_binding_type(VISIBILITY_RANGES_STORAGE_BUFFER_COUNT); let view_layout_motion_vectors = render_device.create_bind_group_layout( "prepass_view_layout_motion_vectors", &BindGroupLayoutEntries::with_indices( ShaderStages::VERTEX_FRAGMENT, ( // View (0, uniform_buffer::(true)), // Globals (1, uniform_buffer::(false)), // PreviousViewUniforms (2, uniform_buffer::(true)), // VisibilityRanges ( 14, buffer_layout( visibility_ranges_buffer_binding_type, false, Some(Vec4::min_size()), ) .visibility(ShaderStages::VERTEX), ), ), ), ); let view_layout_no_motion_vectors = render_device.create_bind_group_layout( "prepass_view_layout_no_motion_vectors", &BindGroupLayoutEntries::with_indices( ShaderStages::VERTEX_FRAGMENT, ( // View (0, uniform_buffer::(true)), // Globals (1, uniform_buffer::(false)), // VisibilityRanges ( 14, buffer_layout( visibility_ranges_buffer_binding_type, false, Some(Vec4::min_size()), ) .visibility(ShaderStages::VERTEX), ), ), ), ); let depth_clip_control_supported = render_device .features() .contains(WgpuFeatures::DEPTH_CLIP_CONTROL); commands.insert_resource(PrepassPipeline { view_layout_motion_vectors, 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), depth_clip_control_supported, binding_arrays_are_usable: binding_arrays_are_usable(&render_device, &render_adapter), empty_layout: render_device.create_bind_group_layout("prepass_empty_layout", &[]), material_pipeline: material_pipeline.clone(), }); } pub struct PrepassPipelineSpecializer { pub(crate) pipeline: PrepassPipeline, pub(crate) properties: Arc, } impl SpecializedMeshPipeline for PrepassPipelineSpecializer { type Key = ErasedMaterialPipelineKey; fn specialize( &self, key: Self::Key, layout: &MeshVertexBufferLayoutRef, ) -> Result { let mut shader_defs = Vec::new(); if self.properties.bindless { shader_defs.push("BINDLESS".into()); } let mut descriptor = self.pipeline .specialize(key.mesh_key, shader_defs, layout, &self.properties)?; // This is a bit risky because it's possible to change something that would // break the prepass but be fine in the main pass. // Since this api is pretty low-level it doesn't matter that much, but it is a potential issue. if let Some(specialize) = self.properties.specialize { specialize( &self.pipeline.material_pipeline, &mut descriptor, layout, key, )?; } Ok(descriptor) } } impl PrepassPipeline { fn specialize( &self, mesh_key: MeshPipelineKey, shader_defs: Vec, layout: &MeshVertexBufferLayoutRef, material_properties: &MaterialProperties, ) -> Result { let mut shader_defs = shader_defs; let mut bind_group_layouts = vec![ if mesh_key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) { self.view_layout_motion_vectors.clone() } else { self.view_layout_no_motion_vectors.clone() }, self.empty_layout.clone(), ]; let mut vertex_attributes = Vec::new(); // Let the shader code know that it's running in a prepass pipeline. // (PBR code will use this to detect that it's running in deferred mode, // since that's the only time it gets called from a prepass pipeline.) shader_defs.push("PREPASS_PIPELINE".into()); // NOTE: Eventually, it would be nice to only add this when the shaders are overloaded by the Material. // The main limitation right now is that bind group order is hardcoded in shaders. bind_group_layouts.push( material_properties .material_layout .as_ref() .unwrap() .clone(), ); #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] shader_defs.push("WEBGL2".into()); shader_defs.push("VERTEX_OUTPUT_INSTANCE_INDEX".into()); if mesh_key.contains(MeshPipelineKey::DEPTH_PREPASS) { shader_defs.push("DEPTH_PREPASS".into()); } if mesh_key.contains(MeshPipelineKey::MAY_DISCARD) { shader_defs.push("MAY_DISCARD".into()); } let blend_key = mesh_key.intersection(MeshPipelineKey::BLEND_RESERVED_BITS); if blend_key == MeshPipelineKey::BLEND_PREMULTIPLIED_ALPHA { shader_defs.push("BLEND_PREMULTIPLIED_ALPHA".into()); } if blend_key == MeshPipelineKey::BLEND_ALPHA { shader_defs.push("BLEND_ALPHA".into()); } if layout.0.contains(Mesh::ATTRIBUTE_POSITION) { shader_defs.push("VERTEX_POSITIONS".into()); vertex_attributes.push(Mesh::ATTRIBUTE_POSITION.at_shader_location(0)); } // For directional light shadow map views, use unclipped depth via either the native GPU feature, // or emulated by setting depth in the fragment shader for GPUs that don't support it natively. let emulate_unclipped_depth = mesh_key.contains(MeshPipelineKey::UNCLIPPED_DEPTH_ORTHO) && !self.depth_clip_control_supported; if emulate_unclipped_depth { shader_defs.push("UNCLIPPED_DEPTH_ORTHO_EMULATION".into()); // PERF: This line forces the "prepass fragment shader" to always run in // common scenarios like "directional light calculation". Doing so resolves // a pretty nasty depth clamping bug, but it also feels a bit excessive. // We should try to find a way to resolve this without forcing the fragment // shader to run. // https://github.com/bevyengine/bevy/pull/8877 shader_defs.push("PREPASS_FRAGMENT".into()); } let unclipped_depth = mesh_key.contains(MeshPipelineKey::UNCLIPPED_DEPTH_ORTHO) && self.depth_clip_control_supported; if layout.0.contains(Mesh::ATTRIBUTE_UV_0) { shader_defs.push("VERTEX_UVS".into()); shader_defs.push("VERTEX_UVS_A".into()); vertex_attributes.push(Mesh::ATTRIBUTE_UV_0.at_shader_location(1)); } if layout.0.contains(Mesh::ATTRIBUTE_UV_1) { shader_defs.push("VERTEX_UVS".into()); shader_defs.push("VERTEX_UVS_B".into()); vertex_attributes.push(Mesh::ATTRIBUTE_UV_1.at_shader_location(2)); } if mesh_key.contains(MeshPipelineKey::NORMAL_PREPASS) { shader_defs.push("NORMAL_PREPASS".into()); } if mesh_key.intersects(MeshPipelineKey::NORMAL_PREPASS | MeshPipelineKey::DEFERRED_PREPASS) { shader_defs.push("NORMAL_PREPASS_OR_DEFERRED_PREPASS".into()); if layout.0.contains(Mesh::ATTRIBUTE_NORMAL) { shader_defs.push("VERTEX_NORMALS".into()); vertex_attributes.push(Mesh::ATTRIBUTE_NORMAL.at_shader_location(3)); } else if mesh_key.contains(MeshPipelineKey::NORMAL_PREPASS) { warn!( "The default normal prepass expects the mesh to have vertex normal attributes." ); } if layout.0.contains(Mesh::ATTRIBUTE_TANGENT) { shader_defs.push("VERTEX_TANGENTS".into()); vertex_attributes.push(Mesh::ATTRIBUTE_TANGENT.at_shader_location(4)); } } if mesh_key .intersects(MeshPipelineKey::MOTION_VECTOR_PREPASS | MeshPipelineKey::DEFERRED_PREPASS) { shader_defs.push("MOTION_VECTOR_PREPASS_OR_DEFERRED_PREPASS".into()); } if mesh_key.contains(MeshPipelineKey::DEFERRED_PREPASS) { shader_defs.push("DEFERRED_PREPASS".into()); } if mesh_key.contains(MeshPipelineKey::LIGHTMAPPED) { shader_defs.push("LIGHTMAP".into()); } if mesh_key.contains(MeshPipelineKey::LIGHTMAP_BICUBIC_SAMPLING) { shader_defs.push("LIGHTMAP_BICUBIC_SAMPLING".into()); } if layout.0.contains(Mesh::ATTRIBUTE_COLOR) { shader_defs.push("VERTEX_COLORS".into()); vertex_attributes.push(Mesh::ATTRIBUTE_COLOR.at_shader_location(7)); } if mesh_key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) { shader_defs.push("MOTION_VECTOR_PREPASS".into()); } if mesh_key.contains(MeshPipelineKey::HAS_PREVIOUS_SKIN) { shader_defs.push("HAS_PREVIOUS_SKIN".into()); } if mesh_key.contains(MeshPipelineKey::HAS_PREVIOUS_MORPH) { shader_defs.push("HAS_PREVIOUS_MORPH".into()); } if self.binding_arrays_are_usable { shader_defs.push("MULTIPLE_LIGHTMAPS_IN_ARRAY".into()); } if mesh_key.contains(MeshPipelineKey::VISIBILITY_RANGE_DITHER) { shader_defs.push("VISIBILITY_RANGE_DITHER".into()); } if mesh_key.intersects( MeshPipelineKey::NORMAL_PREPASS | MeshPipelineKey::MOTION_VECTOR_PREPASS | MeshPipelineKey::DEFERRED_PREPASS, ) { shader_defs.push("PREPASS_FRAGMENT".into()); } let bind_group = setup_morph_and_skinning_defs( &self.mesh_layouts, layout, 5, &mesh_key, &mut shader_defs, &mut vertex_attributes, self.skins_use_uniform_buffers, ); bind_group_layouts.insert(2, bind_group); let vertex_buffer_layout = layout.0.get_layout(&vertex_attributes)?; // Setup prepass fragment targets - normals in slot 0 (or None if not needed), motion vectors in slot 1 let mut targets = prepass_target_descriptors( mesh_key.contains(MeshPipelineKey::NORMAL_PREPASS), mesh_key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS), mesh_key.contains(MeshPipelineKey::DEFERRED_PREPASS), ); if targets.iter().all(Option::is_none) { // if no targets are required then clear the list, so that no fragment shader is required // (though one may still be used for discarding depth buffer writes) targets.clear(); } // The fragment shader is only used when the normal prepass or motion vectors prepass // is enabled, the material uses alpha cutoff values and doesn't rely on the standard // prepass shader, or we are emulating unclipped depth in the fragment shader. let fragment_required = !targets.is_empty() || emulate_unclipped_depth || (mesh_key.contains(MeshPipelineKey::MAY_DISCARD) && material_properties .get_shader(PrepassFragmentShader) .is_some()); let fragment = fragment_required.then(|| { // Use the fragment shader from the material let frag_shader_handle = if mesh_key.contains(MeshPipelineKey::DEFERRED_PREPASS) { match material_properties.get_shader(DeferredFragmentShader) { Some(frag_shader_handle) => frag_shader_handle, None => self.default_prepass_shader.clone(), } } else { match material_properties.get_shader(PrepassFragmentShader) { Some(frag_shader_handle) => frag_shader_handle, None => self.default_prepass_shader.clone(), } }; FragmentState { shader: frag_shader_handle, shader_defs: shader_defs.clone(), targets, ..default() } }); // Use the vertex shader from the material if present let vert_shader_handle = if mesh_key.contains(MeshPipelineKey::DEFERRED_PREPASS) { if let Some(handle) = material_properties.get_shader(DeferredVertexShader) { handle } else { self.default_prepass_shader.clone() } } else if let Some(handle) = material_properties.get_shader(PrepassVertexShader) { handle } else { self.default_prepass_shader.clone() }; let descriptor = RenderPipelineDescriptor { vertex: VertexState { shader: vert_shader_handle, shader_defs, buffers: vec![vertex_buffer_layout], ..default() }, fragment, layout: bind_group_layouts, primitive: PrimitiveState { topology: mesh_key.primitive_topology(), unclipped_depth, ..default() }, depth_stencil: Some(DepthStencilState { format: CORE_3D_DEPTH_FORMAT, depth_write_enabled: true, depth_compare: CompareFunction::GreaterEqual, stencil: StencilState { front: StencilFaceState::IGNORE, back: StencilFaceState::IGNORE, read_mask: 0, write_mask: 0, }, bias: DepthBiasState { constant: 0, slope_scale: 0.0, clamp: 0.0, }, }), multisample: MultisampleState { count: mesh_key.msaa_samples(), mask: !0, alpha_to_coverage_enabled: false, }, label: Some("prepass_pipeline".into()), ..default() }; Ok(descriptor) } } // 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.to_matrix(); let view_from_world = 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: 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: Res, ) { let empty_bind_group = render_device.create_bind_group( "prepass_view_empty_bind_group", &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, 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", &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", &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>, )>, 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()); } } } pub fn specialize_prepass_material_meshes( params: SpecializeMeshParams, render_materials: 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, prepass_pipeline, mut pipelines, view_specialization_ticks, ): ( ResMut, Res, ResMut>, 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; }; let Some(mesh_instance) = params .render_mesh_instances .render_mesh_queue_data(*visible_entity) else { continue; }; let entity_tick = params .entity_specialization_ticks .get(visible_entity) .unwrap(); 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, params.ticks.this_run()) || entity_tick.is_newer_than(tick, params.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) = params.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( ¶ms.pipeline_cache, &prepass_pipeline_specializer, erased_key, &mesh.layout, ); let pipeline_id = match pipeline_id { Ok(id) => id, Err(err) => { error!("{}", err); continue; } }; view_specialized_material_pipeline_cache .insert(*visible_entity, (params.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; }; // 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!(), }; match material.properties.render_phase_type { RenderPhaseType::Opaque => { if deferred { opaque_deferred_phase.as_mut().unwrap().add( OpaqueNoLightmap3dBatchSetKey { draw_function: material .properties .get_draw_function(DeferredDrawFunction) .unwrap(), 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); opaque_phase.add( OpaqueNoLightmap3dBatchSetKey { draw_function: material .properties .get_draw_function(PrepassDrawFunction) .unwrap(), 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 batch_set_key = OpaqueNoLightmap3dBatchSetKey { draw_function: material .properties .get_draw_function(DeferredDrawFunction) .unwrap(), 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 batch_set_key = OpaqueNoLightmap3dBatchSetKey { draw_function: material .properties .get_draw_function(PrepassDrawFunction) .unwrap(), 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, ); } } _ => {} } } } } pub struct SetPrepassViewBindGroup; impl RenderCommand

for SetPrepassViewBindGroup { type Param = SRes; type ViewQuery = ( Read, Has, Option>, ); type ItemQuery = (); #[inline] fn render<'w>( _item: &P, (view_uniform_offset, has_motion_vector_prepass, previous_view_uniform_offset): ( &'_ ViewUniformOffset, bool, Option<&'_ PreviousViewUniformOffset>, ), _entity: Option<()>, prepass_view_bind_group: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { let prepass_view_bind_group = prepass_view_bind_group.into_inner(); match previous_view_uniform_offset { Some(previous_view_uniform_offset) if has_motion_vector_prepass => { pass.set_bind_group( I, prepass_view_bind_group.motion_vectors.as_ref().unwrap(), &[ view_uniform_offset.offset, previous_view_uniform_offset.offset, ], ); } _ => { pass.set_bind_group( I, prepass_view_bind_group.no_motion_vectors.as_ref().unwrap(), &[view_uniform_offset.offset], ); } } RenderCommandResult::Success } } pub struct SetPrepassViewEmptyBindGroup; impl RenderCommand

for SetPrepassViewEmptyBindGroup { type Param = SRes; type ViewQuery = (); type ItemQuery = (); #[inline] fn render<'w>( _item: &P, _view: (), _entity: Option<()>, prepass_view_bind_group: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { let prepass_view_bind_group = prepass_view_bind_group.into_inner(); pass.set_bind_group(I, &prepass_view_bind_group.empty_bind_group, &[]); RenderCommandResult::Success } } pub type DrawPrepass = ( SetItemPipeline, SetPrepassViewBindGroup<0>, SetPrepassViewEmptyBindGroup<1>, SetMeshBindGroup<2>, SetMaterialBindGroup<3>, DrawMesh, );