bevy_pbr/
material.rs

1use crate::material_bind_groups::{
2    FallbackBindlessResources, MaterialBindGroupAllocator, MaterialBindingId,
3};
4use crate::*;
5use alloc::sync::Arc;
6use bevy_asset::prelude::AssetChanged;
7use bevy_asset::{Asset, AssetEventSystems, AssetId, AssetServer, UntypedAssetId};
8use bevy_camera::visibility::ViewVisibility;
9use bevy_camera::ScreenSpaceTransmissionQuality;
10use bevy_core_pipeline::deferred::{AlphaMask3dDeferred, Opaque3dDeferred};
11use bevy_core_pipeline::prepass::{AlphaMask3dPrepass, Opaque3dPrepass};
12use bevy_core_pipeline::{
13    core_3d::{
14        AlphaMask3d, Opaque3d, Opaque3dBatchSetKey, Opaque3dBinKey, Transmissive3d, Transparent3d,
15    },
16    prepass::{OpaqueNoLightmap3dBatchSetKey, OpaqueNoLightmap3dBinKey},
17    tonemapping::Tonemapping,
18};
19use bevy_derive::{Deref, DerefMut};
20use bevy_ecs::component::Tick;
21use bevy_ecs::system::SystemChangeTick;
22use bevy_ecs::{
23    prelude::*,
24    system::{
25        lifetimeless::{SRes, SResMut},
26        SystemParamItem,
27    },
28};
29use bevy_mesh::{
30    mark_3d_meshes_as_changed_if_their_assets_changed, Mesh3d, MeshVertexBufferLayoutRef,
31};
32use bevy_platform::collections::hash_map::Entry;
33use bevy_platform::collections::{HashMap, HashSet};
34use bevy_platform::hash::FixedHasher;
35use bevy_reflect::std_traits::ReflectDefault;
36use bevy_reflect::Reflect;
37use bevy_render::camera::extract_cameras;
38use bevy_render::erased_render_asset::{
39    ErasedRenderAsset, ErasedRenderAssetPlugin, ErasedRenderAssets, PrepareAssetError,
40};
41use bevy_render::render_asset::{prepare_assets, RenderAssets};
42use bevy_render::renderer::RenderQueue;
43use bevy_render::RenderStartup;
44use bevy_render::{
45    batching::gpu_preprocessing::GpuPreprocessingSupport,
46    extract_resource::ExtractResource,
47    mesh::RenderMesh,
48    prelude::*,
49    render_phase::*,
50    render_resource::*,
51    renderer::RenderDevice,
52    sync_world::MainEntity,
53    view::{ExtractedView, Msaa, RenderVisibilityRanges, RetainedViewEntity},
54    Extract,
55};
56use bevy_render::{mesh::allocator::MeshAllocator, sync_world::MainEntityHashMap};
57use bevy_render::{texture::FallbackImage, view::RenderVisibleEntities};
58use bevy_shader::{Shader, ShaderDefVal};
59use bevy_utils::Parallel;
60use core::any::{Any, TypeId};
61use core::hash::{BuildHasher, Hasher};
62use core::{hash::Hash, marker::PhantomData};
63use smallvec::SmallVec;
64use tracing::error;
65
66pub const MATERIAL_BIND_GROUP_INDEX: usize = 3;
67
68/// Materials are used alongside [`MaterialPlugin`], [`Mesh3d`], and [`MeshMaterial3d`]
69/// to spawn entities that are rendered with a specific [`Material`] type. They serve as an easy to use high level
70/// way to render [`Mesh3d`] entities with custom shader logic.
71///
72/// Materials must implement [`AsBindGroup`] to define how data will be transferred to the GPU and bound in shaders.
73/// [`AsBindGroup`] can be derived, which makes generating bindings straightforward. See the [`AsBindGroup`] docs for details.
74///
75/// # Example
76///
77/// Here is a simple [`Material`] implementation. The [`AsBindGroup`] derive has many features. To see what else is available,
78/// check out the [`AsBindGroup`] documentation.
79///
80/// ```
81/// # use bevy_pbr::{Material, MeshMaterial3d};
82/// # use bevy_ecs::prelude::*;
83/// # use bevy_image::Image;
84/// # use bevy_reflect::TypePath;
85/// # use bevy_mesh::{Mesh, Mesh3d};
86/// # use bevy_render::render_resource::AsBindGroup;
87/// # use bevy_shader::ShaderRef;
88/// # use bevy_color::LinearRgba;
89/// # use bevy_color::palettes::basic::RED;
90/// # use bevy_asset::{Handle, AssetServer, Assets, Asset};
91/// # use bevy_math::primitives::Capsule3d;
92/// #
93/// #[derive(AsBindGroup, Debug, Clone, Asset, TypePath)]
94/// pub struct CustomMaterial {
95///     // Uniform bindings must implement `ShaderType`, which will be used to convert the value to
96///     // its shader-compatible equivalent. Most core math types already implement `ShaderType`.
97///     #[uniform(0)]
98///     color: LinearRgba,
99///     // Images can be bound as textures in shaders. If the Image's sampler is also needed, just
100///     // add the sampler attribute with a different binding index.
101///     #[texture(1)]
102///     #[sampler(2)]
103///     color_texture: Handle<Image>,
104/// }
105///
106/// // All functions on `Material` have default impls. You only need to implement the
107/// // functions that are relevant for your material.
108/// impl Material for CustomMaterial {
109///     fn fragment_shader() -> ShaderRef {
110///         "shaders/custom_material.wgsl".into()
111///     }
112/// }
113///
114/// // Spawn an entity with a mesh using `CustomMaterial`.
115/// fn setup(
116///     mut commands: Commands,
117///     mut meshes: ResMut<Assets<Mesh>>,
118///     mut materials: ResMut<Assets<CustomMaterial>>,
119///     asset_server: Res<AssetServer>
120/// ) {
121///     commands.spawn((
122///         Mesh3d(meshes.add(Capsule3d::default())),
123///         MeshMaterial3d(materials.add(CustomMaterial {
124///             color: RED.into(),
125///             color_texture: asset_server.load("some_image.png"),
126///         })),
127///     ));
128/// }
129/// ```
130///
131/// In WGSL shaders, the material's binding would look like this:
132///
133/// ```wgsl
134/// @group(#{MATERIAL_BIND_GROUP}) @binding(0) var<uniform> color: vec4<f32>;
135/// @group(#{MATERIAL_BIND_GROUP}) @binding(1) var color_texture: texture_2d<f32>;
136/// @group(#{MATERIAL_BIND_GROUP}) @binding(2) var color_sampler: sampler;
137/// ```
138pub trait Material: Asset + AsBindGroup + Clone + Sized {
139    /// Returns this material's vertex shader. If [`ShaderRef::Default`] is returned, the default mesh vertex shader
140    /// will be used.
141    fn vertex_shader() -> ShaderRef {
142        ShaderRef::Default
143    }
144
145    /// Returns this material's fragment shader. If [`ShaderRef::Default`] is returned, the default mesh fragment shader
146    /// will be used.
147    fn fragment_shader() -> ShaderRef {
148        ShaderRef::Default
149    }
150
151    /// Returns this material's [`AlphaMode`]. Defaults to [`AlphaMode::Opaque`].
152    #[inline]
153    fn alpha_mode(&self) -> AlphaMode {
154        AlphaMode::Opaque
155    }
156
157    /// Returns if this material should be rendered by the deferred or forward renderer.
158    /// for `AlphaMode::Opaque` or `AlphaMode::Mask` materials.
159    /// If `OpaqueRendererMethod::Auto`, it will default to what is selected in the `DefaultOpaqueRendererMethod` resource.
160    #[inline]
161    fn opaque_render_method(&self) -> OpaqueRendererMethod {
162        OpaqueRendererMethod::Forward
163    }
164
165    #[inline]
166    /// Add a bias to the view depth of the mesh which can be used to force a specific render order.
167    /// for meshes with similar depth, to avoid z-fighting.
168    /// The bias is in depth-texture units so large values may be needed to overcome small depth differences.
169    fn depth_bias(&self) -> f32 {
170        0.0
171    }
172
173    #[inline]
174    /// Returns whether the material would like to read from [`ViewTransmissionTexture`](bevy_core_pipeline::core_3d::ViewTransmissionTexture).
175    ///
176    /// This allows taking color output from the [`Opaque3d`] pass as an input, (for screen-space transmission) but requires
177    /// rendering to take place in a separate [`Transmissive3d`] pass.
178    fn reads_view_transmission_texture(&self) -> bool {
179        false
180    }
181
182    /// Returns this material's prepass vertex shader. If [`ShaderRef::Default`] is returned, the default prepass vertex shader
183    /// will be used.
184    ///
185    /// This is used for the various [prepasses](bevy_core_pipeline::prepass) as well as for generating the depth maps
186    /// required for shadow mapping.
187    fn prepass_vertex_shader() -> ShaderRef {
188        ShaderRef::Default
189    }
190
191    /// Returns this material's prepass fragment shader. If [`ShaderRef::Default`] is returned, the default prepass fragment shader
192    /// will be used.
193    ///
194    /// This is used for the various [prepasses](bevy_core_pipeline::prepass) as well as for generating the depth maps
195    /// required for shadow mapping.
196    fn prepass_fragment_shader() -> ShaderRef {
197        ShaderRef::Default
198    }
199
200    /// Returns this material's deferred vertex shader. If [`ShaderRef::Default`] is returned, the default deferred vertex shader
201    /// will be used.
202    fn deferred_vertex_shader() -> ShaderRef {
203        ShaderRef::Default
204    }
205
206    /// Returns this material's deferred fragment shader. If [`ShaderRef::Default`] is returned, the default deferred fragment shader
207    /// will be used.
208    fn deferred_fragment_shader() -> ShaderRef {
209        ShaderRef::Default
210    }
211
212    /// Returns this material's [`crate::meshlet::MeshletMesh`] fragment shader. If [`ShaderRef::Default`] is returned,
213    /// the default meshlet mesh fragment shader will be used.
214    ///
215    /// This is part of an experimental feature, and is unnecessary to implement unless you are using `MeshletMesh`'s.
216    ///
217    /// See [`crate::meshlet::MeshletMesh`] for limitations.
218    #[cfg(feature = "meshlet")]
219    fn meshlet_mesh_fragment_shader() -> ShaderRef {
220        ShaderRef::Default
221    }
222
223    /// Returns this material's [`crate::meshlet::MeshletMesh`] prepass fragment shader. If [`ShaderRef::Default`] is returned,
224    /// the default meshlet mesh prepass fragment shader will be used.
225    ///
226    /// This is part of an experimental feature, and is unnecessary to implement unless you are using `MeshletMesh`'s.
227    ///
228    /// See [`crate::meshlet::MeshletMesh`] for limitations.
229    #[cfg(feature = "meshlet")]
230    fn meshlet_mesh_prepass_fragment_shader() -> ShaderRef {
231        ShaderRef::Default
232    }
233
234    /// Returns this material's [`crate::meshlet::MeshletMesh`] deferred fragment shader. If [`ShaderRef::Default`] is returned,
235    /// the default meshlet mesh deferred fragment shader will be used.
236    ///
237    /// This is part of an experimental feature, and is unnecessary to implement unless you are using `MeshletMesh`'s.
238    ///
239    /// See [`crate::meshlet::MeshletMesh`] for limitations.
240    #[cfg(feature = "meshlet")]
241    fn meshlet_mesh_deferred_fragment_shader() -> ShaderRef {
242        ShaderRef::Default
243    }
244
245    /// Customizes the default [`RenderPipelineDescriptor`] for a specific entity using the entity's
246    /// [`MaterialPipelineKey`] and [`MeshVertexBufferLayoutRef`] as input.
247    #[expect(
248        unused_variables,
249        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."
250    )]
251    #[inline]
252    fn specialize(
253        pipeline: &MaterialPipeline,
254        descriptor: &mut RenderPipelineDescriptor,
255        layout: &MeshVertexBufferLayoutRef,
256        key: MaterialPipelineKey<Self>,
257    ) -> Result<(), SpecializedMeshPipelineError> {
258        Ok(())
259    }
260}
261
262#[derive(Default)]
263pub struct MaterialsPlugin {
264    /// Debugging flags that can optionally be set when constructing the renderer.
265    pub debug_flags: RenderDebugFlags,
266}
267
268impl Plugin for MaterialsPlugin {
269    fn build(&self, app: &mut App) {
270        app.add_plugins((PrepassPipelinePlugin, PrepassPlugin::new(self.debug_flags)));
271        if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
272            render_app
273                .init_resource::<EntitySpecializationTicks>()
274                .init_resource::<SpecializedMaterialPipelineCache>()
275                .init_resource::<SpecializedMeshPipelines<MaterialPipelineSpecializer>>()
276                .init_resource::<LightKeyCache>()
277                .init_resource::<LightSpecializationTicks>()
278                .init_resource::<SpecializedShadowMaterialPipelineCache>()
279                .init_resource::<DrawFunctions<Shadow>>()
280                .init_resource::<RenderMaterialInstances>()
281                .init_resource::<MaterialBindGroupAllocators>()
282                .add_render_command::<Shadow, DrawPrepass>()
283                .add_render_command::<Transmissive3d, DrawMaterial>()
284                .add_render_command::<Transparent3d, DrawMaterial>()
285                .add_render_command::<Opaque3d, DrawMaterial>()
286                .add_render_command::<AlphaMask3d, DrawMaterial>()
287                .add_systems(RenderStartup, init_material_pipeline)
288                .add_systems(
289                    Render,
290                    (
291                        specialize_material_meshes
292                            .in_set(RenderSystems::PrepareMeshes)
293                            .after(prepare_assets::<RenderMesh>)
294                            .after(collect_meshes_for_gpu_building)
295                            .after(set_mesh_motion_vector_flags),
296                        queue_material_meshes.in_set(RenderSystems::QueueMeshes),
297                    ),
298                )
299                .add_systems(
300                    Render,
301                    (
302                        prepare_material_bind_groups,
303                        write_material_bind_group_buffers,
304                    )
305                        .chain()
306                        .in_set(RenderSystems::PrepareBindGroups),
307                )
308                .add_systems(
309                    Render,
310                    (
311                        check_views_lights_need_specialization.in_set(RenderSystems::PrepareAssets),
312                        // specialize_shadows also needs to run after prepare_assets::<PreparedMaterial>,
313                        // which is fine since ManageViews is after PrepareAssets
314                        specialize_shadows
315                            .in_set(RenderSystems::ManageViews)
316                            .after(prepare_lights),
317                        queue_shadows.in_set(RenderSystems::QueueMeshes),
318                    ),
319                );
320        }
321    }
322}
323
324/// Adds the necessary ECS resources and render logic to enable rendering entities using the given [`Material`]
325/// asset type.
326pub struct MaterialPlugin<M: Material> {
327    /// Controls if the prepass is enabled for the Material.
328    /// For more information about what a prepass is, see the [`bevy_core_pipeline::prepass`] docs.
329    ///
330    /// When it is enabled, it will automatically add the [`PrepassPlugin`]
331    /// required to make the prepass work on this Material.
332    pub prepass_enabled: bool,
333    /// Controls if shadows are enabled for the Material.
334    pub shadows_enabled: bool,
335    /// Debugging flags that can optionally be set when constructing the renderer.
336    pub debug_flags: RenderDebugFlags,
337    pub _marker: PhantomData<M>,
338}
339
340impl<M: Material> Default for MaterialPlugin<M> {
341    fn default() -> Self {
342        Self {
343            prepass_enabled: true,
344            shadows_enabled: true,
345            debug_flags: RenderDebugFlags::default(),
346            _marker: Default::default(),
347        }
348    }
349}
350
351impl<M: Material> Plugin for MaterialPlugin<M>
352where
353    M::Data: PartialEq + Eq + Hash + Clone,
354{
355    fn build(&self, app: &mut App) {
356        app.init_asset::<M>()
357            .register_type::<MeshMaterial3d<M>>()
358            .init_resource::<EntitiesNeedingSpecialization<M>>()
359            .add_plugins((ErasedRenderAssetPlugin::<MeshMaterial3d<M>>::default(),))
360            .add_systems(
361                PostUpdate,
362                (
363                    mark_meshes_as_changed_if_their_materials_changed::<M>.ambiguous_with_all(),
364                    check_entities_needing_specialization::<M>.after(AssetEventSystems),
365                )
366                    .after(mark_3d_meshes_as_changed_if_their_assets_changed),
367            );
368
369        if self.shadows_enabled {
370            app.add_systems(
371                PostUpdate,
372                check_light_entities_needing_specialization::<M>
373                    .after(check_entities_needing_specialization::<M>),
374            );
375        }
376
377        if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
378            if self.prepass_enabled {
379                render_app.init_resource::<PrepassEnabled<M>>();
380            }
381            if self.shadows_enabled {
382                render_app.init_resource::<ShadowsEnabled<M>>();
383            }
384
385            render_app
386                .add_systems(RenderStartup, add_material_bind_group_allocator::<M>)
387                .add_systems(
388                    ExtractSchedule,
389                    (
390                        extract_mesh_materials::<M>.in_set(MaterialExtractionSystems),
391                        early_sweep_material_instances::<M>
392                            .after(MaterialExtractionSystems)
393                            .before(late_sweep_material_instances),
394                        // See the comments in
395                        // `sweep_entities_needing_specialization` for an
396                        // explanation of why the systems are ordered this way.
397                        extract_entities_needs_specialization::<M>
398                            .in_set(MaterialExtractEntitiesNeedingSpecializationSystems),
399                        sweep_entities_needing_specialization::<M>
400                            .after(MaterialExtractEntitiesNeedingSpecializationSystems)
401                            .after(MaterialExtractionSystems)
402                            .after(extract_cameras)
403                            .before(late_sweep_material_instances),
404                    ),
405                );
406        }
407    }
408}
409
410fn add_material_bind_group_allocator<M: Material>(
411    render_device: Res<RenderDevice>,
412    mut bind_group_allocators: ResMut<MaterialBindGroupAllocators>,
413) {
414    bind_group_allocators.insert(
415        TypeId::of::<M>(),
416        MaterialBindGroupAllocator::new(
417            &render_device,
418            M::label(),
419            material_uses_bindless_resources::<M>(&render_device)
420                .then(|| M::bindless_descriptor())
421                .flatten(),
422            M::bind_group_layout(&render_device),
423            M::bindless_slot_count(),
424        ),
425    );
426}
427
428/// A dummy [`AssetId`] that we use as a placeholder whenever a mesh doesn't
429/// have a material.
430///
431/// See the comments in [`RenderMaterialInstances::mesh_material`] for more
432/// information.
433pub(crate) static DUMMY_MESH_MATERIAL: AssetId<StandardMaterial> =
434    AssetId::<StandardMaterial>::invalid();
435
436/// A key uniquely identifying a specialized [`MaterialPipeline`].
437pub struct MaterialPipelineKey<M: Material> {
438    pub mesh_key: MeshPipelineKey,
439    pub bind_group_data: M::Data,
440}
441
442#[derive(Clone, Debug, PartialEq, Eq, Hash)]
443pub struct ErasedMaterialPipelineKey {
444    pub mesh_key: MeshPipelineKey,
445    pub material_key: ErasedMaterialKey,
446    pub type_id: TypeId,
447}
448
449/// Render pipeline data for a given [`Material`].
450#[derive(Resource, Clone)]
451pub struct MaterialPipeline {
452    pub mesh_pipeline: MeshPipeline,
453}
454
455pub struct MaterialPipelineSpecializer {
456    pub(crate) pipeline: MaterialPipeline,
457    pub(crate) properties: Arc<MaterialProperties>,
458}
459
460impl SpecializedMeshPipeline for MaterialPipelineSpecializer {
461    type Key = ErasedMaterialPipelineKey;
462
463    fn specialize(
464        &self,
465        key: Self::Key,
466        layout: &MeshVertexBufferLayoutRef,
467    ) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
468        let mut descriptor = self
469            .pipeline
470            .mesh_pipeline
471            .specialize(key.mesh_key, layout)?;
472        descriptor.vertex.shader_defs.push(ShaderDefVal::UInt(
473            "MATERIAL_BIND_GROUP".into(),
474            MATERIAL_BIND_GROUP_INDEX as u32,
475        ));
476        if let Some(ref mut fragment) = descriptor.fragment {
477            fragment.shader_defs.push(ShaderDefVal::UInt(
478                "MATERIAL_BIND_GROUP".into(),
479                MATERIAL_BIND_GROUP_INDEX as u32,
480            ));
481        };
482        if let Some(vertex_shader) = self.properties.get_shader(MaterialVertexShader) {
483            descriptor.vertex.shader = vertex_shader.clone();
484        }
485
486        if let Some(fragment_shader) = self.properties.get_shader(MaterialFragmentShader) {
487            descriptor.fragment.as_mut().unwrap().shader = fragment_shader.clone();
488        }
489
490        descriptor
491            .layout
492            .insert(3, self.properties.material_layout.as_ref().unwrap().clone());
493
494        if let Some(specialize) = self.properties.specialize {
495            specialize(&self.pipeline, &mut descriptor, layout, key)?;
496        }
497
498        // If bindless mode is on, add a `BINDLESS` define.
499        if self.properties.bindless {
500            descriptor.vertex.shader_defs.push("BINDLESS".into());
501            if let Some(ref mut fragment) = descriptor.fragment {
502                fragment.shader_defs.push("BINDLESS".into());
503            }
504        }
505
506        Ok(descriptor)
507    }
508}
509
510pub fn init_material_pipeline(mut commands: Commands, mesh_pipeline: Res<MeshPipeline>) {
511    commands.insert_resource(MaterialPipeline {
512        mesh_pipeline: mesh_pipeline.clone(),
513    });
514}
515
516pub type DrawMaterial = (
517    SetItemPipeline,
518    SetMeshViewBindGroup<0>,
519    SetMeshViewBindingArrayBindGroup<1>,
520    SetMeshBindGroup<2>,
521    SetMaterialBindGroup<MATERIAL_BIND_GROUP_INDEX>,
522    DrawMesh,
523);
524
525/// Sets the bind group for a given [`Material`] at the configured `I` index.
526pub struct SetMaterialBindGroup<const I: usize>;
527impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetMaterialBindGroup<I> {
528    type Param = (
529        SRes<ErasedRenderAssets<PreparedMaterial>>,
530        SRes<RenderMaterialInstances>,
531        SRes<MaterialBindGroupAllocators>,
532    );
533    type ViewQuery = ();
534    type ItemQuery = ();
535
536    #[inline]
537    fn render<'w>(
538        item: &P,
539        _view: (),
540        _item_query: Option<()>,
541        (materials, material_instances, material_bind_group_allocator): SystemParamItem<
542            'w,
543            '_,
544            Self::Param,
545        >,
546        pass: &mut TrackedRenderPass<'w>,
547    ) -> RenderCommandResult {
548        let materials = materials.into_inner();
549        let material_instances = material_instances.into_inner();
550        let material_bind_group_allocators = material_bind_group_allocator.into_inner();
551
552        let Some(material_instance) = material_instances.instances.get(&item.main_entity()) else {
553            return RenderCommandResult::Skip;
554        };
555        let Some(material_bind_group_allocator) =
556            material_bind_group_allocators.get(&material_instance.asset_id.type_id())
557        else {
558            return RenderCommandResult::Skip;
559        };
560        let Some(material) = materials.get(material_instance.asset_id) else {
561            return RenderCommandResult::Skip;
562        };
563        let Some(material_bind_group) = material_bind_group_allocator.get(material.binding.group)
564        else {
565            return RenderCommandResult::Skip;
566        };
567        let Some(bind_group) = material_bind_group.bind_group() else {
568            return RenderCommandResult::Skip;
569        };
570        pass.set_bind_group(I, bind_group, &[]);
571        RenderCommandResult::Success
572    }
573}
574
575/// Stores all extracted instances of all [`Material`]s in the render world.
576#[derive(Resource, Default)]
577pub struct RenderMaterialInstances {
578    /// Maps from each entity in the main world to the
579    /// [`RenderMaterialInstance`] associated with it.
580    pub instances: MainEntityHashMap<RenderMaterialInstance>,
581    /// A monotonically-increasing counter, which we use to sweep
582    /// [`RenderMaterialInstances::instances`] when the entities and/or required
583    /// components are removed.
584    pub current_change_tick: Tick,
585}
586
587impl RenderMaterialInstances {
588    /// Returns the mesh material ID for the entity with the given mesh, or a
589    /// dummy mesh material ID if the mesh has no material ID.
590    ///
591    /// Meshes almost always have materials, but in very specific circumstances
592    /// involving custom pipelines they won't. (See the
593    /// `specialized_mesh_pipelines` example.)
594    pub(crate) fn mesh_material(&self, entity: MainEntity) -> UntypedAssetId {
595        match self.instances.get(&entity) {
596            Some(render_instance) => render_instance.asset_id,
597            None => DUMMY_MESH_MATERIAL.into(),
598        }
599    }
600}
601
602/// The material associated with a single mesh instance in the main world.
603///
604/// Note that this uses an [`UntypedAssetId`] and isn't generic over the
605/// material type, for simplicity.
606pub struct RenderMaterialInstance {
607    /// The material asset.
608    pub asset_id: UntypedAssetId,
609    /// The [`RenderMaterialInstances::current_change_tick`] at which this
610    /// material instance was last modified.
611    pub last_change_tick: Tick,
612}
613
614/// A [`SystemSet`] that contains all `extract_mesh_materials` systems.
615#[derive(SystemSet, Clone, PartialEq, Eq, Debug, Hash)]
616pub struct MaterialExtractionSystems;
617
618/// A [`SystemSet`] that contains all `extract_entities_needs_specialization`
619/// systems.
620#[derive(SystemSet, Clone, PartialEq, Eq, Debug, Hash)]
621pub struct MaterialExtractEntitiesNeedingSpecializationSystems;
622
623/// Deprecated alias for [`MaterialExtractionSystems`].
624#[deprecated(since = "0.17.0", note = "Renamed to `MaterialExtractionSystems`.")]
625pub type ExtractMaterialsSet = MaterialExtractionSystems;
626
627pub const fn alpha_mode_pipeline_key(alpha_mode: AlphaMode, msaa: &Msaa) -> MeshPipelineKey {
628    match alpha_mode {
629        // Premultiplied and Add share the same pipeline key
630        // They're made distinct in the PBR shader, via `premultiply_alpha()`
631        AlphaMode::Premultiplied | AlphaMode::Add => MeshPipelineKey::BLEND_PREMULTIPLIED_ALPHA,
632        AlphaMode::Blend => MeshPipelineKey::BLEND_ALPHA,
633        AlphaMode::Multiply => MeshPipelineKey::BLEND_MULTIPLY,
634        AlphaMode::Mask(_) => MeshPipelineKey::MAY_DISCARD,
635        AlphaMode::AlphaToCoverage => match *msaa {
636            Msaa::Off => MeshPipelineKey::MAY_DISCARD,
637            _ => MeshPipelineKey::BLEND_ALPHA_TO_COVERAGE,
638        },
639        _ => MeshPipelineKey::NONE,
640    }
641}
642
643pub const fn tonemapping_pipeline_key(tonemapping: Tonemapping) -> MeshPipelineKey {
644    match tonemapping {
645        Tonemapping::None => MeshPipelineKey::TONEMAP_METHOD_NONE,
646        Tonemapping::Reinhard => MeshPipelineKey::TONEMAP_METHOD_REINHARD,
647        Tonemapping::ReinhardLuminance => MeshPipelineKey::TONEMAP_METHOD_REINHARD_LUMINANCE,
648        Tonemapping::AcesFitted => MeshPipelineKey::TONEMAP_METHOD_ACES_FITTED,
649        Tonemapping::AgX => MeshPipelineKey::TONEMAP_METHOD_AGX,
650        Tonemapping::SomewhatBoringDisplayTransform => {
651            MeshPipelineKey::TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM
652        }
653        Tonemapping::TonyMcMapface => MeshPipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE,
654        Tonemapping::BlenderFilmic => MeshPipelineKey::TONEMAP_METHOD_BLENDER_FILMIC,
655    }
656}
657
658pub const fn screen_space_specular_transmission_pipeline_key(
659    screen_space_transmissive_blur_quality: ScreenSpaceTransmissionQuality,
660) -> MeshPipelineKey {
661    match screen_space_transmissive_blur_quality {
662        ScreenSpaceTransmissionQuality::Low => {
663            MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_LOW
664        }
665        ScreenSpaceTransmissionQuality::Medium => {
666            MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_MEDIUM
667        }
668        ScreenSpaceTransmissionQuality::High => {
669            MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_HIGH
670        }
671        ScreenSpaceTransmissionQuality::Ultra => {
672            MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_ULTRA
673        }
674    }
675}
676
677/// A system that ensures that
678/// [`crate::render::mesh::extract_meshes_for_gpu_building`] re-extracts meshes
679/// whose materials changed.
680///
681/// As [`crate::render::mesh::collect_meshes_for_gpu_building`] only considers
682/// meshes that were newly extracted, and it writes information from the
683/// [`RenderMaterialInstances`] into the
684/// [`crate::render::mesh::MeshInputUniform`], we must tell
685/// [`crate::render::mesh::extract_meshes_for_gpu_building`] to re-extract a
686/// mesh if its material changed. Otherwise, the material binding information in
687/// the [`crate::render::mesh::MeshInputUniform`] might not be updated properly.
688/// The easiest way to ensure that
689/// [`crate::render::mesh::extract_meshes_for_gpu_building`] re-extracts a mesh
690/// is to mark its [`Mesh3d`] as changed, so that's what this system does.
691fn mark_meshes_as_changed_if_their_materials_changed<M>(
692    mut changed_meshes_query: Query<
693        &mut Mesh3d,
694        Or<(Changed<MeshMaterial3d<M>>, AssetChanged<MeshMaterial3d<M>>)>,
695    >,
696) where
697    M: Material,
698{
699    for mut mesh in &mut changed_meshes_query {
700        mesh.set_changed();
701    }
702}
703
704/// Fills the [`RenderMaterialInstances`] resources from the meshes in the
705/// scene.
706fn extract_mesh_materials<M: Material>(
707    mut material_instances: ResMut<RenderMaterialInstances>,
708    changed_meshes_query: Extract<
709        Query<
710            (Entity, &ViewVisibility, &MeshMaterial3d<M>),
711            Or<(Changed<ViewVisibility>, Changed<MeshMaterial3d<M>>)>,
712        >,
713    >,
714) {
715    let last_change_tick = material_instances.current_change_tick;
716
717    for (entity, view_visibility, material) in &changed_meshes_query {
718        if view_visibility.get() {
719            material_instances.instances.insert(
720                entity.into(),
721                RenderMaterialInstance {
722                    asset_id: material.id().untyped(),
723                    last_change_tick,
724                },
725            );
726        } else {
727            material_instances
728                .instances
729                .remove(&MainEntity::from(entity));
730        }
731    }
732}
733
734/// Removes mesh materials from [`RenderMaterialInstances`] when their
735/// [`MeshMaterial3d`] components are removed.
736///
737/// This is tricky because we have to deal with the case in which a material of
738/// type A was removed and replaced with a material of type B in the same frame
739/// (which is actually somewhat common of an operation). In this case, even
740/// though an entry will be present in `RemovedComponents<MeshMaterial3d<A>>`,
741/// we must not remove the entry in `RenderMaterialInstances` which corresponds
742/// to material B. To handle this case, we use change ticks to avoid removing
743/// the entry if it was updated this frame.
744///
745/// This is the first of two sweep phases. Because this phase runs once per
746/// material type, we need a second phase in order to guarantee that we only
747/// bump [`RenderMaterialInstances::current_change_tick`] once.
748fn early_sweep_material_instances<M>(
749    mut material_instances: ResMut<RenderMaterialInstances>,
750    mut removed_materials_query: Extract<RemovedComponents<MeshMaterial3d<M>>>,
751) where
752    M: Material,
753{
754    let last_change_tick = material_instances.current_change_tick;
755
756    for entity in removed_materials_query.read() {
757        if let Entry::Occupied(occupied_entry) = material_instances.instances.entry(entity.into()) {
758            // Only sweep the entry if it wasn't updated this frame.
759            if occupied_entry.get().last_change_tick != last_change_tick {
760                occupied_entry.remove();
761            }
762        }
763    }
764}
765
766/// Removes mesh materials from [`RenderMaterialInstances`] when their
767/// [`ViewVisibility`] components are removed.
768///
769/// This runs after all invocations of `early_sweep_material_instances` and is
770/// responsible for bumping [`RenderMaterialInstances::current_change_tick`] in
771/// preparation for a new frame.
772pub fn late_sweep_material_instances(
773    mut material_instances: ResMut<RenderMaterialInstances>,
774    mut removed_meshes_query: Extract<RemovedComponents<Mesh3d>>,
775) {
776    let last_change_tick = material_instances.current_change_tick;
777
778    for entity in removed_meshes_query.read() {
779        if let Entry::Occupied(occupied_entry) = material_instances.instances.entry(entity.into()) {
780            // Only sweep the entry if it wasn't updated this frame. It's
781            // possible that a `ViewVisibility` component was removed and
782            // re-added in the same frame.
783            if occupied_entry.get().last_change_tick != last_change_tick {
784                occupied_entry.remove();
785            }
786        }
787    }
788
789    material_instances
790        .current_change_tick
791        .set(last_change_tick.get() + 1);
792}
793
794pub fn extract_entities_needs_specialization<M>(
795    entities_needing_specialization: Extract<Res<EntitiesNeedingSpecialization<M>>>,
796    mut entity_specialization_ticks: ResMut<EntitySpecializationTicks>,
797    render_material_instances: Res<RenderMaterialInstances>,
798    ticks: SystemChangeTick,
799) where
800    M: Material,
801{
802    for entity in entities_needing_specialization.iter() {
803        // Update the entity's specialization tick with this run's tick
804        entity_specialization_ticks.insert(
805            (*entity).into(),
806            EntitySpecializationTickPair {
807                system_tick: ticks.this_run(),
808                material_instances_tick: render_material_instances.current_change_tick,
809            },
810        );
811    }
812}
813
814/// A system that runs after all instances of
815/// [`extract_entities_needs_specialization`] in order to delete specialization
816/// ticks for entities that are no longer renderable.
817///
818/// We delete entities from the [`EntitySpecializationTicks`] table *after*
819/// updating it with newly-discovered renderable entities in order to handle the
820/// case in which a single entity changes material types. If we naïvely removed
821/// entities from that table when their [`MeshMaterial3d<M>`] components were
822/// removed, and an entity changed material types, we might end up adding a new
823/// set of [`EntitySpecializationTickPair`] for the new material and then
824/// deleting it upon detecting the removed component for the old material.
825/// Deferring [`sweep_entities_needing_specialization`] to the end allows us to
826/// detect the case in which another material type updated the entity
827/// specialization ticks this frame and avoid deleting it if so.
828pub fn sweep_entities_needing_specialization<M>(
829    mut entity_specialization_ticks: ResMut<EntitySpecializationTicks>,
830    mut removed_mesh_material_components: Extract<RemovedComponents<MeshMaterial3d<M>>>,
831    mut specialized_material_pipeline_cache: ResMut<SpecializedMaterialPipelineCache>,
832    mut specialized_prepass_material_pipeline_cache: Option<
833        ResMut<SpecializedPrepassMaterialPipelineCache>,
834    >,
835    mut specialized_shadow_material_pipeline_cache: Option<
836        ResMut<SpecializedShadowMaterialPipelineCache>,
837    >,
838    render_material_instances: Res<RenderMaterialInstances>,
839    views: Query<&ExtractedView>,
840) where
841    M: Material,
842{
843    // Clean up any despawned entities, we do this first in case the removed material was re-added
844    // the same frame, thus will appear both in the removed components list and have been added to
845    // the `EntitiesNeedingSpecialization` collection by triggering the `Changed` filter
846    //
847    // Additionally, we need to make sure that we are careful about materials
848    // that could have changed type, e.g. from a `StandardMaterial` to a
849    // `CustomMaterial`, as this will also appear in the removed components
850    // list. As such, we make sure that this system runs after
851    // `extract_entities_needs_specialization` so that the entity specialization
852    // tick bookkeeping has already been done, and we can check if the entity's
853    // tick was updated this frame.
854    for entity in removed_mesh_material_components.read() {
855        // If the entity's specialization tick was updated this frame, that
856        // means that that entity changed materials this frame. Don't remove the
857        // entity from the table in that case.
858        if entity_specialization_ticks
859            .get(&MainEntity::from(entity))
860            .is_some_and(|ticks| {
861                ticks.material_instances_tick == render_material_instances.current_change_tick
862            })
863        {
864            continue;
865        }
866
867        entity_specialization_ticks.remove(&MainEntity::from(entity));
868        for view in views {
869            if let Some(cache) =
870                specialized_material_pipeline_cache.get_mut(&view.retained_view_entity)
871            {
872                cache.remove(&MainEntity::from(entity));
873            }
874            if let Some(cache) = specialized_prepass_material_pipeline_cache
875                .as_mut()
876                .and_then(|c| c.get_mut(&view.retained_view_entity))
877            {
878                cache.remove(&MainEntity::from(entity));
879            }
880            if let Some(cache) = specialized_shadow_material_pipeline_cache
881                .as_mut()
882                .and_then(|c| c.get_mut(&view.retained_view_entity))
883            {
884                cache.remove(&MainEntity::from(entity));
885            }
886        }
887    }
888}
889
890#[derive(Resource, Deref, DerefMut, Clone, Debug)]
891pub struct EntitiesNeedingSpecialization<M> {
892    #[deref]
893    pub entities: Vec<Entity>,
894    _marker: PhantomData<M>,
895}
896
897impl<M> Default for EntitiesNeedingSpecialization<M> {
898    fn default() -> Self {
899        Self {
900            entities: Default::default(),
901            _marker: Default::default(),
902        }
903    }
904}
905
906/// Stores ticks specifying the last time Bevy specialized the pipelines of each
907/// entity.
908///
909/// Every entity that has a mesh and material must be present in this table,
910/// even if that mesh isn't visible.
911#[derive(Resource, Deref, DerefMut, Default, Clone, Debug)]
912pub struct EntitySpecializationTicks {
913    /// A mapping from each main entity to ticks that specify the last time this
914    /// entity's pipeline was specialized.
915    ///
916    /// Every entity that has a mesh and material must be present in this table,
917    /// even if that mesh isn't visible.
918    #[deref]
919    pub entities: MainEntityHashMap<EntitySpecializationTickPair>,
920}
921
922/// Ticks that specify the last time an entity's pipeline was specialized.
923///
924/// We need two different types of ticks here for a subtle reason. First, we
925/// need the [`Self::system_tick`], which maps to Bevy's [`SystemChangeTick`],
926/// because that's what we use in [`specialize_material_meshes`] to check
927/// whether pipelines need specialization. But we also need
928/// [`Self::material_instances_tick`], which maps to the
929/// [`RenderMaterialInstances::current_change_tick`]. That's because the latter
930/// only changes once per frame, which is a guarantee we need to handle the
931/// following case:
932///
933/// 1. The app removes material A from a mesh and replaces it with material B.
934///    Both A and B are of different [`Material`] types entirely.
935///
936/// 2. [`extract_entities_needs_specialization`] runs for material B and marks
937///    the mesh as up to date by recording the current tick.
938///
939/// 3. [`sweep_entities_needing_specialization`] runs for material A and checks
940///    to ensure it's safe to remove the [`EntitySpecializationTickPair`] for the mesh
941///    from the [`EntitySpecializationTicks`]. To do this, it needs to know
942///    whether [`extract_entities_needs_specialization`] for some *different*
943///    material (in this case, material B) ran earlier in the frame and updated the
944///    change tick, and to skip removing the [`EntitySpecializationTickPair`] if so.
945///    It can't reliably use the [`Self::system_tick`] to determine this because
946///    the [`SystemChangeTick`] can be updated multiple times in the same frame.
947///    Instead, it needs a type of tick that's updated only once per frame, after
948///    all materials' versions of [`sweep_entities_needing_specialization`] have
949///    run. The [`RenderMaterialInstances`] tick satisfies this criterion, and so
950///    that's what [`sweep_entities_needing_specialization`] uses.
951#[derive(Clone, Copy, Debug)]
952pub struct EntitySpecializationTickPair {
953    /// The standard Bevy system tick.
954    pub system_tick: Tick,
955    /// The tick in [`RenderMaterialInstances`], which is updated in
956    /// `late_sweep_material_instances`.
957    pub material_instances_tick: Tick,
958}
959
960/// Stores the [`SpecializedMaterialViewPipelineCache`] for each view.
961#[derive(Resource, Deref, DerefMut, Default)]
962pub struct SpecializedMaterialPipelineCache {
963    // view entity -> view pipeline cache
964    #[deref]
965    map: HashMap<RetainedViewEntity, SpecializedMaterialViewPipelineCache>,
966}
967
968/// Stores the cached render pipeline ID for each entity in a single view, as
969/// well as the last time it was changed.
970#[derive(Deref, DerefMut, Default)]
971pub struct SpecializedMaterialViewPipelineCache {
972    // material entity -> (tick, pipeline_id)
973    #[deref]
974    map: MainEntityHashMap<(Tick, CachedRenderPipelineId)>,
975}
976
977pub fn check_entities_needing_specialization<M>(
978    needs_specialization: Query<
979        Entity,
980        (
981            Or<(
982                Changed<Mesh3d>,
983                AssetChanged<Mesh3d>,
984                Changed<MeshMaterial3d<M>>,
985                AssetChanged<MeshMaterial3d<M>>,
986            )>,
987            With<MeshMaterial3d<M>>,
988        ),
989    >,
990    mut par_local: Local<Parallel<Vec<Entity>>>,
991    mut entities_needing_specialization: ResMut<EntitiesNeedingSpecialization<M>>,
992) where
993    M: Material,
994{
995    entities_needing_specialization.clear();
996
997    needs_specialization
998        .par_iter()
999        .for_each(|entity| par_local.borrow_local_mut().push(entity));
1000
1001    par_local.drain_into(&mut entities_needing_specialization);
1002}
1003
1004pub fn specialize_material_meshes(
1005    render_meshes: Res<RenderAssets<RenderMesh>>,
1006    render_materials: Res<ErasedRenderAssets<PreparedMaterial>>,
1007    render_mesh_instances: Res<RenderMeshInstances>,
1008    render_material_instances: Res<RenderMaterialInstances>,
1009    render_lightmaps: Res<RenderLightmaps>,
1010    render_visibility_ranges: Res<RenderVisibilityRanges>,
1011    (
1012        opaque_render_phases,
1013        alpha_mask_render_phases,
1014        transmissive_render_phases,
1015        transparent_render_phases,
1016    ): (
1017        Res<ViewBinnedRenderPhases<Opaque3d>>,
1018        Res<ViewBinnedRenderPhases<AlphaMask3d>>,
1019        Res<ViewSortedRenderPhases<Transmissive3d>>,
1020        Res<ViewSortedRenderPhases<Transparent3d>>,
1021    ),
1022    views: Query<(&ExtractedView, &RenderVisibleEntities)>,
1023    view_key_cache: Res<ViewKeyCache>,
1024    entity_specialization_ticks: Res<EntitySpecializationTicks>,
1025    view_specialization_ticks: Res<ViewSpecializationTicks>,
1026    mut specialized_material_pipeline_cache: ResMut<SpecializedMaterialPipelineCache>,
1027    mut pipelines: ResMut<SpecializedMeshPipelines<MaterialPipelineSpecializer>>,
1028    pipeline: Res<MaterialPipeline>,
1029    pipeline_cache: Res<PipelineCache>,
1030    ticks: SystemChangeTick,
1031) {
1032    // Record the retained IDs of all shadow views so that we can expire old
1033    // pipeline IDs.
1034    let mut all_views: HashSet<RetainedViewEntity, FixedHasher> = HashSet::default();
1035
1036    for (view, visible_entities) in &views {
1037        all_views.insert(view.retained_view_entity);
1038
1039        if !transparent_render_phases.contains_key(&view.retained_view_entity)
1040            && !opaque_render_phases.contains_key(&view.retained_view_entity)
1041            && !alpha_mask_render_phases.contains_key(&view.retained_view_entity)
1042            && !transmissive_render_phases.contains_key(&view.retained_view_entity)
1043        {
1044            continue;
1045        }
1046
1047        let Some(view_key) = view_key_cache.get(&view.retained_view_entity) else {
1048            continue;
1049        };
1050
1051        let view_tick = view_specialization_ticks
1052            .get(&view.retained_view_entity)
1053            .unwrap();
1054        let view_specialized_material_pipeline_cache = specialized_material_pipeline_cache
1055            .entry(view.retained_view_entity)
1056            .or_default();
1057
1058        for (_, visible_entity) in visible_entities.iter::<Mesh3d>() {
1059            let Some(material_instance) = render_material_instances.instances.get(visible_entity)
1060            else {
1061                continue;
1062            };
1063            let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity)
1064            else {
1065                continue;
1066            };
1067            let entity_tick = entity_specialization_ticks
1068                .get(visible_entity)
1069                .unwrap()
1070                .system_tick;
1071            let last_specialized_tick = view_specialized_material_pipeline_cache
1072                .get(visible_entity)
1073                .map(|(tick, _)| *tick);
1074            let needs_specialization = last_specialized_tick.is_none_or(|tick| {
1075                view_tick.is_newer_than(tick, ticks.this_run())
1076                    || entity_tick.is_newer_than(tick, ticks.this_run())
1077            });
1078            if !needs_specialization {
1079                continue;
1080            }
1081            let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else {
1082                continue;
1083            };
1084            let Some(material) = render_materials.get(material_instance.asset_id) else {
1085                continue;
1086            };
1087
1088            let mut mesh_pipeline_key_bits = material.properties.mesh_pipeline_key_bits;
1089            mesh_pipeline_key_bits.insert(alpha_mode_pipeline_key(
1090                material.properties.alpha_mode,
1091                &Msaa::from_samples(view_key.msaa_samples()),
1092            ));
1093            let mut mesh_key = *view_key
1094                | MeshPipelineKey::from_bits_retain(mesh.key_bits.bits())
1095                | mesh_pipeline_key_bits;
1096
1097            if let Some(lightmap) = render_lightmaps.render_lightmaps.get(visible_entity) {
1098                mesh_key |= MeshPipelineKey::LIGHTMAPPED;
1099
1100                if lightmap.bicubic_sampling {
1101                    mesh_key |= MeshPipelineKey::LIGHTMAP_BICUBIC_SAMPLING;
1102                }
1103            }
1104
1105            if render_visibility_ranges.entity_has_crossfading_visibility_ranges(*visible_entity) {
1106                mesh_key |= MeshPipelineKey::VISIBILITY_RANGE_DITHER;
1107            }
1108
1109            if view_key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) {
1110                // If the previous frame have skins or morph targets, note that.
1111                if mesh_instance
1112                    .flags
1113                    .contains(RenderMeshInstanceFlags::HAS_PREVIOUS_SKIN)
1114                {
1115                    mesh_key |= MeshPipelineKey::HAS_PREVIOUS_SKIN;
1116                }
1117                if mesh_instance
1118                    .flags
1119                    .contains(RenderMeshInstanceFlags::HAS_PREVIOUS_MORPH)
1120                {
1121                    mesh_key |= MeshPipelineKey::HAS_PREVIOUS_MORPH;
1122                }
1123            }
1124
1125            let erased_key = ErasedMaterialPipelineKey {
1126                type_id: material_instance.asset_id.type_id(),
1127                mesh_key,
1128                material_key: material.properties.material_key.clone(),
1129            };
1130            let material_pipeline_specializer = MaterialPipelineSpecializer {
1131                pipeline: pipeline.clone(),
1132                properties: material.properties.clone(),
1133            };
1134            let pipeline_id = pipelines.specialize(
1135                &pipeline_cache,
1136                &material_pipeline_specializer,
1137                erased_key,
1138                &mesh.layout,
1139            );
1140            let pipeline_id = match pipeline_id {
1141                Ok(id) => id,
1142                Err(err) => {
1143                    error!("{}", err);
1144                    continue;
1145                }
1146            };
1147
1148            view_specialized_material_pipeline_cache
1149                .insert(*visible_entity, (ticks.this_run(), pipeline_id));
1150        }
1151    }
1152
1153    // Delete specialized pipelines belonging to views that have expired.
1154    specialized_material_pipeline_cache
1155        .retain(|retained_view_entity, _| all_views.contains(retained_view_entity));
1156}
1157
1158/// For each view, iterates over all the meshes visible from that view and adds
1159/// them to [`BinnedRenderPhase`]s or [`SortedRenderPhase`]s as appropriate.
1160pub fn queue_material_meshes(
1161    render_materials: Res<ErasedRenderAssets<PreparedMaterial>>,
1162    render_mesh_instances: Res<RenderMeshInstances>,
1163    render_material_instances: Res<RenderMaterialInstances>,
1164    mesh_allocator: Res<MeshAllocator>,
1165    gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
1166    mut opaque_render_phases: ResMut<ViewBinnedRenderPhases<Opaque3d>>,
1167    mut alpha_mask_render_phases: ResMut<ViewBinnedRenderPhases<AlphaMask3d>>,
1168    mut transmissive_render_phases: ResMut<ViewSortedRenderPhases<Transmissive3d>>,
1169    mut transparent_render_phases: ResMut<ViewSortedRenderPhases<Transparent3d>>,
1170    views: Query<(&ExtractedView, &RenderVisibleEntities)>,
1171    specialized_material_pipeline_cache: ResMut<SpecializedMaterialPipelineCache>,
1172) {
1173    for (view, visible_entities) in &views {
1174        let (
1175            Some(opaque_phase),
1176            Some(alpha_mask_phase),
1177            Some(transmissive_phase),
1178            Some(transparent_phase),
1179        ) = (
1180            opaque_render_phases.get_mut(&view.retained_view_entity),
1181            alpha_mask_render_phases.get_mut(&view.retained_view_entity),
1182            transmissive_render_phases.get_mut(&view.retained_view_entity),
1183            transparent_render_phases.get_mut(&view.retained_view_entity),
1184        )
1185        else {
1186            continue;
1187        };
1188
1189        let Some(view_specialized_material_pipeline_cache) =
1190            specialized_material_pipeline_cache.get(&view.retained_view_entity)
1191        else {
1192            continue;
1193        };
1194
1195        let rangefinder = view.rangefinder3d();
1196        for (render_entity, visible_entity) in visible_entities.iter::<Mesh3d>() {
1197            let Some((current_change_tick, pipeline_id)) = view_specialized_material_pipeline_cache
1198                .get(visible_entity)
1199                .map(|(current_change_tick, pipeline_id)| (*current_change_tick, *pipeline_id))
1200            else {
1201                continue;
1202            };
1203
1204            // Skip the entity if it's cached in a bin and up to date.
1205            if opaque_phase.validate_cached_entity(*visible_entity, current_change_tick)
1206                || alpha_mask_phase.validate_cached_entity(*visible_entity, current_change_tick)
1207            {
1208                continue;
1209            }
1210
1211            let Some(material_instance) = render_material_instances.instances.get(visible_entity)
1212            else {
1213                continue;
1214            };
1215            let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity)
1216            else {
1217                continue;
1218            };
1219            let Some(material) = render_materials.get(material_instance.asset_id) else {
1220                continue;
1221            };
1222
1223            // Fetch the slabs that this mesh resides in.
1224            let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
1225            let Some(draw_function) = material.properties.get_draw_function(MaterialDrawFunction)
1226            else {
1227                continue;
1228            };
1229
1230            match material.properties.render_phase_type {
1231                RenderPhaseType::Transmissive => {
1232                    let distance = rangefinder.distance_translation(&mesh_instance.translation)
1233                        + material.properties.depth_bias;
1234                    transmissive_phase.add(Transmissive3d {
1235                        entity: (*render_entity, *visible_entity),
1236                        draw_function,
1237                        pipeline: pipeline_id,
1238                        distance,
1239                        batch_range: 0..1,
1240                        extra_index: PhaseItemExtraIndex::None,
1241                        indexed: index_slab.is_some(),
1242                    });
1243                }
1244                RenderPhaseType::Opaque => {
1245                    if material.properties.render_method == OpaqueRendererMethod::Deferred {
1246                        // Even though we aren't going to insert the entity into
1247                        // a bin, we still want to update its cache entry. That
1248                        // way, we know we don't need to re-examine it in future
1249                        // frames.
1250                        opaque_phase.update_cache(*visible_entity, None, current_change_tick);
1251                        continue;
1252                    }
1253                    let batch_set_key = Opaque3dBatchSetKey {
1254                        pipeline: pipeline_id,
1255                        draw_function,
1256                        material_bind_group_index: Some(material.binding.group.0),
1257                        vertex_slab: vertex_slab.unwrap_or_default(),
1258                        index_slab,
1259                        lightmap_slab: mesh_instance.shared.lightmap_slab_index.map(|index| *index),
1260                    };
1261                    let bin_key = Opaque3dBinKey {
1262                        asset_id: mesh_instance.mesh_asset_id.into(),
1263                    };
1264                    opaque_phase.add(
1265                        batch_set_key,
1266                        bin_key,
1267                        (*render_entity, *visible_entity),
1268                        mesh_instance.current_uniform_index,
1269                        BinnedRenderPhaseType::mesh(
1270                            mesh_instance.should_batch(),
1271                            &gpu_preprocessing_support,
1272                        ),
1273                        current_change_tick,
1274                    );
1275                }
1276                // Alpha mask
1277                RenderPhaseType::AlphaMask => {
1278                    let batch_set_key = OpaqueNoLightmap3dBatchSetKey {
1279                        draw_function,
1280                        pipeline: pipeline_id,
1281                        material_bind_group_index: Some(material.binding.group.0),
1282                        vertex_slab: vertex_slab.unwrap_or_default(),
1283                        index_slab,
1284                    };
1285                    let bin_key = OpaqueNoLightmap3dBinKey {
1286                        asset_id: mesh_instance.mesh_asset_id.into(),
1287                    };
1288                    alpha_mask_phase.add(
1289                        batch_set_key,
1290                        bin_key,
1291                        (*render_entity, *visible_entity),
1292                        mesh_instance.current_uniform_index,
1293                        BinnedRenderPhaseType::mesh(
1294                            mesh_instance.should_batch(),
1295                            &gpu_preprocessing_support,
1296                        ),
1297                        current_change_tick,
1298                    );
1299                }
1300                RenderPhaseType::Transparent => {
1301                    let distance = rangefinder.distance_translation(&mesh_instance.translation)
1302                        + material.properties.depth_bias;
1303                    transparent_phase.add(Transparent3d {
1304                        entity: (*render_entity, *visible_entity),
1305                        draw_function,
1306                        pipeline: pipeline_id,
1307                        distance,
1308                        batch_range: 0..1,
1309                        extra_index: PhaseItemExtraIndex::None,
1310                        indexed: index_slab.is_some(),
1311                    });
1312                }
1313            }
1314        }
1315    }
1316}
1317
1318/// Default render method used for opaque materials.
1319#[derive(Default, Resource, Clone, Debug, ExtractResource, Reflect)]
1320#[reflect(Resource, Default, Debug, Clone)]
1321pub struct DefaultOpaqueRendererMethod(OpaqueRendererMethod);
1322
1323impl DefaultOpaqueRendererMethod {
1324    pub fn forward() -> Self {
1325        DefaultOpaqueRendererMethod(OpaqueRendererMethod::Forward)
1326    }
1327
1328    pub fn deferred() -> Self {
1329        DefaultOpaqueRendererMethod(OpaqueRendererMethod::Deferred)
1330    }
1331
1332    pub fn set_to_forward(&mut self) {
1333        self.0 = OpaqueRendererMethod::Forward;
1334    }
1335
1336    pub fn set_to_deferred(&mut self) {
1337        self.0 = OpaqueRendererMethod::Deferred;
1338    }
1339}
1340
1341/// Render method used for opaque materials.
1342///
1343/// The forward rendering main pass draws each mesh entity and shades it according to its
1344/// corresponding material and the lights that affect it. Some render features like Screen Space
1345/// Ambient Occlusion require running depth and normal prepasses, that are 'deferred'-like
1346/// prepasses over all mesh entities to populate depth and normal textures. This means that when
1347/// using render features that require running prepasses, multiple passes over all visible geometry
1348/// are required. This can be slow if there is a lot of geometry that cannot be batched into few
1349/// draws.
1350///
1351/// Deferred rendering runs a prepass to gather not only geometric information like depth and
1352/// normals, but also all the material properties like base color, emissive color, reflectance,
1353/// metalness, etc, and writes them into a deferred 'g-buffer' texture. The deferred main pass is
1354/// then a fullscreen pass that reads data from these textures and executes shading. This allows
1355/// for one pass over geometry, but is at the cost of not being able to use MSAA, and has heavier
1356/// bandwidth usage which can be unsuitable for low end mobile or other bandwidth-constrained devices.
1357///
1358/// If a material indicates `OpaqueRendererMethod::Auto`, `DefaultOpaqueRendererMethod` will be used.
1359#[derive(Default, Clone, Copy, Debug, PartialEq, Reflect)]
1360#[reflect(Default, Clone, PartialEq)]
1361pub enum OpaqueRendererMethod {
1362    #[default]
1363    Forward,
1364    Deferred,
1365    Auto,
1366}
1367
1368#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1369pub struct MaterialVertexShader;
1370
1371#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1372pub struct MaterialFragmentShader;
1373
1374#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1375pub struct PrepassVertexShader;
1376
1377#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1378pub struct PrepassFragmentShader;
1379
1380#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1381pub struct DeferredVertexShader;
1382
1383#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1384pub struct DeferredFragmentShader;
1385
1386#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1387pub struct MeshletFragmentShader;
1388
1389#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1390pub struct MeshletPrepassFragmentShader;
1391
1392#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1393pub struct MeshletDeferredFragmentShader;
1394
1395#[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1396pub struct MaterialDrawFunction;
1397
1398#[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1399pub struct PrepassDrawFunction;
1400
1401#[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1402pub struct DeferredDrawFunction;
1403
1404#[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1405pub struct ShadowsDrawFunction;
1406
1407#[derive(Debug)]
1408pub struct ErasedMaterialKey {
1409    type_id: TypeId,
1410    hash: u64,
1411    value: Box<dyn Any + Send + Sync>,
1412    vtable: Arc<ErasedMaterialKeyVTable>,
1413}
1414
1415#[derive(Debug)]
1416pub struct ErasedMaterialKeyVTable {
1417    clone_fn: fn(&dyn Any) -> Box<dyn Any + Send + Sync>,
1418    partial_eq_fn: fn(&dyn Any, &dyn Any) -> bool,
1419}
1420
1421impl ErasedMaterialKey {
1422    pub fn new<T>(material_key: T) -> Self
1423    where
1424        T: Clone + Hash + PartialEq + Send + Sync + 'static,
1425    {
1426        let type_id = TypeId::of::<T>();
1427        let hash = FixedHasher::hash_one(&FixedHasher, &material_key);
1428
1429        fn clone<T: Clone + Send + Sync + 'static>(any: &dyn Any) -> Box<dyn Any + Send + Sync> {
1430            Box::new(any.downcast_ref::<T>().unwrap().clone())
1431        }
1432        fn partial_eq<T: PartialEq + 'static>(a: &dyn Any, b: &dyn Any) -> bool {
1433            a.downcast_ref::<T>().unwrap() == b.downcast_ref::<T>().unwrap()
1434        }
1435
1436        Self {
1437            type_id,
1438            hash,
1439            value: Box::new(material_key),
1440            vtable: Arc::new(ErasedMaterialKeyVTable {
1441                clone_fn: clone::<T>,
1442                partial_eq_fn: partial_eq::<T>,
1443            }),
1444        }
1445    }
1446
1447    pub fn to_key<T: Clone + 'static>(&self) -> T {
1448        debug_assert_eq!(self.type_id, TypeId::of::<T>());
1449        self.value.downcast_ref::<T>().unwrap().clone()
1450    }
1451}
1452
1453impl PartialEq for ErasedMaterialKey {
1454    fn eq(&self, other: &Self) -> bool {
1455        self.type_id == other.type_id
1456            && (self.vtable.partial_eq_fn)(self.value.as_ref(), other.value.as_ref())
1457    }
1458}
1459
1460impl Eq for ErasedMaterialKey {}
1461
1462impl Clone for ErasedMaterialKey {
1463    fn clone(&self) -> Self {
1464        Self {
1465            type_id: self.type_id,
1466            hash: self.hash,
1467            value: (self.vtable.clone_fn)(self.value.as_ref()),
1468            vtable: self.vtable.clone(),
1469        }
1470    }
1471}
1472
1473impl Hash for ErasedMaterialKey {
1474    fn hash<H: Hasher>(&self, state: &mut H) {
1475        self.type_id.hash(state);
1476        self.hash.hash(state);
1477    }
1478}
1479
1480impl Default for ErasedMaterialKey {
1481    fn default() -> Self {
1482        Self::new(())
1483    }
1484}
1485
1486/// Common [`Material`] properties, calculated for a specific material instance.
1487#[derive(Default)]
1488pub struct MaterialProperties {
1489    /// Is this material should be rendered by the deferred renderer when.
1490    /// [`AlphaMode::Opaque`] or [`AlphaMode::Mask`]
1491    pub render_method: OpaqueRendererMethod,
1492    /// The [`AlphaMode`] of this material.
1493    pub alpha_mode: AlphaMode,
1494    /// The bits in the [`MeshPipelineKey`] for this material.
1495    ///
1496    /// These are precalculated so that we can just "or" them together in
1497    /// [`queue_material_meshes`].
1498    pub mesh_pipeline_key_bits: MeshPipelineKey,
1499    /// Add a bias to the view depth of the mesh which can be used to force a specific render order
1500    /// for meshes with equal depth, to avoid z-fighting.
1501    /// The bias is in depth-texture units so large values may be needed to overcome small depth differences.
1502    pub depth_bias: f32,
1503    /// Whether the material would like to read from [`ViewTransmissionTexture`](bevy_core_pipeline::core_3d::ViewTransmissionTexture).
1504    ///
1505    /// This allows taking color output from the [`Opaque3d`] pass as an input, (for screen-space transmission) but requires
1506    /// rendering to take place in a separate [`Transmissive3d`] pass.
1507    pub reads_view_transmission_texture: bool,
1508    pub render_phase_type: RenderPhaseType,
1509    pub material_layout: Option<BindGroupLayout>,
1510    /// Backing array is a size of 4 because the `StandardMaterial` needs 4 draw functions by default
1511    pub draw_functions: SmallVec<[(InternedDrawFunctionLabel, DrawFunctionId); 4]>,
1512    /// Backing array is a size of 3 because the `StandardMaterial` has 3 custom shaders (`frag`, `prepass_frag`, `deferred_frag`) which is the
1513    /// most common use case
1514    pub shaders: SmallVec<[(InternedShaderLabel, Handle<Shader>); 3]>,
1515    /// Whether this material *actually* uses bindless resources, taking the
1516    /// platform support (or lack thereof) of bindless resources into account.
1517    pub bindless: bool,
1518    pub specialize: Option<
1519        fn(
1520            &MaterialPipeline,
1521            &mut RenderPipelineDescriptor,
1522            &MeshVertexBufferLayoutRef,
1523            ErasedMaterialPipelineKey,
1524        ) -> Result<(), SpecializedMeshPipelineError>,
1525    >,
1526    /// The key for this material, typically a bitfield of flags that are used to modify
1527    /// the pipeline descriptor used for this material.
1528    pub material_key: ErasedMaterialKey,
1529    /// Whether shadows are enabled for this material
1530    pub shadows_enabled: bool,
1531    /// Whether prepass is enabled for this material
1532    pub prepass_enabled: bool,
1533}
1534
1535impl MaterialProperties {
1536    pub fn get_shader(&self, label: impl ShaderLabel) -> Option<Handle<Shader>> {
1537        self.shaders
1538            .iter()
1539            .find(|(inner_label, _)| inner_label == &label.intern())
1540            .map(|(_, shader)| shader)
1541            .cloned()
1542    }
1543
1544    pub fn add_shader(&mut self, label: impl ShaderLabel, shader: Handle<Shader>) {
1545        self.shaders.push((label.intern(), shader));
1546    }
1547
1548    pub fn get_draw_function(&self, label: impl DrawFunctionLabel) -> Option<DrawFunctionId> {
1549        self.draw_functions
1550            .iter()
1551            .find(|(inner_label, _)| inner_label == &label.intern())
1552            .map(|(_, shader)| shader)
1553            .cloned()
1554    }
1555
1556    pub fn add_draw_function(
1557        &mut self,
1558        label: impl DrawFunctionLabel,
1559        draw_function: DrawFunctionId,
1560    ) {
1561        self.draw_functions.push((label.intern(), draw_function));
1562    }
1563}
1564
1565#[derive(Clone, Copy, Default)]
1566pub enum RenderPhaseType {
1567    #[default]
1568    Opaque,
1569    AlphaMask,
1570    Transmissive,
1571    Transparent,
1572}
1573
1574/// A resource that maps each untyped material ID to its binding.
1575///
1576/// This duplicates information in `RenderAssets<M>`, but it doesn't have the
1577/// `M` type parameter, so it can be used in untyped contexts like
1578/// [`crate::render::mesh::collect_meshes_for_gpu_building`].
1579#[derive(Resource, Default, Deref, DerefMut)]
1580pub struct RenderMaterialBindings(HashMap<UntypedAssetId, MaterialBindingId>);
1581
1582/// Data prepared for a [`Material`] instance.
1583pub struct PreparedMaterial {
1584    pub binding: MaterialBindingId,
1585    pub properties: Arc<MaterialProperties>,
1586}
1587
1588// orphan rules T_T
1589impl<M: Material> ErasedRenderAsset for MeshMaterial3d<M>
1590where
1591    M::Data: PartialEq + Eq + Hash + Clone,
1592{
1593    type SourceAsset = M;
1594    type ErasedAsset = PreparedMaterial;
1595
1596    type Param = (
1597        SRes<RenderDevice>,
1598        SRes<DefaultOpaqueRendererMethod>,
1599        SResMut<MaterialBindGroupAllocators>,
1600        SResMut<RenderMaterialBindings>,
1601        SRes<DrawFunctions<Opaque3d>>,
1602        SRes<DrawFunctions<AlphaMask3d>>,
1603        SRes<DrawFunctions<Transmissive3d>>,
1604        SRes<DrawFunctions<Transparent3d>>,
1605        SRes<DrawFunctions<Opaque3dPrepass>>,
1606        SRes<DrawFunctions<AlphaMask3dPrepass>>,
1607        SRes<DrawFunctions<Opaque3dDeferred>>,
1608        SRes<DrawFunctions<AlphaMask3dDeferred>>,
1609        SRes<DrawFunctions<Shadow>>,
1610        SRes<AssetServer>,
1611        (
1612            Option<SRes<ShadowsEnabled<M>>>,
1613            Option<SRes<PrepassEnabled<M>>>,
1614            M::Param,
1615        ),
1616    );
1617
1618    fn prepare_asset(
1619        material: Self::SourceAsset,
1620        material_id: AssetId<Self::SourceAsset>,
1621        (
1622            render_device,
1623            default_opaque_render_method,
1624            bind_group_allocators,
1625            render_material_bindings,
1626            opaque_draw_functions,
1627            alpha_mask_draw_functions,
1628            transmissive_draw_functions,
1629            transparent_draw_functions,
1630            opaque_prepass_draw_functions,
1631            alpha_mask_prepass_draw_functions,
1632            opaque_deferred_draw_functions,
1633            alpha_mask_deferred_draw_functions,
1634            shadow_draw_functions,
1635            asset_server,
1636            (shadows_enabled, prepass_enabled, material_param),
1637        ): &mut SystemParamItem<Self::Param>,
1638    ) -> Result<Self::ErasedAsset, PrepareAssetError<Self::SourceAsset>> {
1639        let material_layout = M::bind_group_layout(render_device);
1640
1641        let shadows_enabled = shadows_enabled.is_some();
1642        let prepass_enabled = prepass_enabled.is_some();
1643
1644        let draw_opaque_pbr = opaque_draw_functions.read().id::<DrawMaterial>();
1645        let draw_alpha_mask_pbr = alpha_mask_draw_functions.read().id::<DrawMaterial>();
1646        let draw_transmissive_pbr = transmissive_draw_functions.read().id::<DrawMaterial>();
1647        let draw_transparent_pbr = transparent_draw_functions.read().id::<DrawMaterial>();
1648        let draw_opaque_prepass = opaque_prepass_draw_functions.read().get_id::<DrawPrepass>();
1649        let draw_alpha_mask_prepass = alpha_mask_prepass_draw_functions
1650            .read()
1651            .get_id::<DrawPrepass>();
1652        let draw_opaque_deferred = opaque_deferred_draw_functions
1653            .read()
1654            .get_id::<DrawPrepass>();
1655        let draw_alpha_mask_deferred = alpha_mask_deferred_draw_functions
1656            .read()
1657            .get_id::<DrawPrepass>();
1658        let shadow_draw_function_id = shadow_draw_functions.read().get_id::<DrawPrepass>();
1659
1660        let render_method = match material.opaque_render_method() {
1661            OpaqueRendererMethod::Forward => OpaqueRendererMethod::Forward,
1662            OpaqueRendererMethod::Deferred => OpaqueRendererMethod::Deferred,
1663            OpaqueRendererMethod::Auto => default_opaque_render_method.0,
1664        };
1665
1666        let mut mesh_pipeline_key_bits = MeshPipelineKey::empty();
1667        mesh_pipeline_key_bits.set(
1668            MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE,
1669            material.reads_view_transmission_texture(),
1670        );
1671
1672        let reads_view_transmission_texture =
1673            mesh_pipeline_key_bits.contains(MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE);
1674
1675        let render_phase_type = match material.alpha_mode() {
1676            AlphaMode::Blend | AlphaMode::Premultiplied | AlphaMode::Add | AlphaMode::Multiply => {
1677                RenderPhaseType::Transparent
1678            }
1679            _ if reads_view_transmission_texture => RenderPhaseType::Transmissive,
1680            AlphaMode::Opaque | AlphaMode::AlphaToCoverage => RenderPhaseType::Opaque,
1681            AlphaMode::Mask(_) => RenderPhaseType::AlphaMask,
1682        };
1683
1684        let draw_function_id = match render_phase_type {
1685            RenderPhaseType::Opaque => draw_opaque_pbr,
1686            RenderPhaseType::AlphaMask => draw_alpha_mask_pbr,
1687            RenderPhaseType::Transmissive => draw_transmissive_pbr,
1688            RenderPhaseType::Transparent => draw_transparent_pbr,
1689        };
1690        let prepass_draw_function_id = match render_phase_type {
1691            RenderPhaseType::Opaque => draw_opaque_prepass,
1692            RenderPhaseType::AlphaMask => draw_alpha_mask_prepass,
1693            _ => None,
1694        };
1695        let deferred_draw_function_id = match render_phase_type {
1696            RenderPhaseType::Opaque => draw_opaque_deferred,
1697            RenderPhaseType::AlphaMask => draw_alpha_mask_deferred,
1698            _ => None,
1699        };
1700
1701        let mut draw_functions = SmallVec::new();
1702        draw_functions.push((MaterialDrawFunction.intern(), draw_function_id));
1703        if let Some(prepass_draw_function_id) = prepass_draw_function_id {
1704            draw_functions.push((PrepassDrawFunction.intern(), prepass_draw_function_id));
1705        }
1706        if let Some(deferred_draw_function_id) = deferred_draw_function_id {
1707            draw_functions.push((DeferredDrawFunction.intern(), deferred_draw_function_id));
1708        }
1709        if let Some(shadow_draw_function_id) = shadow_draw_function_id {
1710            draw_functions.push((ShadowsDrawFunction.intern(), shadow_draw_function_id));
1711        }
1712
1713        let mut shaders = SmallVec::new();
1714        let mut add_shader = |label: InternedShaderLabel, shader_ref: ShaderRef| {
1715            let mayber_shader = match shader_ref {
1716                ShaderRef::Default => None,
1717                ShaderRef::Handle(handle) => Some(handle),
1718                ShaderRef::Path(path) => Some(asset_server.load(path)),
1719            };
1720            if let Some(shader) = mayber_shader {
1721                shaders.push((label, shader));
1722            }
1723        };
1724        add_shader(MaterialVertexShader.intern(), M::vertex_shader());
1725        add_shader(MaterialFragmentShader.intern(), M::fragment_shader());
1726        add_shader(PrepassVertexShader.intern(), M::prepass_vertex_shader());
1727        add_shader(PrepassFragmentShader.intern(), M::prepass_fragment_shader());
1728        add_shader(DeferredVertexShader.intern(), M::deferred_vertex_shader());
1729        add_shader(
1730            DeferredFragmentShader.intern(),
1731            M::deferred_fragment_shader(),
1732        );
1733
1734        #[cfg(feature = "meshlet")]
1735        {
1736            add_shader(
1737                MeshletFragmentShader.intern(),
1738                M::meshlet_mesh_fragment_shader(),
1739            );
1740            add_shader(
1741                MeshletPrepassFragmentShader.intern(),
1742                M::meshlet_mesh_prepass_fragment_shader(),
1743            );
1744            add_shader(
1745                MeshletDeferredFragmentShader.intern(),
1746                M::meshlet_mesh_deferred_fragment_shader(),
1747            );
1748        }
1749
1750        let bindless = material_uses_bindless_resources::<M>(render_device);
1751        let bind_group_data = material.bind_group_data();
1752        let material_key = ErasedMaterialKey::new(bind_group_data);
1753        fn specialize<M: Material>(
1754            pipeline: &MaterialPipeline,
1755            descriptor: &mut RenderPipelineDescriptor,
1756            mesh_layout: &MeshVertexBufferLayoutRef,
1757            erased_key: ErasedMaterialPipelineKey,
1758        ) -> Result<(), SpecializedMeshPipelineError>
1759        where
1760            M::Data: Hash + Clone,
1761        {
1762            let material_key = erased_key.material_key.to_key();
1763            M::specialize(
1764                pipeline,
1765                descriptor,
1766                mesh_layout,
1767                MaterialPipelineKey {
1768                    mesh_key: erased_key.mesh_key,
1769                    bind_group_data: material_key,
1770                },
1771            )
1772        }
1773
1774        match material.unprepared_bind_group(&material_layout, render_device, material_param, false)
1775        {
1776            Ok(unprepared) => {
1777                let bind_group_allocator =
1778                    bind_group_allocators.get_mut(&TypeId::of::<M>()).unwrap();
1779                // Allocate or update the material.
1780                let binding = match render_material_bindings.entry(material_id.into()) {
1781                    Entry::Occupied(mut occupied_entry) => {
1782                        // TODO: Have a fast path that doesn't require
1783                        // recreating the bind group if only buffer contents
1784                        // change. For now, we just delete and recreate the bind
1785                        // group.
1786                        bind_group_allocator.free(*occupied_entry.get());
1787                        let new_binding =
1788                            bind_group_allocator.allocate_unprepared(unprepared, &material_layout);
1789                        *occupied_entry.get_mut() = new_binding;
1790                        new_binding
1791                    }
1792                    Entry::Vacant(vacant_entry) => *vacant_entry.insert(
1793                        bind_group_allocator.allocate_unprepared(unprepared, &material_layout),
1794                    ),
1795                };
1796
1797                Ok(PreparedMaterial {
1798                    binding,
1799                    properties: Arc::new(MaterialProperties {
1800                        alpha_mode: material.alpha_mode(),
1801                        depth_bias: material.depth_bias(),
1802                        reads_view_transmission_texture,
1803                        render_phase_type,
1804                        render_method,
1805                        mesh_pipeline_key_bits,
1806                        material_layout: Some(material_layout),
1807                        draw_functions,
1808                        shaders,
1809                        bindless,
1810                        specialize: Some(specialize::<M>),
1811                        material_key,
1812                        shadows_enabled,
1813                        prepass_enabled,
1814                    }),
1815                })
1816            }
1817
1818            Err(AsBindGroupError::RetryNextUpdate) => {
1819                Err(PrepareAssetError::RetryNextUpdate(material))
1820            }
1821
1822            Err(AsBindGroupError::CreateBindGroupDirectly) => {
1823                // This material has opted out of automatic bind group creation
1824                // and is requesting a fully-custom bind group. Invoke
1825                // `as_bind_group` as requested, and store the resulting bind
1826                // group in the slot.
1827                match material.as_bind_group(&material_layout, render_device, material_param) {
1828                    Ok(prepared_bind_group) => {
1829                        let bind_group_allocator =
1830                            bind_group_allocators.get_mut(&TypeId::of::<M>()).unwrap();
1831                        // Store the resulting bind group directly in the slot.
1832                        let material_binding_id =
1833                            bind_group_allocator.allocate_prepared(prepared_bind_group);
1834                        render_material_bindings.insert(material_id.into(), material_binding_id);
1835
1836                        Ok(PreparedMaterial {
1837                            binding: material_binding_id,
1838                            properties: Arc::new(MaterialProperties {
1839                                alpha_mode: material.alpha_mode(),
1840                                depth_bias: material.depth_bias(),
1841                                reads_view_transmission_texture,
1842                                render_phase_type,
1843                                render_method,
1844                                mesh_pipeline_key_bits,
1845                                material_layout: Some(material_layout),
1846                                draw_functions,
1847                                shaders,
1848                                bindless,
1849                                specialize: Some(specialize::<M>),
1850                                material_key,
1851                                shadows_enabled,
1852                                prepass_enabled,
1853                            }),
1854                        })
1855                    }
1856
1857                    Err(AsBindGroupError::RetryNextUpdate) => {
1858                        Err(PrepareAssetError::RetryNextUpdate(material))
1859                    }
1860
1861                    Err(other) => Err(PrepareAssetError::AsBindGroupError(other)),
1862                }
1863            }
1864
1865            Err(other) => Err(PrepareAssetError::AsBindGroupError(other)),
1866        }
1867    }
1868
1869    fn unload_asset(
1870        source_asset: AssetId<Self::SourceAsset>,
1871        (_, _, bind_group_allocators, render_material_bindings, ..): &mut SystemParamItem<
1872            Self::Param,
1873        >,
1874    ) {
1875        let Some(material_binding_id) = render_material_bindings.remove(&source_asset.untyped())
1876        else {
1877            return;
1878        };
1879        let bind_group_allactor = bind_group_allocators.get_mut(&TypeId::of::<M>()).unwrap();
1880        bind_group_allactor.free(material_binding_id);
1881    }
1882}
1883
1884/// Creates and/or recreates any bind groups that contain materials that were
1885/// modified this frame.
1886pub fn prepare_material_bind_groups(
1887    mut allocators: ResMut<MaterialBindGroupAllocators>,
1888    render_device: Res<RenderDevice>,
1889    fallback_image: Res<FallbackImage>,
1890    fallback_resources: Res<FallbackBindlessResources>,
1891) {
1892    for (_, allocator) in allocators.iter_mut() {
1893        allocator.prepare_bind_groups(&render_device, &fallback_resources, &fallback_image);
1894    }
1895}
1896
1897/// Uploads the contents of all buffers that the [`MaterialBindGroupAllocator`]
1898/// manages to the GPU.
1899///
1900/// Non-bindless allocators don't currently manage any buffers, so this method
1901/// only has an effect for bindless allocators.
1902pub fn write_material_bind_group_buffers(
1903    mut allocators: ResMut<MaterialBindGroupAllocators>,
1904    render_device: Res<RenderDevice>,
1905    render_queue: Res<RenderQueue>,
1906) {
1907    for (_, allocator) in allocators.iter_mut() {
1908        allocator.write_buffers(&render_device, &render_queue);
1909    }
1910}
1911
1912/// Marker resource for whether shadows are enabled for this material type
1913#[derive(Resource, Debug)]
1914pub struct ShadowsEnabled<M: Material>(PhantomData<M>);
1915
1916impl<M: Material> Default for ShadowsEnabled<M> {
1917    fn default() -> Self {
1918        Self(PhantomData)
1919    }
1920}