use crate::*; use bevy_asset::{Asset, AssetEvent, AssetId, AssetServer}; use bevy_core_pipeline::{ core_3d::{ AlphaMask3d, Camera3d, Opaque3d, ScreenSpaceTransmissionQuality, Transmissive3d, Transparent3d, }, prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass}, tonemapping::{DebandDither, Tonemapping}, }; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ prelude::*, system::{lifetimeless::SRes, SystemParamItem}, }; use bevy_reflect::Reflect; use bevy_render::{ camera::TemporalJitter, extract_instances::{ExtractInstancesPlugin, ExtractedInstances}, extract_resource::ExtractResource, mesh::{Mesh, MeshVertexBufferLayout}, render_asset::RenderAssets, render_phase::*, render_resource::*, renderer::RenderDevice, texture::FallbackImage, view::{ExtractedView, Msaa, VisibleEntities}, Extract, }; use bevy_utils::{tracing::error, HashMap, HashSet}; use std::marker::PhantomData; use std::sync::atomic::{AtomicU32, Ordering}; use std::{hash::Hash, num::NonZeroU32}; use self::{irradiance_volume::IrradianceVolume, prelude::EnvironmentMapLight}; /// Materials are used alongside [`MaterialPlugin`] and [`MaterialMeshBundle`] /// to spawn entities that are rendered with a specific [`Material`] type. They serve as an easy to use high level /// way to render [`Mesh`] entities with custom shader logic. /// /// Materials must implement [`AsBindGroup`] to define how data will be transferred to the GPU and bound in shaders. /// [`AsBindGroup`] can be derived, which makes generating bindings straightforward. See the [`AsBindGroup`] docs for details. /// /// # Example /// /// Here is a simple Material implementation. The [`AsBindGroup`] derive has many features. To see what else is available, /// check out the [`AsBindGroup`] documentation. /// ``` /// # use bevy_pbr::{Material, MaterialMeshBundle}; /// # use bevy_ecs::prelude::*; /// # use bevy_reflect::TypePath; /// # use bevy_render::{render_resource::{AsBindGroup, ShaderRef}, texture::Image, color::LegacyColor}; /// # use bevy_asset::{Handle, AssetServer, Assets, Asset}; /// /// #[derive(AsBindGroup, Debug, Clone, Asset, TypePath)] /// pub struct CustomMaterial { /// // Uniform bindings must implement `ShaderType`, which will be used to convert the value to /// // its shader-compatible equivalent. Most core math types already implement `ShaderType`. /// #[uniform(0)] /// color: LegacyColor, /// // Images can be bound as textures in shaders. If the Image's sampler is also needed, just /// // add the sampler attribute with a different binding index. /// #[texture(1)] /// #[sampler(2)] /// color_texture: Handle, /// } /// /// // All functions on `Material` have default impls. You only need to implement the /// // functions that are relevant for your material. /// impl Material for CustomMaterial { /// fn fragment_shader() -> ShaderRef { /// "shaders/custom_material.wgsl".into() /// } /// } /// /// // Spawn an entity using `CustomMaterial`. /// fn setup(mut commands: Commands, mut materials: ResMut>, asset_server: Res) { /// commands.spawn(MaterialMeshBundle { /// material: materials.add(CustomMaterial { /// color: LegacyColor::RED, /// color_texture: asset_server.load("some_image.png"), /// }), /// ..Default::default() /// }); /// } /// ``` /// In WGSL shaders, the material's binding would look like this: /// /// ```wgsl /// @group(2) @binding(0) var color: vec4; /// @group(2) @binding(1) var color_texture: texture_2d; /// @group(2) @binding(2) var color_sampler: sampler; /// ``` pub trait Material: Asset + AsBindGroup + Clone + Sized { /// Returns this material's vertex shader. If [`ShaderRef::Default`] is returned, the default mesh vertex shader /// will be used. fn vertex_shader() -> ShaderRef { ShaderRef::Default } /// Returns this material's fragment shader. If [`ShaderRef::Default`] is returned, the default mesh fragment shader /// will be used. #[allow(unused_variables)] fn fragment_shader() -> ShaderRef { ShaderRef::Default } /// Returns this material's [`AlphaMode`]. Defaults to [`AlphaMode::Opaque`]. #[inline] fn alpha_mode(&self) -> AlphaMode { AlphaMode::Opaque } /// Returns if this material should be rendered by the deferred or forward renderer. /// for `AlphaMode::Opaque` or `AlphaMode::Mask` materials. /// If `OpaqueRendererMethod::Auto`, it will default to what is selected in the `DefaultOpaqueRendererMethod` resource. #[inline] fn opaque_render_method(&self) -> OpaqueRendererMethod { OpaqueRendererMethod::Forward } #[inline] /// Add a bias to the view depth of the mesh which can be used to force a specific render order. /// for meshes with similar depth, to avoid z-fighting. /// The bias is in depth-texture units so large values may be needed to overcome small depth differences. fn depth_bias(&self) -> f32 { 0.0 } #[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. fn reads_view_transmission_texture(&self) -> bool { false } /// Returns this material's prepass vertex shader. If [`ShaderRef::Default`] is returned, the default prepass vertex shader /// will be used. /// /// This is used for the various [prepasses](bevy_core_pipeline::prepass) as well as for generating the depth maps /// required for shadow mapping. fn prepass_vertex_shader() -> ShaderRef { ShaderRef::Default } /// Returns this material's prepass fragment shader. If [`ShaderRef::Default`] is returned, the default prepass fragment shader /// will be used. /// /// This is used for the various [prepasses](bevy_core_pipeline::prepass) as well as for generating the depth maps /// required for shadow mapping. #[allow(unused_variables)] fn prepass_fragment_shader() -> ShaderRef { ShaderRef::Default } /// Returns this material's deferred vertex shader. If [`ShaderRef::Default`] is returned, the default deferred vertex shader /// will be used. fn deferred_vertex_shader() -> ShaderRef { ShaderRef::Default } /// Returns this material's deferred fragment shader. If [`ShaderRef::Default`] is returned, the default deferred fragment shader /// will be used. #[allow(unused_variables)] fn deferred_fragment_shader() -> ShaderRef { ShaderRef::Default } /// Customizes the default [`RenderPipelineDescriptor`] for a specific entity using the entity's /// [`MaterialPipelineKey`] and [`MeshVertexBufferLayout`] as input. #[allow(unused_variables)] #[inline] fn specialize( pipeline: &MaterialPipeline, descriptor: &mut RenderPipelineDescriptor, layout: &MeshVertexBufferLayout, key: MaterialPipelineKey, ) -> Result<(), SpecializedMeshPipelineError> { Ok(()) } } /// Adds the necessary ECS resources and render logic to enable rendering entities using the given [`Material`] /// asset type. pub struct MaterialPlugin { /// Controls if the prepass is enabled for the Material. /// For more information about what a prepass is, see the [`bevy_core_pipeline::prepass`] docs. /// /// When it is enabled, it will automatically add the [`PrepassPlugin`] /// required to make the prepass work on this Material. pub prepass_enabled: bool, pub _marker: PhantomData, } impl Default for MaterialPlugin { fn default() -> Self { Self { prepass_enabled: true, _marker: Default::default(), } } } impl Plugin for MaterialPlugin where M::Data: PartialEq + Eq + Hash + Clone, { fn build(&self, app: &mut App) { app.init_asset::() .add_plugins(ExtractInstancesPlugin::>::extract_visible()); if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .init_resource::>() .add_render_command::>() .add_render_command::>() .add_render_command::>() .add_render_command::>() .add_render_command::>() .init_resource::>() .init_resource::>() .init_resource::>>() .add_systems(ExtractSchedule, extract_materials::) .add_systems( Render, ( prepare_materials:: .in_set(RenderSet::PrepareAssets) .after(prepare_assets::), queue_shadows:: .in_set(RenderSet::QueueMeshes) .after(prepare_materials::), queue_material_meshes:: .in_set(RenderSet::QueueMeshes) .after(prepare_materials::), ), ); } // PrepassPipelinePlugin is required for shadow mapping and the optional PrepassPlugin app.add_plugins(PrepassPipelinePlugin::::default()); if self.prepass_enabled { app.add_plugins(PrepassPlugin::::default()); } } fn finish(&self, app: &mut App) { if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app.init_resource::>(); } } } /// A key uniquely identifying a specialized [`MaterialPipeline`]. pub struct MaterialPipelineKey { pub mesh_key: MeshPipelineKey, pub bind_group_data: M::Data, } impl Eq for MaterialPipelineKey where M::Data: PartialEq {} impl PartialEq for MaterialPipelineKey where M::Data: PartialEq, { fn eq(&self, other: &Self) -> bool { self.mesh_key == other.mesh_key && self.bind_group_data == other.bind_group_data } } impl Clone for MaterialPipelineKey where M::Data: Clone, { fn clone(&self) -> Self { Self { mesh_key: self.mesh_key, bind_group_data: self.bind_group_data.clone(), } } } impl Hash for MaterialPipelineKey where M::Data: Hash, { fn hash(&self, state: &mut H) { self.mesh_key.hash(state); self.bind_group_data.hash(state); } } /// Render pipeline data for a given [`Material`]. #[derive(Resource)] pub struct MaterialPipeline { pub mesh_pipeline: MeshPipeline, pub material_layout: BindGroupLayout, pub vertex_shader: Option>, pub fragment_shader: Option>, pub marker: PhantomData, } impl Clone for MaterialPipeline { fn clone(&self) -> Self { Self { mesh_pipeline: self.mesh_pipeline.clone(), material_layout: self.material_layout.clone(), vertex_shader: self.vertex_shader.clone(), fragment_shader: self.fragment_shader.clone(), marker: PhantomData, } } } impl SpecializedMeshPipeline for MaterialPipeline where M::Data: PartialEq + Eq + Hash + Clone, { type Key = MaterialPipelineKey; fn specialize( &self, key: Self::Key, layout: &MeshVertexBufferLayout, ) -> Result { let mut descriptor = self.mesh_pipeline.specialize(key.mesh_key, layout)?; if let Some(vertex_shader) = &self.vertex_shader { descriptor.vertex.shader = vertex_shader.clone(); } if let Some(fragment_shader) = &self.fragment_shader { descriptor.fragment.as_mut().unwrap().shader = fragment_shader.clone(); } descriptor.layout.insert(2, self.material_layout.clone()); M::specialize(self, &mut descriptor, layout, key)?; Ok(descriptor) } } impl FromWorld for MaterialPipeline { fn from_world(world: &mut World) -> Self { let asset_server = world.resource::(); let render_device = world.resource::(); MaterialPipeline { mesh_pipeline: world.resource::().clone(), material_layout: M::bind_group_layout(render_device), vertex_shader: match M::vertex_shader() { ShaderRef::Default => None, ShaderRef::Handle(handle) => Some(handle), ShaderRef::Path(path) => Some(asset_server.load(path)), }, fragment_shader: match M::fragment_shader() { ShaderRef::Default => None, ShaderRef::Handle(handle) => Some(handle), ShaderRef::Path(path) => Some(asset_server.load(path)), }, marker: PhantomData, } } } type DrawMaterial = ( SetItemPipeline, SetMeshViewBindGroup<0>, SetMeshBindGroup<1>, SetMaterialBindGroup, DrawMesh, ); /// Sets the bind group for a given [`Material`] at the configured `I` index. pub struct SetMaterialBindGroup(PhantomData); impl RenderCommand

for SetMaterialBindGroup { type Param = (SRes>, SRes>); type ViewQuery = (); type ItemQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _item_query: Option<()>, (materials, material_instances): SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { let materials = materials.into_inner(); let material_instances = material_instances.into_inner(); let Some(material_asset_id) = material_instances.get(&item.entity()) else { return RenderCommandResult::Failure; }; let Some(material) = materials.get(material_asset_id) else { return RenderCommandResult::Failure; }; pass.set_bind_group(I, &material.bind_group, &[]); RenderCommandResult::Success } } pub type RenderMaterialInstances = ExtractedInstances>; pub const fn alpha_mode_pipeline_key(alpha_mode: AlphaMode) -> MeshPipelineKey { match alpha_mode { // Premultiplied and Add share the same pipeline key // They're made distinct in the PBR shader, via `premultiply_alpha()` AlphaMode::Premultiplied | AlphaMode::Add => MeshPipelineKey::BLEND_PREMULTIPLIED_ALPHA, AlphaMode::Blend => MeshPipelineKey::BLEND_ALPHA, AlphaMode::Multiply => MeshPipelineKey::BLEND_MULTIPLY, AlphaMode::Mask(_) => MeshPipelineKey::MAY_DISCARD, _ => MeshPipelineKey::NONE, } } pub const fn tonemapping_pipeline_key(tonemapping: Tonemapping) -> MeshPipelineKey { match tonemapping { Tonemapping::None => MeshPipelineKey::TONEMAP_METHOD_NONE, Tonemapping::Reinhard => MeshPipelineKey::TONEMAP_METHOD_REINHARD, Tonemapping::ReinhardLuminance => MeshPipelineKey::TONEMAP_METHOD_REINHARD_LUMINANCE, Tonemapping::AcesFitted => MeshPipelineKey::TONEMAP_METHOD_ACES_FITTED, Tonemapping::AgX => MeshPipelineKey::TONEMAP_METHOD_AGX, Tonemapping::SomewhatBoringDisplayTransform => { MeshPipelineKey::TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM } Tonemapping::TonyMcMapface => MeshPipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE, Tonemapping::BlenderFilmic => MeshPipelineKey::TONEMAP_METHOD_BLENDER_FILMIC, } } pub const fn screen_space_specular_transmission_pipeline_key( screen_space_transmissive_blur_quality: ScreenSpaceTransmissionQuality, ) -> MeshPipelineKey { match screen_space_transmissive_blur_quality { ScreenSpaceTransmissionQuality::Low => { MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_LOW } ScreenSpaceTransmissionQuality::Medium => { MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_MEDIUM } ScreenSpaceTransmissionQuality::High => { MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_HIGH } ScreenSpaceTransmissionQuality::Ultra => { MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_ULTRA } } } #[allow(clippy::too_many_arguments)] pub fn queue_material_meshes( opaque_draw_functions: Res>, alpha_mask_draw_functions: Res>, transmissive_draw_functions: Res>, transparent_draw_functions: Res>, material_pipeline: Res>, mut pipelines: ResMut>>, pipeline_cache: Res, msaa: Res, render_meshes: Res>, render_materials: Res>, render_mesh_instances: Res, render_material_instances: Res>, render_lightmaps: Res, mut views: Query<( &ExtractedView, &VisibleEntities, Option<&Tonemapping>, Option<&DebandDither>, Option<&ShadowFilteringMethod>, Has, ( Has, Has, Has, Has, ), Option<&Camera3d>, Has, Option<&Projection>, &mut RenderPhase, &mut RenderPhase, &mut RenderPhase, &mut RenderPhase, ( Has>, Has>, ), )>, ) where M::Data: PartialEq + Eq + Hash + Clone, { for ( view, visible_entities, tonemapping, dither, shadow_filter_method, ssao, (normal_prepass, depth_prepass, motion_vector_prepass, deferred_prepass), camera_3d, temporal_jitter, projection, mut opaque_phase, mut alpha_mask_phase, mut transmissive_phase, mut transparent_phase, (has_environment_maps, has_irradiance_volumes), ) in &mut views { let draw_opaque_pbr = opaque_draw_functions.read().id::>(); let draw_alpha_mask_pbr = alpha_mask_draw_functions.read().id::>(); let draw_transmissive_pbr = transmissive_draw_functions.read().id::>(); let draw_transparent_pbr = transparent_draw_functions.read().id::>(); let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples()) | MeshPipelineKey::from_hdr(view.hdr); if normal_prepass { view_key |= MeshPipelineKey::NORMAL_PREPASS; } if depth_prepass { view_key |= MeshPipelineKey::DEPTH_PREPASS; } if motion_vector_prepass { view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS; } if deferred_prepass { view_key |= MeshPipelineKey::DEFERRED_PREPASS; } if temporal_jitter { view_key |= MeshPipelineKey::TEMPORAL_JITTER; } if has_environment_maps { view_key |= MeshPipelineKey::ENVIRONMENT_MAP; } if has_irradiance_volumes { view_key |= MeshPipelineKey::IRRADIANCE_VOLUME; } if let Some(projection) = projection { view_key |= match projection { Projection::Perspective(_) => MeshPipelineKey::VIEW_PROJECTION_PERSPECTIVE, Projection::Orthographic(_) => MeshPipelineKey::VIEW_PROJECTION_ORTHOGRAPHIC, }; } match shadow_filter_method.unwrap_or(&ShadowFilteringMethod::default()) { ShadowFilteringMethod::Hardware2x2 => { view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_HARDWARE_2X2; } ShadowFilteringMethod::Castano13 => { view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_CASTANO_13; } ShadowFilteringMethod::Jimenez14 => { view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_JIMENEZ_14; } } 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 let Some(camera_3d) = camera_3d { view_key |= screen_space_specular_transmission_pipeline_key( camera_3d.screen_space_specular_transmission_quality, ); } let rangefinder = view.rangefinder3d(); for visible_entity in &visible_entities.entities { let Some(material_asset_id) = render_material_instances.get(visible_entity) else { continue; }; let Some(mesh_instance) = render_mesh_instances.get(visible_entity) else { continue; }; let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else { continue; }; let Some(material) = render_materials.get(material_asset_id) else { continue; }; let forward = match material.properties.render_method { OpaqueRendererMethod::Forward => true, OpaqueRendererMethod::Deferred => false, OpaqueRendererMethod::Auto => unreachable!(), }; let mut mesh_key = view_key; mesh_key |= MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); if mesh.morph_targets.is_some() { mesh_key |= MeshPipelineKey::MORPH_TARGETS; } if material.properties.reads_view_transmission_texture { mesh_key |= MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE; } mesh_key |= alpha_mode_pipeline_key(material.properties.alpha_mode); if render_lightmaps .render_lightmaps .contains_key(visible_entity) { mesh_key |= MeshPipelineKey::LIGHTMAPPED; } let pipeline_id = pipelines.specialize( &pipeline_cache, &material_pipeline, MaterialPipelineKey { mesh_key, bind_group_data: material.key.clone(), }, &mesh.layout, ); let pipeline_id = match pipeline_id { Ok(id) => id, Err(err) => { error!("{}", err); continue; } }; mesh_instance .material_bind_group_id .set(material.get_bind_group_id()); match material.properties.alpha_mode { AlphaMode::Opaque => { if material.properties.reads_view_transmission_texture { let distance = rangefinder .distance_translation(&mesh_instance.transforms.transform.translation) + material.properties.depth_bias; transmissive_phase.add(Transmissive3d { entity: *visible_entity, draw_function: draw_transmissive_pbr, pipeline: pipeline_id, distance, batch_range: 0..1, dynamic_offset: None, }); } else if forward { opaque_phase.add(Opaque3d { entity: *visible_entity, draw_function: draw_opaque_pbr, pipeline: pipeline_id, asset_id: mesh_instance.mesh_asset_id, batch_range: 0..1, dynamic_offset: None, }); } } AlphaMode::Mask(_) => { let distance = rangefinder .distance_translation(&mesh_instance.transforms.transform.translation) + material.properties.depth_bias; if material.properties.reads_view_transmission_texture { transmissive_phase.add(Transmissive3d { entity: *visible_entity, draw_function: draw_transmissive_pbr, pipeline: pipeline_id, distance, batch_range: 0..1, dynamic_offset: None, }); } else if forward { alpha_mask_phase.add(AlphaMask3d { entity: *visible_entity, draw_function: draw_alpha_mask_pbr, pipeline: pipeline_id, distance, batch_range: 0..1, dynamic_offset: None, }); } } AlphaMode::Blend | AlphaMode::Premultiplied | AlphaMode::Add | AlphaMode::Multiply => { let distance = rangefinder .distance_translation(&mesh_instance.transforms.transform.translation) + material.properties.depth_bias; transparent_phase.add(Transparent3d { entity: *visible_entity, draw_function: draw_transparent_pbr, pipeline: pipeline_id, distance, batch_range: 0..1, dynamic_offset: None, }); } } } } } /// Default render method used for opaque materials. #[derive(Default, Resource, Clone, Debug, ExtractResource, Reflect)] pub struct DefaultOpaqueRendererMethod(OpaqueRendererMethod); impl DefaultOpaqueRendererMethod { pub fn forward() -> Self { DefaultOpaqueRendererMethod(OpaqueRendererMethod::Forward) } pub fn deferred() -> Self { DefaultOpaqueRendererMethod(OpaqueRendererMethod::Deferred) } pub fn set_to_forward(&mut self) { self.0 = OpaqueRendererMethod::Forward; } pub fn set_to_deferred(&mut self) { self.0 = OpaqueRendererMethod::Deferred; } } /// Render method used for opaque materials. /// /// The forward rendering main pass draws each mesh entity and shades it according to its /// corresponding material and the lights that affect it. Some render features like Screen Space /// Ambient Occlusion require running depth and normal prepasses, that are 'deferred'-like /// prepasses over all mesh entities to populate depth and normal textures. This means that when /// using render features that require running prepasses, multiple passes over all visible geometry /// are required. This can be slow if there is a lot of geometry that cannot be batched into few /// draws. /// /// Deferred rendering runs a prepass to gather not only geometric information like depth and /// normals, but also all the material properties like base color, emissive color, reflectance, /// metalness, etc, and writes them into a deferred 'g-buffer' texture. The deferred main pass is /// then a fullscreen pass that reads data from these textures and executes shading. This allows /// for one pass over geometry, but is at the cost of not being able to use MSAA, and has heavier /// bandwidth usage which can be unsuitable for low end mobile or other bandwidth-constrained devices. /// /// If a material indicates `OpaqueRendererMethod::Auto`, `DefaultOpaqueRendererMethod` will be used. #[derive(Default, Clone, Copy, Debug, Reflect)] pub enum OpaqueRendererMethod { #[default] Forward, Deferred, Auto, } /// Common [`Material`] properties, calculated for a specific material instance. pub struct MaterialProperties { /// Is this material should be rendered by the deferred renderer when. /// AlphaMode::Opaque or AlphaMode::Mask pub render_method: OpaqueRendererMethod, /// The [`AlphaMode`] of this material. pub alpha_mode: AlphaMode, /// Add a bias to the view depth of the mesh which can be used to force a specific render order /// for meshes with equal depth, to avoid z-fighting. /// The bias is in depth-texture units so large values may be needed to overcome small depth differences. 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. pub reads_view_transmission_texture: bool, } /// Data prepared for a [`Material`] instance. pub struct PreparedMaterial { pub bindings: Vec<(u32, OwnedBindingResource)>, pub bind_group: BindGroup, pub key: T::Data, pub properties: MaterialProperties, } #[derive(Component, Clone, Copy, Default, PartialEq, Eq, Deref, DerefMut)] pub struct MaterialBindGroupId(pub Option); impl MaterialBindGroupId { pub fn new(id: BindGroupId) -> Self { Self(Some(id)) } } impl From for MaterialBindGroupId { fn from(value: BindGroup) -> Self { Self::new(value.id()) } } /// An atomic version of [`MaterialBindGroupId`] that can be read from and written to /// safely from multiple threads. #[derive(Default)] pub struct AtomicMaterialBindGroupId(AtomicU32); impl AtomicMaterialBindGroupId { /// Stores a value atomically. Uses [`Ordering::Relaxed`] so there is zero guarantee of ordering /// relative to other operations. /// /// See also: [`AtomicU32::store`]. pub fn set(&self, id: MaterialBindGroupId) { let id = if let Some(id) = id.0 { NonZeroU32::from(id).get() } else { 0 }; self.0.store(id, Ordering::Relaxed); } /// Loads a value atomically. Uses [`Ordering::Relaxed`] so there is zero guarantee of ordering /// relative to other operations. /// /// See also: [`AtomicU32::load`]. pub fn get(&self) -> MaterialBindGroupId { MaterialBindGroupId(NonZeroU32::new(self.0.load(Ordering::Relaxed)).map(BindGroupId::from)) } } impl PreparedMaterial { pub fn get_bind_group_id(&self) -> MaterialBindGroupId { MaterialBindGroupId(Some(self.bind_group.id())) } } #[derive(Resource)] pub struct ExtractedMaterials { extracted: Vec<(AssetId, M)>, removed: Vec>, } impl Default for ExtractedMaterials { fn default() -> Self { Self { extracted: Default::default(), removed: Default::default(), } } } /// Stores all prepared representations of [`Material`] assets for as long as they exist. #[derive(Resource, Deref, DerefMut)] pub struct RenderMaterials(pub HashMap, PreparedMaterial>); impl Default for RenderMaterials { fn default() -> Self { Self(Default::default()) } } /// This system extracts all created or modified assets of the corresponding [`Material`] type /// into the "render world". pub fn extract_materials( mut commands: Commands, mut events: Extract>>, assets: Extract>>, ) { let mut changed_assets = HashSet::default(); let mut removed = Vec::new(); for event in events.read() { #[allow(clippy::match_same_arms)] match event { AssetEvent::Added { id } | AssetEvent::Modified { id } => { changed_assets.insert(*id); } AssetEvent::Removed { id } => { changed_assets.remove(id); removed.push(*id); } AssetEvent::Unused { .. } => {} AssetEvent::LoadedWithDependencies { .. } => { // TODO: handle this } } } let mut extracted_assets = Vec::new(); for id in changed_assets.drain() { if let Some(asset) = assets.get(id) { extracted_assets.push((id, asset.clone())); } } commands.insert_resource(ExtractedMaterials { extracted: extracted_assets, removed, }); } /// All [`Material`] values of a given type that should be prepared next frame. pub struct PrepareNextFrameMaterials { assets: Vec<(AssetId, M)>, } impl Default for PrepareNextFrameMaterials { fn default() -> Self { Self { assets: Default::default(), } } } /// This system prepares all assets of the corresponding [`Material`] type /// which where extracted this frame for the GPU. #[allow(clippy::too_many_arguments)] pub fn prepare_materials( mut prepare_next_frame: Local>, mut extracted_assets: ResMut>, mut render_materials: ResMut>, render_device: Res, images: Res>, fallback_image: Res, pipeline: Res>, default_opaque_render_method: Res, ) { let queued_assets = std::mem::take(&mut prepare_next_frame.assets); for (id, material) in queued_assets.into_iter() { match prepare_material( &material, &render_device, &images, &fallback_image, &pipeline, default_opaque_render_method.0, ) { Ok(prepared_asset) => { render_materials.insert(id, prepared_asset); } Err(AsBindGroupError::RetryNextUpdate) => { prepare_next_frame.assets.push((id, material)); } } } for removed in std::mem::take(&mut extracted_assets.removed) { render_materials.remove(&removed); } for (id, material) in std::mem::take(&mut extracted_assets.extracted) { match prepare_material( &material, &render_device, &images, &fallback_image, &pipeline, default_opaque_render_method.0, ) { Ok(prepared_asset) => { render_materials.insert(id, prepared_asset); } Err(AsBindGroupError::RetryNextUpdate) => { prepare_next_frame.assets.push((id, material)); } } } } fn prepare_material( material: &M, render_device: &RenderDevice, images: &RenderAssets, fallback_image: &FallbackImage, pipeline: &MaterialPipeline, default_opaque_render_method: OpaqueRendererMethod, ) -> Result, AsBindGroupError> { let prepared = material.as_bind_group( &pipeline.material_layout, render_device, images, fallback_image, )?; let method = match material.opaque_render_method() { OpaqueRendererMethod::Forward => OpaqueRendererMethod::Forward, OpaqueRendererMethod::Deferred => OpaqueRendererMethod::Deferred, OpaqueRendererMethod::Auto => default_opaque_render_method, }; Ok(PreparedMaterial { bindings: prepared.bindings, bind_group: prepared.bind_group, key: prepared.data, properties: MaterialProperties { alpha_mode: material.alpha_mode(), depth_bias: material.depth_bias(), reads_view_transmission_texture: material.reads_view_transmission_texture(), render_method: method, }, }) }