use crate::{ DrawMesh2d, Mesh2d, Mesh2dPipeline, Mesh2dPipelineKey, RenderMesh2dInstances, SetMesh2dBindGroup, SetMesh2dViewBindGroup, ViewKeyCache, ViewSpecializationTicks, }; use bevy_app::{App, Plugin, PostUpdate}; use bevy_asset::prelude::AssetChanged; use bevy_asset::{AsAssetId, Asset, AssetApp, AssetEvents, AssetId, AssetServer, Handle}; use bevy_core_pipeline::{ core_2d::{ AlphaMask2d, AlphaMask2dBinKey, BatchSetKey2d, Opaque2d, Opaque2dBinKey, Transparent2d, }, tonemapping::Tonemapping, }; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::component::Tick; use bevy_ecs::system::SystemChangeTick; use bevy_ecs::{ prelude::*, system::{lifetimeless::SRes, SystemParamItem}, }; use bevy_math::FloatOrd; use bevy_platform_support::collections::HashMap; use bevy_reflect::{prelude::ReflectDefault, Reflect}; use bevy_render::camera::extract_cameras; use bevy_render::render_phase::{DrawFunctionId, InputUniformIndex}; use bevy_render::render_resource::CachedRenderPipelineId; use bevy_render::view::RenderVisibleEntities; use bevy_render::{ mesh::{MeshVertexBufferLayoutRef, RenderMesh}, render_asset::{ prepare_assets, PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets, }, render_phase::{ AddRenderCommand, BinnedRenderPhaseType, DrawFunctions, PhaseItem, PhaseItemExtraIndex, RenderCommand, RenderCommandResult, SetItemPipeline, TrackedRenderPass, ViewBinnedRenderPhases, ViewSortedRenderPhases, }, render_resource::{ AsBindGroup, AsBindGroupError, BindGroup, BindGroupId, BindGroupLayout, BindingResources, PipelineCache, RenderPipelineDescriptor, Shader, ShaderRef, SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines, }, renderer::RenderDevice, sync_world::{MainEntity, MainEntityHashMap}, view::{ExtractedView, ViewVisibility}, Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; use bevy_utils::Parallel; use core::{hash::Hash, marker::PhantomData}; use derive_more::derive::From; use tracing::error; /// Materials are used alongside [`Material2dPlugin`], [`Mesh2d`], and [`MeshMaterial2d`] /// to spawn entities that are rendered with a specific [`Material2d`] type. They serve as an easy to use high level /// way to render [`Mesh2d`] 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 [`Material2d`] implementation. The [`AsBindGroup`] derive has many features. To see what else is available, /// check out the [`AsBindGroup`] documentation. /// /// ``` /// # use bevy_sprite::{Material2d, MeshMaterial2d}; /// # use bevy_ecs::prelude::*; /// # use bevy_image::Image; /// # use bevy_reflect::TypePath; /// # use bevy_render::{mesh::{Mesh, Mesh2d}, render_resource::{AsBindGroup, ShaderRef}}; /// # use bevy_color::LinearRgba; /// # use bevy_color::palettes::basic::RED; /// # use bevy_asset::{Handle, AssetServer, Assets, Asset}; /// # use bevy_math::primitives::Circle; /// # /// #[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: LinearRgba, /// // 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 `Material2d` have default impls. You only need to implement the /// // functions that are relevant for your material. /// impl Material2d for CustomMaterial { /// fn fragment_shader() -> ShaderRef { /// "shaders/custom_material.wgsl".into() /// } /// } /// /// // Spawn an entity with a mesh using `CustomMaterial`. /// fn setup( /// mut commands: Commands, /// mut meshes: ResMut>, /// mut materials: ResMut>, /// asset_server: Res, /// ) { /// commands.spawn(( /// Mesh2d(meshes.add(Circle::new(50.0))), /// MeshMaterial2d(materials.add(CustomMaterial { /// color: RED.into(), /// color_texture: asset_server.load("some_image.png"), /// })), /// )); /// } /// ``` /// /// In WGSL shaders, the material's binding would look like this: /// /// ```wgsl /// struct CustomMaterial { /// color: vec4, /// } /// /// @group(2) @binding(0) var material: CustomMaterial; /// @group(2) @binding(1) var color_texture: texture_2d; /// @group(2) @binding(2) var color_sampler: sampler; /// ``` pub trait Material2d: AsBindGroup + Asset + 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. fn fragment_shader() -> ShaderRef { ShaderRef::Default } /// Add a bias to the view depth of the mesh which can be used to force a specific render order. #[inline] fn depth_bias(&self) -> f32 { 0.0 } fn alpha_mode(&self) -> AlphaMode2d { AlphaMode2d::Opaque } /// Customizes the default [`RenderPipelineDescriptor`]. #[expect( unused_variables, reason = "The parameters here are intentionally unused by the default implementation; however, putting underscores here will result in the underscores being copied by rust-analyzer's tab completion." )] #[inline] fn specialize( descriptor: &mut RenderPipelineDescriptor, layout: &MeshVertexBufferLayoutRef, key: Material2dKey, ) -> Result<(), SpecializedMeshPipelineError> { Ok(()) } } /// A [material](Material2d) used for rendering a [`Mesh2d`]. /// /// See [`Material2d`] for general information about 2D materials and how to implement your own materials. /// /// # Example /// /// ``` /// # use bevy_sprite::{ColorMaterial, MeshMaterial2d}; /// # use bevy_ecs::prelude::*; /// # use bevy_render::mesh::{Mesh, Mesh2d}; /// # use bevy_color::palettes::basic::RED; /// # use bevy_asset::Assets; /// # use bevy_math::primitives::Circle; /// # /// // Spawn an entity with a mesh using `ColorMaterial`. /// fn setup( /// mut commands: Commands, /// mut meshes: ResMut>, /// mut materials: ResMut>, /// ) { /// commands.spawn(( /// Mesh2d(meshes.add(Circle::new(50.0))), /// MeshMaterial2d(materials.add(ColorMaterial::from_color(RED))), /// )); /// } /// ``` /// /// [`MeshMaterial2d`]: crate::MeshMaterial2d #[derive(Component, Clone, Debug, Deref, DerefMut, Reflect, From)] #[reflect(Component, Default, Clone)] pub struct MeshMaterial2d(pub Handle); impl Default for MeshMaterial2d { fn default() -> Self { Self(Handle::default()) } } impl PartialEq for MeshMaterial2d { fn eq(&self, other: &Self) -> bool { self.0 == other.0 } } impl Eq for MeshMaterial2d {} impl From> for AssetId { fn from(material: MeshMaterial2d) -> Self { material.id() } } impl From<&MeshMaterial2d> for AssetId { fn from(material: &MeshMaterial2d) -> Self { material.id() } } impl AsAssetId for MeshMaterial2d { type Asset = M; fn as_asset_id(&self) -> AssetId { self.id() } } /// Sets how a 2d material's base color alpha channel is used for transparency. /// Currently, this only works with [`Mesh2d`]. Sprites are always transparent. /// /// This is very similar to [`AlphaMode`](bevy_render::alpha::AlphaMode) but this only applies to 2d meshes. /// We use a separate type because 2d doesn't support all the transparency modes that 3d does. #[derive(Debug, Default, Reflect, Copy, Clone, PartialEq)] #[reflect(Default, Debug, Clone)] pub enum AlphaMode2d { /// Base color alpha values are overridden to be fully opaque (1.0). #[default] Opaque, /// Reduce transparency to fully opaque or fully transparent /// based on a threshold. /// /// Compares the base color alpha value to the specified threshold. /// If the value is below the threshold, /// considers the color to be fully transparent (alpha is set to 0.0). /// If it is equal to or above the threshold, /// considers the color to be fully opaque (alpha is set to 1.0). Mask(f32), /// The base color alpha value defines the opacity of the color. /// Standard alpha-blending is used to blend the fragment's color /// with the color behind it. Blend, } /// Adds the necessary ECS resources and render logic to enable rendering entities using the given [`Material2d`] /// asset type (which includes [`Material2d`] types). pub struct Material2dPlugin(PhantomData); impl Default for Material2dPlugin { fn default() -> Self { Self(Default::default()) } } impl Plugin for Material2dPlugin where M::Data: PartialEq + Eq + Hash + Clone, { fn build(&self, app: &mut App) { app.init_asset::() .init_resource::>() .register_type::>() .add_plugins(RenderAssetPlugin::>::default()) .add_systems( PostUpdate, check_entities_needing_specialization::.after(AssetEvents), ); if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app .init_resource::>() .init_resource::>() .add_render_command::>() .add_render_command::>() .add_render_command::>() .init_resource::>() .init_resource::>>() .add_systems( ExtractSchedule, ( extract_entities_needs_specialization::.after(extract_cameras), extract_mesh_materials_2d::, ), ) .add_systems( Render, ( specialize_material2d_meshes:: .in_set(RenderSet::PrepareMeshes) .after(prepare_assets::>) .after(prepare_assets::), queue_material2d_meshes:: .in_set(RenderSet::QueueMeshes) .after(prepare_assets::>), ), ); } } fn finish(&self, app: &mut App) { if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app.init_resource::>(); } } } #[derive(Resource, Deref, DerefMut)] pub struct RenderMaterial2dInstances(MainEntityHashMap>); impl Default for RenderMaterial2dInstances { fn default() -> Self { Self(Default::default()) } } pub fn extract_mesh_materials_2d( mut material_instances: ResMut>, changed_meshes_query: Extract< Query< (Entity, &ViewVisibility, &MeshMaterial2d), Or<(Changed, Changed>)>, >, >, mut removed_visibilities_query: Extract>, mut removed_materials_query: Extract>>, ) { for (entity, view_visibility, material) in &changed_meshes_query { if view_visibility.get() { add_mesh_instance(entity, material, &mut material_instances); } else { remove_mesh_instance(entity, &mut material_instances); } } for entity in removed_visibilities_query .read() .chain(removed_materials_query.read()) { // Only queue a mesh for removal if we didn't pick it up above. // It's possible that a necessary component was removed and re-added in // the same frame. if !changed_meshes_query.contains(entity) { remove_mesh_instance(entity, &mut material_instances); } } // Adds or updates a mesh instance in the [`RenderMaterial2dInstances`] // array. fn add_mesh_instance( entity: Entity, material: &MeshMaterial2d, material_instances: &mut RenderMaterial2dInstances, ) where M: Material2d, { material_instances.insert(entity.into(), material.id()); } // Removes a mesh instance from the [`RenderMaterial2dInstances`] array. fn remove_mesh_instance( entity: Entity, material_instances: &mut RenderMaterial2dInstances, ) where M: Material2d, { material_instances.remove(&MainEntity::from(entity)); } } /// Render pipeline data for a given [`Material2d`] #[derive(Resource)] pub struct Material2dPipeline { pub mesh2d_pipeline: Mesh2dPipeline, pub material2d_layout: BindGroupLayout, pub vertex_shader: Option>, pub fragment_shader: Option>, marker: PhantomData, } pub struct Material2dKey { pub mesh_key: Mesh2dPipelineKey, pub bind_group_data: M::Data, } impl Eq for Material2dKey where M::Data: PartialEq {} impl PartialEq for Material2dKey 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 Material2dKey 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 Material2dKey where M::Data: Hash, { fn hash(&self, state: &mut H) { self.mesh_key.hash(state); self.bind_group_data.hash(state); } } impl Clone for Material2dPipeline { fn clone(&self) -> Self { Self { mesh2d_pipeline: self.mesh2d_pipeline.clone(), material2d_layout: self.material2d_layout.clone(), vertex_shader: self.vertex_shader.clone(), fragment_shader: self.fragment_shader.clone(), marker: PhantomData, } } } impl SpecializedMeshPipeline for Material2dPipeline where M::Data: PartialEq + Eq + Hash + Clone, { type Key = Material2dKey; fn specialize( &self, key: Self::Key, layout: &MeshVertexBufferLayoutRef, ) -> Result { let mut descriptor = self.mesh2d_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 = vec![ self.mesh2d_pipeline.view_layout.clone(), self.mesh2d_pipeline.mesh_layout.clone(), self.material2d_layout.clone(), ]; M::specialize(&mut descriptor, layout, key)?; Ok(descriptor) } } impl FromWorld for Material2dPipeline { fn from_world(world: &mut World) -> Self { let asset_server = world.resource::(); let render_device = world.resource::(); let material2d_layout = M::bind_group_layout(render_device); Material2dPipeline { mesh2d_pipeline: world.resource::().clone(), material2d_layout, 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, } } } pub(super) type DrawMaterial2d = ( SetItemPipeline, SetMesh2dViewBindGroup<0>, SetMesh2dBindGroup<1>, SetMaterial2dBindGroup, DrawMesh2d, ); pub struct SetMaterial2dBindGroup(PhantomData); impl RenderCommand

for SetMaterial2dBindGroup { 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_instance) = material_instances.get(&item.main_entity()) else { return RenderCommandResult::Skip; }; let Some(material2d) = materials.get(*material_instance) else { return RenderCommandResult::Skip; }; pass.set_bind_group(I, &material2d.bind_group, &[]); RenderCommandResult::Success } } pub const fn alpha_mode_pipeline_key(alpha_mode: AlphaMode2d) -> Mesh2dPipelineKey { match alpha_mode { AlphaMode2d::Blend => Mesh2dPipelineKey::BLEND_ALPHA, AlphaMode2d::Mask(_) => Mesh2dPipelineKey::MAY_DISCARD, _ => Mesh2dPipelineKey::NONE, } } pub const fn tonemapping_pipeline_key(tonemapping: Tonemapping) -> Mesh2dPipelineKey { match tonemapping { Tonemapping::None => Mesh2dPipelineKey::TONEMAP_METHOD_NONE, Tonemapping::Reinhard => Mesh2dPipelineKey::TONEMAP_METHOD_REINHARD, Tonemapping::ReinhardLuminance => Mesh2dPipelineKey::TONEMAP_METHOD_REINHARD_LUMINANCE, Tonemapping::AcesFitted => Mesh2dPipelineKey::TONEMAP_METHOD_ACES_FITTED, Tonemapping::AgX => Mesh2dPipelineKey::TONEMAP_METHOD_AGX, Tonemapping::SomewhatBoringDisplayTransform => { Mesh2dPipelineKey::TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM } Tonemapping::TonyMcMapface => Mesh2dPipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE, Tonemapping::BlenderFilmic => Mesh2dPipelineKey::TONEMAP_METHOD_BLENDER_FILMIC, } } pub fn extract_entities_needs_specialization( entities_needing_specialization: Extract>>, mut entity_specialization_ticks: ResMut>, mut removed_mesh_material_components: Extract>>, mut specialized_material2d_pipeline_cache: ResMut>, views: Query<&MainEntity, With>, ticks: SystemChangeTick, ) where M: Material2d, { for entity in entities_needing_specialization.iter() { // Update the entity's specialization tick with this run's tick entity_specialization_ticks.insert((*entity).into(), ticks.this_run()); } // Clean up any despawned entities for entity in removed_mesh_material_components.read() { entity_specialization_ticks.remove(&MainEntity::from(entity)); for view in views { if let Some(cache) = specialized_material2d_pipeline_cache.get_mut(view) { cache.remove(&MainEntity::from(entity)); } } } } #[derive(Clone, Resource, Deref, DerefMut, Debug)] pub struct EntitiesNeedingSpecialization { #[deref] pub entities: Vec, _marker: PhantomData, } impl Default for EntitiesNeedingSpecialization { fn default() -> Self { Self { entities: Default::default(), _marker: Default::default(), } } } #[derive(Clone, Resource, Deref, DerefMut, Debug)] pub struct EntitySpecializationTicks { #[deref] pub entities: MainEntityHashMap, _marker: PhantomData, } impl Default for EntitySpecializationTicks { fn default() -> Self { Self { entities: MainEntityHashMap::default(), _marker: Default::default(), } } } /// Stores the [`SpecializedMaterial2dViewPipelineCache`] for each view. #[derive(Resource, Deref, DerefMut)] pub struct SpecializedMaterial2dPipelineCache { // view_entity -> view pipeline cache #[deref] map: MainEntityHashMap>, marker: PhantomData, } /// 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)] pub struct SpecializedMaterial2dViewPipelineCache { // material entity -> (tick, pipeline_id) #[deref] map: MainEntityHashMap<(Tick, CachedRenderPipelineId)>, marker: PhantomData, } impl Default for SpecializedMaterial2dPipelineCache { fn default() -> Self { Self { map: HashMap::default(), marker: PhantomData, } } } impl Default for SpecializedMaterial2dViewPipelineCache { fn default() -> Self { Self { map: HashMap::default(), marker: PhantomData, } } } pub fn check_entities_needing_specialization( needs_specialization: Query< Entity, ( Or<( Changed, AssetChanged, Changed>, AssetChanged>, )>, With>, ), >, mut par_local: Local>>, mut entities_needing_specialization: ResMut>, ) where M: Material2d, { entities_needing_specialization.clear(); needs_specialization .par_iter() .for_each(|entity| par_local.borrow_local_mut().push(entity)); par_local.drain_into(&mut entities_needing_specialization); } pub fn specialize_material2d_meshes( material2d_pipeline: Res>, mut pipelines: ResMut>>, pipeline_cache: Res, (render_meshes, render_materials): ( Res>, Res>>, ), mut render_mesh_instances: ResMut, render_material_instances: Res>, transparent_render_phases: Res>, opaque_render_phases: Res>, alpha_mask_render_phases: Res>, views: Query<(&MainEntity, &ExtractedView, &RenderVisibleEntities)>, view_key_cache: Res, entity_specialization_ticks: Res>, view_specialization_ticks: Res, ticks: SystemChangeTick, mut specialized_material_pipeline_cache: ResMut>, ) where M::Data: PartialEq + Eq + Hash + Clone, { if render_material_instances.is_empty() { return; } for (view_entity, view, visible_entities) in &views { if !transparent_render_phases.contains_key(&view.retained_view_entity) && !opaque_render_phases.contains_key(&view.retained_view_entity) && !alpha_mask_render_phases.contains_key(&view.retained_view_entity) { continue; } let Some(view_key) = view_key_cache.get(view_entity) else { continue; }; let view_tick = view_specialization_ticks.get(view_entity).unwrap(); let view_specialized_material_pipeline_cache = specialized_material_pipeline_cache .entry(*view_entity) .or_default(); for (_, visible_entity) in visible_entities.iter::() { let Some(material_asset_id) = render_material_instances.get(visible_entity) else { continue; }; let entity_tick = 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, ticks.this_run()) || entity_tick.is_newer_than(tick, ticks.this_run()) }); if !needs_specialization { continue; } let Some(mesh_instance) = render_mesh_instances.get_mut(visible_entity) else { continue; }; let Some(material_2d) = render_materials.get(*material_asset_id) else { continue; }; let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else { continue; }; let mesh_key = *view_key | Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology()) | material_2d.properties.mesh_pipeline_key_bits; let pipeline_id = pipelines.specialize( &pipeline_cache, &material2d_pipeline, Material2dKey { mesh_key, bind_group_data: material_2d.key.clone(), }, &mesh.layout, ); 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_material2d_meshes( (render_meshes, render_materials): ( Res>, Res>>, ), mut render_mesh_instances: ResMut, render_material_instances: Res>, mut transparent_render_phases: ResMut>, mut opaque_render_phases: ResMut>, mut alpha_mask_render_phases: ResMut>, views: Query<(&MainEntity, &ExtractedView, &RenderVisibleEntities)>, specialized_material_pipeline_cache: ResMut>, ) where M::Data: PartialEq + Eq + Hash + Clone, { if render_material_instances.is_empty() { return; } for (view_entity, view, visible_entities) in &views { let Some(view_specialized_material_pipeline_cache) = specialized_material_pipeline_cache.get(view_entity) else { continue; }; let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity) else { continue; }; let Some(opaque_phase) = opaque_render_phases.get_mut(&view.retained_view_entity) else { continue; }; let Some(alpha_mask_phase) = alpha_mask_render_phases.get_mut(&view.retained_view_entity) else { continue; }; for (render_entity, visible_entity) in visible_entities.iter::() { let Some((current_change_tick, pipeline_id)) = view_specialized_material_pipeline_cache .get(visible_entity) .map(|(current_change_tick, pipeline_id)| (*current_change_tick, *pipeline_id)) else { continue; }; // 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) { continue; } let Some(material_asset_id) = render_material_instances.get(visible_entity) else { continue; }; let Some(mesh_instance) = render_mesh_instances.get_mut(visible_entity) else { continue; }; let Some(material_2d) = render_materials.get(*material_asset_id) else { continue; }; let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else { continue; }; mesh_instance.material_bind_group_id = material_2d.get_bind_group_id(); let mesh_z = mesh_instance.transforms.world_from_local.translation.z; // We don't support multidraw yet for 2D meshes, so we use this // custom logic to generate the `BinnedRenderPhaseType` instead of // `BinnedRenderPhaseType::mesh`, which can return // `BinnedRenderPhaseType::MultidrawableMesh` if the hardware // supports multidraw. let binned_render_phase_type = if mesh_instance.automatic_batching { BinnedRenderPhaseType::BatchableMesh } else { BinnedRenderPhaseType::UnbatchableMesh }; match material_2d.properties.alpha_mode { AlphaMode2d::Opaque => { let bin_key = Opaque2dBinKey { pipeline: pipeline_id, draw_function: material_2d.properties.draw_function_id, asset_id: mesh_instance.mesh_asset_id.into(), material_bind_group_id: material_2d.get_bind_group_id().0, }; opaque_phase.add( BatchSetKey2d { indexed: mesh.indexed(), }, bin_key, (*render_entity, *visible_entity), InputUniformIndex::default(), binned_render_phase_type, current_change_tick, ); } AlphaMode2d::Mask(_) => { let bin_key = AlphaMask2dBinKey { pipeline: pipeline_id, draw_function: material_2d.properties.draw_function_id, asset_id: mesh_instance.mesh_asset_id.into(), material_bind_group_id: material_2d.get_bind_group_id().0, }; alpha_mask_phase.add( BatchSetKey2d { indexed: mesh.indexed(), }, bin_key, (*render_entity, *visible_entity), InputUniformIndex::default(), binned_render_phase_type, current_change_tick, ); } AlphaMode2d::Blend => { transparent_phase.add(Transparent2d { entity: (*render_entity, *visible_entity), draw_function: material_2d.properties.draw_function_id, pipeline: pipeline_id, // NOTE: Back-to-front ordering for transparent with ascending sort means far should have the // lowest sort key and getting closer should increase. As we have // -z in front of the camera, the largest distance is -far with values increasing toward the // camera. As such we can just use mesh_z as the distance sort_key: FloatOrd(mesh_z + material_2d.properties.depth_bias), // Batching is done in batch_and_prepare_render_phase batch_range: 0..1, extra_index: PhaseItemExtraIndex::None, extracted_index: usize::MAX, indexed: mesh.indexed(), }); } } } } } #[derive(Component, Clone, Copy, Default, PartialEq, Eq, Deref, DerefMut)] pub struct Material2dBindGroupId(pub Option); /// Common [`Material2d`] properties, calculated for a specific material instance. pub struct Material2dProperties { /// The [`AlphaMode2d`] of this material. pub alpha_mode: AlphaMode2d, /// 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 pub depth_bias: f32, /// The bits in the [`Mesh2dPipelineKey`] for this material. /// /// These are precalculated so that we can just "or" them together in /// [`queue_material2d_meshes`]. pub mesh_pipeline_key_bits: Mesh2dPipelineKey, pub draw_function_id: DrawFunctionId, } /// Data prepared for a [`Material2d`] instance. pub struct PreparedMaterial2d { pub bindings: BindingResources, pub bind_group: BindGroup, pub key: T::Data, pub properties: Material2dProperties, } impl PreparedMaterial2d { pub fn get_bind_group_id(&self) -> Material2dBindGroupId { Material2dBindGroupId(Some(self.bind_group.id())) } } impl RenderAsset for PreparedMaterial2d { type SourceAsset = M; type Param = ( SRes, SRes>, SRes>, SRes>, SRes>, M::Param, ); fn prepare_asset( material: Self::SourceAsset, _: AssetId, ( render_device, pipeline, opaque_draw_functions, alpha_mask_draw_functions, transparent_draw_functions, material_param, ): &mut SystemParamItem, ) -> Result> { match material.as_bind_group(&pipeline.material2d_layout, render_device, material_param) { Ok(prepared) => { let mut mesh_pipeline_key_bits = Mesh2dPipelineKey::empty(); mesh_pipeline_key_bits.insert(alpha_mode_pipeline_key(material.alpha_mode())); let draw_function_id = match material.alpha_mode() { AlphaMode2d::Opaque => opaque_draw_functions.read().id::>(), AlphaMode2d::Mask(_) => { alpha_mask_draw_functions.read().id::>() } AlphaMode2d::Blend => { transparent_draw_functions.read().id::>() } }; Ok(PreparedMaterial2d { bindings: prepared.bindings, bind_group: prepared.bind_group, key: prepared.data, properties: Material2dProperties { depth_bias: material.depth_bias(), alpha_mode: material.alpha_mode(), mesh_pipeline_key_bits, draw_function_id, }, }) } Err(AsBindGroupError::RetryNextUpdate) => { Err(PrepareAssetError::RetryNextUpdate(material)) } Err(other) => Err(PrepareAssetError::AsBindGroupError(other)), } } }