Path: blob/main/crates/bevy_sprite_render/src/mesh2d/material.rs
6849 views
use crate::{1init_mesh_2d_pipeline, DrawMesh2d, Mesh2d, Mesh2dPipeline, Mesh2dPipelineKey,2RenderMesh2dInstances, SetMesh2dBindGroup, SetMesh2dViewBindGroup, ViewKeyCache,3ViewSpecializationTicks,4};5use bevy_app::{App, Plugin, PostUpdate};6use bevy_asset::prelude::AssetChanged;7use bevy_asset::{AsAssetId, Asset, AssetApp, AssetEventSystems, AssetId, AssetServer, Handle};8use bevy_camera::visibility::ViewVisibility;9use bevy_core_pipeline::{10core_2d::{11AlphaMask2d, AlphaMask2dBinKey, BatchSetKey2d, Opaque2d, Opaque2dBinKey, Transparent2d,12},13tonemapping::Tonemapping,14};15use bevy_derive::{Deref, DerefMut};16use bevy_ecs::component::Tick;17use bevy_ecs::system::SystemChangeTick;18use bevy_ecs::{19prelude::*,20system::{lifetimeless::SRes, SystemParamItem},21};22use bevy_math::FloatOrd;23use bevy_mesh::MeshVertexBufferLayoutRef;24use bevy_platform::collections::HashMap;25use bevy_reflect::{prelude::ReflectDefault, Reflect};26use bevy_render::{27camera::extract_cameras,28mesh::RenderMesh,29render_asset::{30prepare_assets, PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets,31},32render_phase::{33AddRenderCommand, BinnedRenderPhaseType, DrawFunctionId, DrawFunctions, InputUniformIndex,34PhaseItem, PhaseItemExtraIndex, RenderCommand, RenderCommandResult, SetItemPipeline,35TrackedRenderPass, ViewBinnedRenderPhases, ViewSortedRenderPhases,36},37render_resource::{38AsBindGroup, AsBindGroupError, BindGroup, BindGroupId, BindGroupLayout, BindingResources,39CachedRenderPipelineId, PipelineCache, RenderPipelineDescriptor, SpecializedMeshPipeline,40SpecializedMeshPipelineError, SpecializedMeshPipelines,41},42renderer::RenderDevice,43sync_world::{MainEntity, MainEntityHashMap},44view::{ExtractedView, RenderVisibleEntities},45Extract, ExtractSchedule, Render, RenderApp, RenderStartup, RenderSystems,46};47use bevy_shader::{Shader, ShaderDefVal, ShaderRef};48use bevy_utils::Parallel;49use core::{hash::Hash, marker::PhantomData};50use derive_more::derive::From;51use tracing::error;5253pub const MATERIAL_2D_BIND_GROUP_INDEX: usize = 2;5455/// Materials are used alongside [`Material2dPlugin`], [`Mesh2d`], and [`MeshMaterial2d`]56/// to spawn entities that are rendered with a specific [`Material2d`] type. They serve as an easy to use high level57/// way to render [`Mesh2d`] entities with custom shader logic.58///59/// Materials must implement [`AsBindGroup`] to define how data will be transferred to the GPU and bound in shaders.60/// [`AsBindGroup`] can be derived, which makes generating bindings straightforward. See the [`AsBindGroup`] docs for details.61///62/// # Example63///64/// Here is a simple [`Material2d`] implementation. The [`AsBindGroup`] derive has many features. To see what else is available,65/// check out the [`AsBindGroup`] documentation.66///67/// ```68/// # use bevy_sprite_render::{Material2d, MeshMaterial2d};69/// # use bevy_ecs::prelude::*;70/// # use bevy_image::Image;71/// # use bevy_reflect::TypePath;72/// # use bevy_mesh::{Mesh, Mesh2d};73/// # use bevy_render::render_resource::AsBindGroup;74/// # use bevy_shader::ShaderRef;75/// # use bevy_color::LinearRgba;76/// # use bevy_color::palettes::basic::RED;77/// # use bevy_asset::{Handle, AssetServer, Assets, Asset};78/// # use bevy_math::primitives::Circle;79/// #80/// #[derive(AsBindGroup, Debug, Clone, Asset, TypePath)]81/// pub struct CustomMaterial {82/// // Uniform bindings must implement `ShaderType`, which will be used to convert the value to83/// // its shader-compatible equivalent. Most core math types already implement `ShaderType`.84/// #[uniform(0)]85/// color: LinearRgba,86/// // Images can be bound as textures in shaders. If the Image's sampler is also needed, just87/// // add the sampler attribute with a different binding index.88/// #[texture(1)]89/// #[sampler(2)]90/// color_texture: Handle<Image>,91/// }92///93/// // All functions on `Material2d` have default impls. You only need to implement the94/// // functions that are relevant for your material.95/// impl Material2d for CustomMaterial {96/// fn fragment_shader() -> ShaderRef {97/// "shaders/custom_material.wgsl".into()98/// }99/// }100///101/// // Spawn an entity with a mesh using `CustomMaterial`.102/// fn setup(103/// mut commands: Commands,104/// mut meshes: ResMut<Assets<Mesh>>,105/// mut materials: ResMut<Assets<CustomMaterial>>,106/// asset_server: Res<AssetServer>,107/// ) {108/// commands.spawn((109/// Mesh2d(meshes.add(Circle::new(50.0))),110/// MeshMaterial2d(materials.add(CustomMaterial {111/// color: RED.into(),112/// color_texture: asset_server.load("some_image.png"),113/// })),114/// ));115/// }116/// ```117///118/// In WGSL shaders, the material's binding would look like this:119///120/// ```wgsl121/// struct CustomMaterial {122/// color: vec4<f32>,123/// }124///125/// @group(2) @binding(0) var<uniform> material: CustomMaterial;126/// @group(2) @binding(1) var color_texture: texture_2d<f32>;127/// @group(2) @binding(2) var color_sampler: sampler;128/// ```129pub trait Material2d: AsBindGroup + Asset + Clone + Sized {130/// Returns this material's vertex shader. If [`ShaderRef::Default`] is returned, the default mesh vertex shader131/// will be used.132fn vertex_shader() -> ShaderRef {133ShaderRef::Default134}135136/// Returns this material's fragment shader. If [`ShaderRef::Default`] is returned, the default mesh fragment shader137/// will be used.138fn fragment_shader() -> ShaderRef {139ShaderRef::Default140}141142/// Add a bias to the view depth of the mesh which can be used to force a specific render order.143#[inline]144fn depth_bias(&self) -> f32 {1450.0146}147148fn alpha_mode(&self) -> AlphaMode2d {149AlphaMode2d::Opaque150}151152/// Customizes the default [`RenderPipelineDescriptor`].153#[expect(154unused_variables,155reason = "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."156)]157#[inline]158fn specialize(159descriptor: &mut RenderPipelineDescriptor,160layout: &MeshVertexBufferLayoutRef,161key: Material2dKey<Self>,162) -> Result<(), SpecializedMeshPipelineError> {163Ok(())164}165}166167/// A [material](Material2d) used for rendering a [`Mesh2d`].168///169/// See [`Material2d`] for general information about 2D materials and how to implement your own materials.170///171/// # Example172///173/// ```174/// # use bevy_sprite_render::{ColorMaterial, MeshMaterial2d};175/// # use bevy_ecs::prelude::*;176/// # use bevy_mesh::{Mesh, Mesh2d};177/// # use bevy_color::palettes::basic::RED;178/// # use bevy_asset::Assets;179/// # use bevy_math::primitives::Circle;180/// #181/// // Spawn an entity with a mesh using `ColorMaterial`.182/// fn setup(183/// mut commands: Commands,184/// mut meshes: ResMut<Assets<Mesh>>,185/// mut materials: ResMut<Assets<ColorMaterial>>,186/// ) {187/// commands.spawn((188/// Mesh2d(meshes.add(Circle::new(50.0))),189/// MeshMaterial2d(materials.add(ColorMaterial::from_color(RED))),190/// ));191/// }192/// ```193///194/// [`MeshMaterial2d`]: crate::MeshMaterial2d195#[derive(Component, Clone, Debug, Deref, DerefMut, Reflect, From)]196#[reflect(Component, Default, Clone)]197pub struct MeshMaterial2d<M: Material2d>(pub Handle<M>);198199impl<M: Material2d> Default for MeshMaterial2d<M> {200fn default() -> Self {201Self(Handle::default())202}203}204205impl<M: Material2d> PartialEq for MeshMaterial2d<M> {206fn eq(&self, other: &Self) -> bool {207self.0 == other.0208}209}210211impl<M: Material2d> Eq for MeshMaterial2d<M> {}212213impl<M: Material2d> From<MeshMaterial2d<M>> for AssetId<M> {214fn from(material: MeshMaterial2d<M>) -> Self {215material.id()216}217}218219impl<M: Material2d> From<&MeshMaterial2d<M>> for AssetId<M> {220fn from(material: &MeshMaterial2d<M>) -> Self {221material.id()222}223}224225impl<M: Material2d> AsAssetId for MeshMaterial2d<M> {226type Asset = M;227228fn as_asset_id(&self) -> AssetId<Self::Asset> {229self.id()230}231}232233/// Sets how a 2d material's base color alpha channel is used for transparency.234/// Currently, this only works with [`Mesh2d`]. Sprites are always transparent.235///236/// This is very similar to [`AlphaMode`](bevy_render::alpha::AlphaMode) but this only applies to 2d meshes.237/// We use a separate type because 2d doesn't support all the transparency modes that 3d does.238#[derive(Debug, Default, Reflect, Copy, Clone, PartialEq)]239#[reflect(Default, Debug, Clone)]240pub enum AlphaMode2d {241/// Base color alpha values are overridden to be fully opaque (1.0).242#[default]243Opaque,244/// Reduce transparency to fully opaque or fully transparent245/// based on a threshold.246///247/// Compares the base color alpha value to the specified threshold.248/// If the value is below the threshold,249/// considers the color to be fully transparent (alpha is set to 0.0).250/// If it is equal to or above the threshold,251/// considers the color to be fully opaque (alpha is set to 1.0).252Mask(f32),253/// The base color alpha value defines the opacity of the color.254/// Standard alpha-blending is used to blend the fragment's color255/// with the color behind it.256Blend,257}258259/// Adds the necessary ECS resources and render logic to enable rendering entities using the given [`Material2d`]260/// asset type (which includes [`Material2d`] types).261pub struct Material2dPlugin<M: Material2d>(PhantomData<M>);262263impl<M: Material2d> Default for Material2dPlugin<M> {264fn default() -> Self {265Self(Default::default())266}267}268269impl<M: Material2d> Plugin for Material2dPlugin<M>270where271M::Data: PartialEq + Eq + Hash + Clone,272{273fn build(&self, app: &mut App) {274app.init_asset::<M>()275.init_resource::<EntitiesNeedingSpecialization<M>>()276.register_type::<MeshMaterial2d<M>>()277.add_plugins(RenderAssetPlugin::<PreparedMaterial2d<M>>::default())278.add_systems(279PostUpdate,280check_entities_needing_specialization::<M>.after(AssetEventSystems),281);282283if let Some(render_app) = app.get_sub_app_mut(RenderApp) {284render_app285.init_resource::<EntitySpecializationTicks<M>>()286.init_resource::<SpecializedMaterial2dPipelineCache<M>>()287.add_render_command::<Opaque2d, DrawMaterial2d<M>>()288.add_render_command::<AlphaMask2d, DrawMaterial2d<M>>()289.add_render_command::<Transparent2d, DrawMaterial2d<M>>()290.init_resource::<RenderMaterial2dInstances<M>>()291.init_resource::<SpecializedMeshPipelines<Material2dPipeline<M>>>()292.add_systems(293RenderStartup,294init_material_2d_pipeline::<M>.after(init_mesh_2d_pipeline),295)296.add_systems(297ExtractSchedule,298(299extract_entities_needs_specialization::<M>.after(extract_cameras),300extract_mesh_materials_2d::<M>,301),302)303.add_systems(304Render,305(306specialize_material2d_meshes::<M>307.in_set(RenderSystems::PrepareMeshes)308.after(prepare_assets::<PreparedMaterial2d<M>>)309.after(prepare_assets::<RenderMesh>),310queue_material2d_meshes::<M>311.in_set(RenderSystems::QueueMeshes)312.after(prepare_assets::<PreparedMaterial2d<M>>),313),314);315}316}317}318319#[derive(Resource, Deref, DerefMut)]320pub struct RenderMaterial2dInstances<M: Material2d>(MainEntityHashMap<AssetId<M>>);321322impl<M: Material2d> Default for RenderMaterial2dInstances<M> {323fn default() -> Self {324Self(Default::default())325}326}327328pub fn extract_mesh_materials_2d<M: Material2d>(329mut material_instances: ResMut<RenderMaterial2dInstances<M>>,330changed_meshes_query: Extract<331Query<332(Entity, &ViewVisibility, &MeshMaterial2d<M>),333Or<(Changed<ViewVisibility>, Changed<MeshMaterial2d<M>>)>,334>,335>,336mut removed_materials_query: Extract<RemovedComponents<MeshMaterial2d<M>>>,337) {338for (entity, view_visibility, material) in &changed_meshes_query {339if view_visibility.get() {340add_mesh_instance(entity, material, &mut material_instances);341} else {342remove_mesh_instance(entity, &mut material_instances);343}344}345346for entity in removed_materials_query.read() {347// Only queue a mesh for removal if we didn't pick it up above.348// It's possible that a necessary component was removed and re-added in349// the same frame.350if !changed_meshes_query.contains(entity) {351remove_mesh_instance(entity, &mut material_instances);352}353}354355// Adds or updates a mesh instance in the [`RenderMaterial2dInstances`]356// array.357fn add_mesh_instance<M>(358entity: Entity,359material: &MeshMaterial2d<M>,360material_instances: &mut RenderMaterial2dInstances<M>,361) where362M: Material2d,363{364material_instances.insert(entity.into(), material.id());365}366367// Removes a mesh instance from the [`RenderMaterial2dInstances`] array.368fn remove_mesh_instance<M>(369entity: Entity,370material_instances: &mut RenderMaterial2dInstances<M>,371) where372M: Material2d,373{374material_instances.remove(&MainEntity::from(entity));375}376}377378/// Render pipeline data for a given [`Material2d`]379#[derive(Resource)]380pub struct Material2dPipeline<M: Material2d> {381pub mesh2d_pipeline: Mesh2dPipeline,382pub material2d_layout: BindGroupLayout,383pub vertex_shader: Option<Handle<Shader>>,384pub fragment_shader: Option<Handle<Shader>>,385marker: PhantomData<M>,386}387388pub struct Material2dKey<M: Material2d> {389pub mesh_key: Mesh2dPipelineKey,390pub bind_group_data: M::Data,391}392393impl<M: Material2d> Eq for Material2dKey<M> where M::Data: PartialEq {}394395impl<M: Material2d> PartialEq for Material2dKey<M>396where397M::Data: PartialEq,398{399fn eq(&self, other: &Self) -> bool {400self.mesh_key == other.mesh_key && self.bind_group_data == other.bind_group_data401}402}403404impl<M: Material2d> Clone for Material2dKey<M>405where406M::Data: Clone,407{408fn clone(&self) -> Self {409Self {410mesh_key: self.mesh_key,411bind_group_data: self.bind_group_data.clone(),412}413}414}415416impl<M: Material2d> Hash for Material2dKey<M>417where418M::Data: Hash,419{420fn hash<H: core::hash::Hasher>(&self, state: &mut H) {421self.mesh_key.hash(state);422self.bind_group_data.hash(state);423}424}425426impl<M: Material2d> Clone for Material2dPipeline<M> {427fn clone(&self) -> Self {428Self {429mesh2d_pipeline: self.mesh2d_pipeline.clone(),430material2d_layout: self.material2d_layout.clone(),431vertex_shader: self.vertex_shader.clone(),432fragment_shader: self.fragment_shader.clone(),433marker: PhantomData,434}435}436}437438impl<M: Material2d> SpecializedMeshPipeline for Material2dPipeline<M>439where440M::Data: PartialEq + Eq + Hash + Clone,441{442type Key = Material2dKey<M>;443444fn specialize(445&self,446key: Self::Key,447layout: &MeshVertexBufferLayoutRef,448) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {449let mut descriptor = self.mesh2d_pipeline.specialize(key.mesh_key, layout)?;450descriptor.vertex.shader_defs.push(ShaderDefVal::UInt(451"MATERIAL_BIND_GROUP".into(),452MATERIAL_2D_BIND_GROUP_INDEX as u32,453));454if let Some(ref mut fragment) = descriptor.fragment {455fragment.shader_defs.push(ShaderDefVal::UInt(456"MATERIAL_BIND_GROUP".into(),457MATERIAL_2D_BIND_GROUP_INDEX as u32,458));459}460if let Some(vertex_shader) = &self.vertex_shader {461descriptor.vertex.shader = vertex_shader.clone();462}463464if let Some(fragment_shader) = &self.fragment_shader {465descriptor.fragment.as_mut().unwrap().shader = fragment_shader.clone();466}467descriptor.layout = vec![468self.mesh2d_pipeline.view_layout.clone(),469self.mesh2d_pipeline.mesh_layout.clone(),470self.material2d_layout.clone(),471];472473M::specialize(&mut descriptor, layout, key)?;474Ok(descriptor)475}476}477478pub fn init_material_2d_pipeline<M: Material2d>(479mut commands: Commands,480render_device: Res<RenderDevice>,481asset_server: Res<AssetServer>,482mesh_2d_pipeline: Res<Mesh2dPipeline>,483) {484let material2d_layout = M::bind_group_layout(&render_device);485486commands.insert_resource(Material2dPipeline::<M> {487mesh2d_pipeline: mesh_2d_pipeline.clone(),488material2d_layout,489vertex_shader: match M::vertex_shader() {490ShaderRef::Default => None,491ShaderRef::Handle(handle) => Some(handle),492ShaderRef::Path(path) => Some(asset_server.load(path)),493},494fragment_shader: match M::fragment_shader() {495ShaderRef::Default => None,496ShaderRef::Handle(handle) => Some(handle),497ShaderRef::Path(path) => Some(asset_server.load(path)),498},499marker: PhantomData,500});501}502503pub(super) type DrawMaterial2d<M> = (504SetItemPipeline,505SetMesh2dViewBindGroup<0>,506SetMesh2dBindGroup<1>,507SetMaterial2dBindGroup<M, MATERIAL_2D_BIND_GROUP_INDEX>,508DrawMesh2d,509);510511pub struct SetMaterial2dBindGroup<M: Material2d, const I: usize>(PhantomData<M>);512impl<P: PhaseItem, M: Material2d, const I: usize> RenderCommand<P>513for SetMaterial2dBindGroup<M, I>514{515type Param = (516SRes<RenderAssets<PreparedMaterial2d<M>>>,517SRes<RenderMaterial2dInstances<M>>,518);519type ViewQuery = ();520type ItemQuery = ();521522#[inline]523fn render<'w>(524item: &P,525_view: (),526_item_query: Option<()>,527(materials, material_instances): SystemParamItem<'w, '_, Self::Param>,528pass: &mut TrackedRenderPass<'w>,529) -> RenderCommandResult {530let materials = materials.into_inner();531let material_instances = material_instances.into_inner();532let Some(material_instance) = material_instances.get(&item.main_entity()) else {533return RenderCommandResult::Skip;534};535let Some(material2d) = materials.get(*material_instance) else {536return RenderCommandResult::Skip;537};538pass.set_bind_group(I, &material2d.bind_group, &[]);539RenderCommandResult::Success540}541}542543pub const fn alpha_mode_pipeline_key(alpha_mode: AlphaMode2d) -> Mesh2dPipelineKey {544match alpha_mode {545AlphaMode2d::Blend => Mesh2dPipelineKey::BLEND_ALPHA,546AlphaMode2d::Mask(_) => Mesh2dPipelineKey::MAY_DISCARD,547_ => Mesh2dPipelineKey::NONE,548}549}550551pub const fn tonemapping_pipeline_key(tonemapping: Tonemapping) -> Mesh2dPipelineKey {552match tonemapping {553Tonemapping::None => Mesh2dPipelineKey::TONEMAP_METHOD_NONE,554Tonemapping::Reinhard => Mesh2dPipelineKey::TONEMAP_METHOD_REINHARD,555Tonemapping::ReinhardLuminance => Mesh2dPipelineKey::TONEMAP_METHOD_REINHARD_LUMINANCE,556Tonemapping::AcesFitted => Mesh2dPipelineKey::TONEMAP_METHOD_ACES_FITTED,557Tonemapping::AgX => Mesh2dPipelineKey::TONEMAP_METHOD_AGX,558Tonemapping::SomewhatBoringDisplayTransform => {559Mesh2dPipelineKey::TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM560}561Tonemapping::TonyMcMapface => Mesh2dPipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE,562Tonemapping::BlenderFilmic => Mesh2dPipelineKey::TONEMAP_METHOD_BLENDER_FILMIC,563}564}565566pub fn extract_entities_needs_specialization<M>(567entities_needing_specialization: Extract<Res<EntitiesNeedingSpecialization<M>>>,568mut entity_specialization_ticks: ResMut<EntitySpecializationTicks<M>>,569mut removed_mesh_material_components: Extract<RemovedComponents<MeshMaterial2d<M>>>,570mut specialized_material2d_pipeline_cache: ResMut<SpecializedMaterial2dPipelineCache<M>>,571views: Query<&MainEntity, With<ExtractedView>>,572ticks: SystemChangeTick,573) where574M: Material2d,575{576// Clean up any despawned entities, we do this first in case the removed material was re-added577// the same frame, thus will appear both in the removed components list and have been added to578// the `EntitiesNeedingSpecialization` collection by triggering the `Changed` filter579for entity in removed_mesh_material_components.read() {580entity_specialization_ticks.remove(&MainEntity::from(entity));581for view in views {582if let Some(cache) = specialized_material2d_pipeline_cache.get_mut(view) {583cache.remove(&MainEntity::from(entity));584}585}586}587for entity in entities_needing_specialization.iter() {588// Update the entity's specialization tick with this run's tick589entity_specialization_ticks.insert((*entity).into(), ticks.this_run());590}591}592593#[derive(Clone, Resource, Deref, DerefMut, Debug)]594pub struct EntitiesNeedingSpecialization<M> {595#[deref]596pub entities: Vec<Entity>,597_marker: PhantomData<M>,598}599600impl<M> Default for EntitiesNeedingSpecialization<M> {601fn default() -> Self {602Self {603entities: Default::default(),604_marker: Default::default(),605}606}607}608609#[derive(Clone, Resource, Deref, DerefMut, Debug)]610pub struct EntitySpecializationTicks<M> {611#[deref]612pub entities: MainEntityHashMap<Tick>,613_marker: PhantomData<M>,614}615616impl<M> Default for EntitySpecializationTicks<M> {617fn default() -> Self {618Self {619entities: MainEntityHashMap::default(),620_marker: Default::default(),621}622}623}624625/// Stores the [`SpecializedMaterial2dViewPipelineCache`] for each view.626#[derive(Resource, Deref, DerefMut)]627pub struct SpecializedMaterial2dPipelineCache<M> {628// view_entity -> view pipeline cache629#[deref]630map: MainEntityHashMap<SpecializedMaterial2dViewPipelineCache<M>>,631marker: PhantomData<M>,632}633634/// Stores the cached render pipeline ID for each entity in a single view, as635/// well as the last time it was changed.636#[derive(Deref, DerefMut)]637pub struct SpecializedMaterial2dViewPipelineCache<M> {638// material entity -> (tick, pipeline_id)639#[deref]640map: MainEntityHashMap<(Tick, CachedRenderPipelineId)>,641marker: PhantomData<M>,642}643644impl<M> Default for SpecializedMaterial2dPipelineCache<M> {645fn default() -> Self {646Self {647map: HashMap::default(),648marker: PhantomData,649}650}651}652653impl<M> Default for SpecializedMaterial2dViewPipelineCache<M> {654fn default() -> Self {655Self {656map: HashMap::default(),657marker: PhantomData,658}659}660}661662pub fn check_entities_needing_specialization<M>(663needs_specialization: Query<664Entity,665(666Or<(667Changed<Mesh2d>,668AssetChanged<Mesh2d>,669Changed<MeshMaterial2d<M>>,670AssetChanged<MeshMaterial2d<M>>,671)>,672With<MeshMaterial2d<M>>,673),674>,675mut par_local: Local<Parallel<Vec<Entity>>>,676mut entities_needing_specialization: ResMut<EntitiesNeedingSpecialization<M>>,677) where678M: Material2d,679{680entities_needing_specialization.clear();681682needs_specialization683.par_iter()684.for_each(|entity| par_local.borrow_local_mut().push(entity));685686par_local.drain_into(&mut entities_needing_specialization);687}688689pub fn specialize_material2d_meshes<M: Material2d>(690material2d_pipeline: Res<Material2dPipeline<M>>,691mut pipelines: ResMut<SpecializedMeshPipelines<Material2dPipeline<M>>>,692pipeline_cache: Res<PipelineCache>,693(render_meshes, render_materials): (694Res<RenderAssets<RenderMesh>>,695Res<RenderAssets<PreparedMaterial2d<M>>>,696),697mut render_mesh_instances: ResMut<RenderMesh2dInstances>,698render_material_instances: Res<RenderMaterial2dInstances<M>>,699transparent_render_phases: Res<ViewSortedRenderPhases<Transparent2d>>,700opaque_render_phases: Res<ViewBinnedRenderPhases<Opaque2d>>,701alpha_mask_render_phases: Res<ViewBinnedRenderPhases<AlphaMask2d>>,702views: Query<(&MainEntity, &ExtractedView, &RenderVisibleEntities)>,703view_key_cache: Res<ViewKeyCache>,704entity_specialization_ticks: Res<EntitySpecializationTicks<M>>,705view_specialization_ticks: Res<ViewSpecializationTicks>,706ticks: SystemChangeTick,707mut specialized_material_pipeline_cache: ResMut<SpecializedMaterial2dPipelineCache<M>>,708) where709M::Data: PartialEq + Eq + Hash + Clone,710{711if render_material_instances.is_empty() {712return;713}714715for (view_entity, view, visible_entities) in &views {716if !transparent_render_phases.contains_key(&view.retained_view_entity)717&& !opaque_render_phases.contains_key(&view.retained_view_entity)718&& !alpha_mask_render_phases.contains_key(&view.retained_view_entity)719{720continue;721}722723let Some(view_key) = view_key_cache.get(view_entity) else {724continue;725};726727let view_tick = view_specialization_ticks.get(view_entity).unwrap();728let view_specialized_material_pipeline_cache = specialized_material_pipeline_cache729.entry(*view_entity)730.or_default();731732for (_, visible_entity) in visible_entities.iter::<Mesh2d>() {733let Some(material_asset_id) = render_material_instances.get(visible_entity) else {734continue;735};736let Some(mesh_instance) = render_mesh_instances.get_mut(visible_entity) else {737continue;738};739let Some(entity_tick) = entity_specialization_ticks.get(visible_entity) else {740error!("{visible_entity:?} is missing specialization tick. Spawning Meshes in PostUpdate or later is currently not fully supported.");741continue;742};743let last_specialized_tick = view_specialized_material_pipeline_cache744.get(visible_entity)745.map(|(tick, _)| *tick);746let needs_specialization = last_specialized_tick.is_none_or(|tick| {747view_tick.is_newer_than(tick, ticks.this_run())748|| entity_tick.is_newer_than(tick, ticks.this_run())749});750if !needs_specialization {751continue;752}753let Some(material_2d) = render_materials.get(*material_asset_id) else {754continue;755};756let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else {757continue;758};759let mesh_key = *view_key760| Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology())761| material_2d.properties.mesh_pipeline_key_bits;762763let pipeline_id = pipelines.specialize(764&pipeline_cache,765&material2d_pipeline,766Material2dKey {767mesh_key,768bind_group_data: material_2d.key.clone(),769},770&mesh.layout,771);772773let pipeline_id = match pipeline_id {774Ok(id) => id,775Err(err) => {776error!("{}", err);777continue;778}779};780781view_specialized_material_pipeline_cache782.insert(*visible_entity, (ticks.this_run(), pipeline_id));783}784}785}786787pub fn queue_material2d_meshes<M: Material2d>(788(render_meshes, render_materials): (789Res<RenderAssets<RenderMesh>>,790Res<RenderAssets<PreparedMaterial2d<M>>>,791),792mut render_mesh_instances: ResMut<RenderMesh2dInstances>,793render_material_instances: Res<RenderMaterial2dInstances<M>>,794mut transparent_render_phases: ResMut<ViewSortedRenderPhases<Transparent2d>>,795mut opaque_render_phases: ResMut<ViewBinnedRenderPhases<Opaque2d>>,796mut alpha_mask_render_phases: ResMut<ViewBinnedRenderPhases<AlphaMask2d>>,797views: Query<(&MainEntity, &ExtractedView, &RenderVisibleEntities)>,798specialized_material_pipeline_cache: ResMut<SpecializedMaterial2dPipelineCache<M>>,799) where800M::Data: PartialEq + Eq + Hash + Clone,801{802if render_material_instances.is_empty() {803return;804}805806for (view_entity, view, visible_entities) in &views {807let Some(view_specialized_material_pipeline_cache) =808specialized_material_pipeline_cache.get(view_entity)809else {810continue;811};812813let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity)814else {815continue;816};817let Some(opaque_phase) = opaque_render_phases.get_mut(&view.retained_view_entity) else {818continue;819};820let Some(alpha_mask_phase) = alpha_mask_render_phases.get_mut(&view.retained_view_entity)821else {822continue;823};824825for (render_entity, visible_entity) in visible_entities.iter::<Mesh2d>() {826let Some((current_change_tick, pipeline_id)) = view_specialized_material_pipeline_cache827.get(visible_entity)828.map(|(current_change_tick, pipeline_id)| (*current_change_tick, *pipeline_id))829else {830continue;831};832833// Skip the entity if it's cached in a bin and up to date.834if opaque_phase.validate_cached_entity(*visible_entity, current_change_tick)835|| alpha_mask_phase.validate_cached_entity(*visible_entity, current_change_tick)836{837continue;838}839840let Some(material_asset_id) = render_material_instances.get(visible_entity) else {841continue;842};843let Some(mesh_instance) = render_mesh_instances.get_mut(visible_entity) else {844continue;845};846let Some(material_2d) = render_materials.get(*material_asset_id) else {847continue;848};849let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else {850continue;851};852853mesh_instance.material_bind_group_id = material_2d.get_bind_group_id();854let mesh_z = mesh_instance.transforms.world_from_local.translation.z;855856// We don't support multidraw yet for 2D meshes, so we use this857// custom logic to generate the `BinnedRenderPhaseType` instead of858// `BinnedRenderPhaseType::mesh`, which can return859// `BinnedRenderPhaseType::MultidrawableMesh` if the hardware860// supports multidraw.861let binned_render_phase_type = if mesh_instance.automatic_batching {862BinnedRenderPhaseType::BatchableMesh863} else {864BinnedRenderPhaseType::UnbatchableMesh865};866867match material_2d.properties.alpha_mode {868AlphaMode2d::Opaque => {869let bin_key = Opaque2dBinKey {870pipeline: pipeline_id,871draw_function: material_2d.properties.draw_function_id,872asset_id: mesh_instance.mesh_asset_id.into(),873material_bind_group_id: material_2d.get_bind_group_id().0,874};875opaque_phase.add(876BatchSetKey2d {877indexed: mesh.indexed(),878},879bin_key,880(*render_entity, *visible_entity),881InputUniformIndex::default(),882binned_render_phase_type,883current_change_tick,884);885}886AlphaMode2d::Mask(_) => {887let bin_key = AlphaMask2dBinKey {888pipeline: pipeline_id,889draw_function: material_2d.properties.draw_function_id,890asset_id: mesh_instance.mesh_asset_id.into(),891material_bind_group_id: material_2d.get_bind_group_id().0,892};893alpha_mask_phase.add(894BatchSetKey2d {895indexed: mesh.indexed(),896},897bin_key,898(*render_entity, *visible_entity),899InputUniformIndex::default(),900binned_render_phase_type,901current_change_tick,902);903}904AlphaMode2d::Blend => {905transparent_phase.add(Transparent2d {906entity: (*render_entity, *visible_entity),907draw_function: material_2d.properties.draw_function_id,908pipeline: pipeline_id,909// NOTE: Back-to-front ordering for transparent with ascending sort means far should have the910// lowest sort key and getting closer should increase. As we have911// -z in front of the camera, the largest distance is -far with values increasing toward the912// camera. As such we can just use mesh_z as the distance913sort_key: FloatOrd(mesh_z + material_2d.properties.depth_bias),914// Batching is done in batch_and_prepare_render_phase915batch_range: 0..1,916extra_index: PhaseItemExtraIndex::None,917extracted_index: usize::MAX,918indexed: mesh.indexed(),919});920}921}922}923}924}925926#[derive(Component, Clone, Copy, Default, PartialEq, Eq, Deref, DerefMut)]927pub struct Material2dBindGroupId(pub Option<BindGroupId>);928929/// Common [`Material2d`] properties, calculated for a specific material instance.930pub struct Material2dProperties {931/// The [`AlphaMode2d`] of this material.932pub alpha_mode: AlphaMode2d,933/// Add a bias to the view depth of the mesh which can be used to force a specific render order934/// for meshes with equal depth, to avoid z-fighting.935/// The bias is in depth-texture units so large values may936pub depth_bias: f32,937/// The bits in the [`Mesh2dPipelineKey`] for this material.938///939/// These are precalculated so that we can just "or" them together in940/// [`queue_material2d_meshes`].941pub mesh_pipeline_key_bits: Mesh2dPipelineKey,942pub draw_function_id: DrawFunctionId,943}944945/// Data prepared for a [`Material2d`] instance.946pub struct PreparedMaterial2d<T: Material2d> {947pub bindings: BindingResources,948pub bind_group: BindGroup,949pub key: T::Data,950pub properties: Material2dProperties,951}952953impl<T: Material2d> PreparedMaterial2d<T> {954pub fn get_bind_group_id(&self) -> Material2dBindGroupId {955Material2dBindGroupId(Some(self.bind_group.id()))956}957}958959impl<M: Material2d> RenderAsset for PreparedMaterial2d<M> {960type SourceAsset = M;961962type Param = (963SRes<RenderDevice>,964SRes<Material2dPipeline<M>>,965SRes<DrawFunctions<Opaque2d>>,966SRes<DrawFunctions<AlphaMask2d>>,967SRes<DrawFunctions<Transparent2d>>,968M::Param,969);970971fn prepare_asset(972material: Self::SourceAsset,973_: AssetId<Self::SourceAsset>,974(975render_device,976pipeline,977opaque_draw_functions,978alpha_mask_draw_functions,979transparent_draw_functions,980material_param,981): &mut SystemParamItem<Self::Param>,982_: Option<&Self>,983) -> Result<Self, PrepareAssetError<Self::SourceAsset>> {984let bind_group_data = material.bind_group_data();985match material.as_bind_group(&pipeline.material2d_layout, render_device, material_param) {986Ok(prepared) => {987let mut mesh_pipeline_key_bits = Mesh2dPipelineKey::empty();988mesh_pipeline_key_bits.insert(alpha_mode_pipeline_key(material.alpha_mode()));989990let draw_function_id = match material.alpha_mode() {991AlphaMode2d::Opaque => opaque_draw_functions.read().id::<DrawMaterial2d<M>>(),992AlphaMode2d::Mask(_) => {993alpha_mask_draw_functions.read().id::<DrawMaterial2d<M>>()994}995AlphaMode2d::Blend => {996transparent_draw_functions.read().id::<DrawMaterial2d<M>>()997}998};9991000Ok(PreparedMaterial2d {1001bindings: prepared.bindings,1002bind_group: prepared.bind_group,1003key: bind_group_data,1004properties: Material2dProperties {1005depth_bias: material.depth_bias(),1006alpha_mode: material.alpha_mode(),1007mesh_pipeline_key_bits,1008draw_function_id,1009},1010})1011}1012Err(AsBindGroupError::RetryNextUpdate) => {1013Err(PrepareAssetError::RetryNextUpdate(material))1014}1015Err(other) => Err(PrepareAssetError::AsBindGroupError(other)),1016}1017}1018}101910201021