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                        extract_entities_needs_specialization::<M>
395                            .after(extract_cameras)
396                            .after(MaterialExtractionSystems),
397                    ),
398                );
399        }
400    }
401}
402
403fn add_material_bind_group_allocator<M: Material>(
404    render_device: Res<RenderDevice>,
405    mut bind_group_allocators: ResMut<MaterialBindGroupAllocators>,
406) {
407    bind_group_allocators.insert(
408        TypeId::of::<M>(),
409        MaterialBindGroupAllocator::new(
410            &render_device,
411            M::label(),
412            material_uses_bindless_resources::<M>(&render_device)
413                .then(|| M::bindless_descriptor())
414                .flatten(),
415            M::bind_group_layout(&render_device),
416            M::bindless_slot_count(),
417        ),
418    );
419}
420
421/// A dummy [`AssetId`] that we use as a placeholder whenever a mesh doesn't
422/// have a material.
423///
424/// See the comments in [`RenderMaterialInstances::mesh_material`] for more
425/// information.
426pub(crate) static DUMMY_MESH_MATERIAL: AssetId<StandardMaterial> =
427    AssetId::<StandardMaterial>::invalid();
428
429/// A key uniquely identifying a specialized [`MaterialPipeline`].
430pub struct MaterialPipelineKey<M: Material> {
431    pub mesh_key: MeshPipelineKey,
432    pub bind_group_data: M::Data,
433}
434
435#[derive(Clone, Debug, PartialEq, Eq, Hash)]
436pub struct ErasedMaterialPipelineKey {
437    pub mesh_key: MeshPipelineKey,
438    pub material_key: ErasedMaterialKey,
439    pub type_id: TypeId,
440}
441
442/// Render pipeline data for a given [`Material`].
443#[derive(Resource, Clone)]
444pub struct MaterialPipeline {
445    pub mesh_pipeline: MeshPipeline,
446}
447
448pub struct MaterialPipelineSpecializer {
449    pub(crate) pipeline: MaterialPipeline,
450    pub(crate) properties: Arc<MaterialProperties>,
451}
452
453impl SpecializedMeshPipeline for MaterialPipelineSpecializer {
454    type Key = ErasedMaterialPipelineKey;
455
456    fn specialize(
457        &self,
458        key: Self::Key,
459        layout: &MeshVertexBufferLayoutRef,
460    ) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
461        let mut descriptor = self
462            .pipeline
463            .mesh_pipeline
464            .specialize(key.mesh_key, layout)?;
465        descriptor.vertex.shader_defs.push(ShaderDefVal::UInt(
466            "MATERIAL_BIND_GROUP".into(),
467            MATERIAL_BIND_GROUP_INDEX as u32,
468        ));
469        if let Some(ref mut fragment) = descriptor.fragment {
470            fragment.shader_defs.push(ShaderDefVal::UInt(
471                "MATERIAL_BIND_GROUP".into(),
472                MATERIAL_BIND_GROUP_INDEX as u32,
473            ));
474        };
475        if let Some(vertex_shader) = self.properties.get_shader(MaterialVertexShader) {
476            descriptor.vertex.shader = vertex_shader.clone();
477        }
478
479        if let Some(fragment_shader) = self.properties.get_shader(MaterialFragmentShader) {
480            descriptor.fragment.as_mut().unwrap().shader = fragment_shader.clone();
481        }
482
483        descriptor
484            .layout
485            .insert(3, self.properties.material_layout.as_ref().unwrap().clone());
486
487        if let Some(specialize) = self.properties.specialize {
488            specialize(&self.pipeline, &mut descriptor, layout, key)?;
489        }
490
491        // If bindless mode is on, add a `BINDLESS` define.
492        if self.properties.bindless {
493            descriptor.vertex.shader_defs.push("BINDLESS".into());
494            if let Some(ref mut fragment) = descriptor.fragment {
495                fragment.shader_defs.push("BINDLESS".into());
496            }
497        }
498
499        Ok(descriptor)
500    }
501}
502
503pub fn init_material_pipeline(mut commands: Commands, mesh_pipeline: Res<MeshPipeline>) {
504    commands.insert_resource(MaterialPipeline {
505        mesh_pipeline: mesh_pipeline.clone(),
506    });
507}
508
509pub type DrawMaterial = (
510    SetItemPipeline,
511    SetMeshViewBindGroup<0>,
512    SetMeshViewBindingArrayBindGroup<1>,
513    SetMeshBindGroup<2>,
514    SetMaterialBindGroup<MATERIAL_BIND_GROUP_INDEX>,
515    DrawMesh,
516);
517
518/// Sets the bind group for a given [`Material`] at the configured `I` index.
519pub struct SetMaterialBindGroup<const I: usize>;
520impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetMaterialBindGroup<I> {
521    type Param = (
522        SRes<ErasedRenderAssets<PreparedMaterial>>,
523        SRes<RenderMaterialInstances>,
524        SRes<MaterialBindGroupAllocators>,
525    );
526    type ViewQuery = ();
527    type ItemQuery = ();
528
529    #[inline]
530    fn render<'w>(
531        item: &P,
532        _view: (),
533        _item_query: Option<()>,
534        (materials, material_instances, material_bind_group_allocator): SystemParamItem<
535            'w,
536            '_,
537            Self::Param,
538        >,
539        pass: &mut TrackedRenderPass<'w>,
540    ) -> RenderCommandResult {
541        let materials = materials.into_inner();
542        let material_instances = material_instances.into_inner();
543        let material_bind_group_allocators = material_bind_group_allocator.into_inner();
544
545        let Some(material_instance) = material_instances.instances.get(&item.main_entity()) else {
546            return RenderCommandResult::Skip;
547        };
548        let Some(material_bind_group_allocator) =
549            material_bind_group_allocators.get(&material_instance.asset_id.type_id())
550        else {
551            return RenderCommandResult::Skip;
552        };
553        let Some(material) = materials.get(material_instance.asset_id) else {
554            return RenderCommandResult::Skip;
555        };
556        let Some(material_bind_group) = material_bind_group_allocator.get(material.binding.group)
557        else {
558            return RenderCommandResult::Skip;
559        };
560        let Some(bind_group) = material_bind_group.bind_group() else {
561            return RenderCommandResult::Skip;
562        };
563        pass.set_bind_group(I, bind_group, &[]);
564        RenderCommandResult::Success
565    }
566}
567
568/// Stores all extracted instances of all [`Material`]s in the render world.
569#[derive(Resource, Default)]
570pub struct RenderMaterialInstances {
571    /// Maps from each entity in the main world to the
572    /// [`RenderMaterialInstance`] associated with it.
573    pub instances: MainEntityHashMap<RenderMaterialInstance>,
574    /// A monotonically-increasing counter, which we use to sweep
575    /// [`RenderMaterialInstances::instances`] when the entities and/or required
576    /// components are removed.
577    pub current_change_tick: Tick,
578}
579
580impl RenderMaterialInstances {
581    /// Returns the mesh material ID for the entity with the given mesh, or a
582    /// dummy mesh material ID if the mesh has no material ID.
583    ///
584    /// Meshes almost always have materials, but in very specific circumstances
585    /// involving custom pipelines they won't. (See the
586    /// `specialized_mesh_pipelines` example.)
587    pub(crate) fn mesh_material(&self, entity: MainEntity) -> UntypedAssetId {
588        match self.instances.get(&entity) {
589            Some(render_instance) => render_instance.asset_id,
590            None => DUMMY_MESH_MATERIAL.into(),
591        }
592    }
593}
594
595/// The material associated with a single mesh instance in the main world.
596///
597/// Note that this uses an [`UntypedAssetId`] and isn't generic over the
598/// material type, for simplicity.
599pub struct RenderMaterialInstance {
600    /// The material asset.
601    pub asset_id: UntypedAssetId,
602    /// The [`RenderMaterialInstances::current_change_tick`] at which this
603    /// material instance was last modified.
604    pub last_change_tick: Tick,
605}
606
607/// A [`SystemSet`] that contains all `extract_mesh_materials` systems.
608#[derive(SystemSet, Clone, PartialEq, Eq, Debug, Hash)]
609pub struct MaterialExtractionSystems;
610
611/// Deprecated alias for [`MaterialExtractionSystems`].
612#[deprecated(since = "0.17.0", note = "Renamed to `MaterialExtractionSystems`.")]
613pub type ExtractMaterialsSet = MaterialExtractionSystems;
614
615pub const fn alpha_mode_pipeline_key(alpha_mode: AlphaMode, msaa: &Msaa) -> MeshPipelineKey {
616    match alpha_mode {
617        // Premultiplied and Add share the same pipeline key
618        // They're made distinct in the PBR shader, via `premultiply_alpha()`
619        AlphaMode::Premultiplied | AlphaMode::Add => MeshPipelineKey::BLEND_PREMULTIPLIED_ALPHA,
620        AlphaMode::Blend => MeshPipelineKey::BLEND_ALPHA,
621        AlphaMode::Multiply => MeshPipelineKey::BLEND_MULTIPLY,
622        AlphaMode::Mask(_) => MeshPipelineKey::MAY_DISCARD,
623        AlphaMode::AlphaToCoverage => match *msaa {
624            Msaa::Off => MeshPipelineKey::MAY_DISCARD,
625            _ => MeshPipelineKey::BLEND_ALPHA_TO_COVERAGE,
626        },
627        _ => MeshPipelineKey::NONE,
628    }
629}
630
631pub const fn tonemapping_pipeline_key(tonemapping: Tonemapping) -> MeshPipelineKey {
632    match tonemapping {
633        Tonemapping::None => MeshPipelineKey::TONEMAP_METHOD_NONE,
634        Tonemapping::Reinhard => MeshPipelineKey::TONEMAP_METHOD_REINHARD,
635        Tonemapping::ReinhardLuminance => MeshPipelineKey::TONEMAP_METHOD_REINHARD_LUMINANCE,
636        Tonemapping::AcesFitted => MeshPipelineKey::TONEMAP_METHOD_ACES_FITTED,
637        Tonemapping::AgX => MeshPipelineKey::TONEMAP_METHOD_AGX,
638        Tonemapping::SomewhatBoringDisplayTransform => {
639            MeshPipelineKey::TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM
640        }
641        Tonemapping::TonyMcMapface => MeshPipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE,
642        Tonemapping::BlenderFilmic => MeshPipelineKey::TONEMAP_METHOD_BLENDER_FILMIC,
643    }
644}
645
646pub const fn screen_space_specular_transmission_pipeline_key(
647    screen_space_transmissive_blur_quality: ScreenSpaceTransmissionQuality,
648) -> MeshPipelineKey {
649    match screen_space_transmissive_blur_quality {
650        ScreenSpaceTransmissionQuality::Low => {
651            MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_LOW
652        }
653        ScreenSpaceTransmissionQuality::Medium => {
654            MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_MEDIUM
655        }
656        ScreenSpaceTransmissionQuality::High => {
657            MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_HIGH
658        }
659        ScreenSpaceTransmissionQuality::Ultra => {
660            MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_ULTRA
661        }
662    }
663}
664
665/// A system that ensures that
666/// [`crate::render::mesh::extract_meshes_for_gpu_building`] re-extracts meshes
667/// whose materials changed.
668///
669/// As [`crate::render::mesh::collect_meshes_for_gpu_building`] only considers
670/// meshes that were newly extracted, and it writes information from the
671/// [`RenderMaterialInstances`] into the
672/// [`crate::render::mesh::MeshInputUniform`], we must tell
673/// [`crate::render::mesh::extract_meshes_for_gpu_building`] to re-extract a
674/// mesh if its material changed. Otherwise, the material binding information in
675/// the [`crate::render::mesh::MeshInputUniform`] might not be updated properly.
676/// The easiest way to ensure that
677/// [`crate::render::mesh::extract_meshes_for_gpu_building`] re-extracts a mesh
678/// is to mark its [`Mesh3d`] as changed, so that's what this system does.
679fn mark_meshes_as_changed_if_their_materials_changed<M>(
680    mut changed_meshes_query: Query<
681        &mut Mesh3d,
682        Or<(Changed<MeshMaterial3d<M>>, AssetChanged<MeshMaterial3d<M>>)>,
683    >,
684) where
685    M: Material,
686{
687    for mut mesh in &mut changed_meshes_query {
688        mesh.set_changed();
689    }
690}
691
692/// Fills the [`RenderMaterialInstances`] resources from the meshes in the
693/// scene.
694fn extract_mesh_materials<M: Material>(
695    mut material_instances: ResMut<RenderMaterialInstances>,
696    changed_meshes_query: Extract<
697        Query<
698            (Entity, &ViewVisibility, &MeshMaterial3d<M>),
699            Or<(Changed<ViewVisibility>, Changed<MeshMaterial3d<M>>)>,
700        >,
701    >,
702) {
703    let last_change_tick = material_instances.current_change_tick;
704
705    for (entity, view_visibility, material) in &changed_meshes_query {
706        if view_visibility.get() {
707            material_instances.instances.insert(
708                entity.into(),
709                RenderMaterialInstance {
710                    asset_id: material.id().untyped(),
711                    last_change_tick,
712                },
713            );
714        } else {
715            material_instances
716                .instances
717                .remove(&MainEntity::from(entity));
718        }
719    }
720}
721
722/// Removes mesh materials from [`RenderMaterialInstances`] when their
723/// [`MeshMaterial3d`] components are removed.
724///
725/// This is tricky because we have to deal with the case in which a material of
726/// type A was removed and replaced with a material of type B in the same frame
727/// (which is actually somewhat common of an operation). In this case, even
728/// though an entry will be present in `RemovedComponents<MeshMaterial3d<A>>`,
729/// we must not remove the entry in `RenderMaterialInstances` which corresponds
730/// to material B. To handle this case, we use change ticks to avoid removing
731/// the entry if it was updated this frame.
732///
733/// This is the first of two sweep phases. Because this phase runs once per
734/// material type, we need a second phase in order to guarantee that we only
735/// bump [`RenderMaterialInstances::current_change_tick`] once.
736fn early_sweep_material_instances<M>(
737    mut material_instances: ResMut<RenderMaterialInstances>,
738    mut removed_materials_query: Extract<RemovedComponents<MeshMaterial3d<M>>>,
739) where
740    M: Material,
741{
742    let last_change_tick = material_instances.current_change_tick;
743
744    for entity in removed_materials_query.read() {
745        if let Entry::Occupied(occupied_entry) = material_instances.instances.entry(entity.into()) {
746            // Only sweep the entry if it wasn't updated this frame.
747            if occupied_entry.get().last_change_tick != last_change_tick {
748                occupied_entry.remove();
749            }
750        }
751    }
752}
753
754/// Removes mesh materials from [`RenderMaterialInstances`] when their
755/// [`ViewVisibility`] components are removed.
756///
757/// This runs after all invocations of [`early_sweep_material_instances`] and is
758/// responsible for bumping [`RenderMaterialInstances::current_change_tick`] in
759/// preparation for a new frame.
760pub(crate) fn late_sweep_material_instances(
761    mut material_instances: ResMut<RenderMaterialInstances>,
762    mut removed_meshes_query: Extract<RemovedComponents<Mesh3d>>,
763) {
764    let last_change_tick = material_instances.current_change_tick;
765
766    for entity in removed_meshes_query.read() {
767        if let Entry::Occupied(occupied_entry) = material_instances.instances.entry(entity.into()) {
768            // Only sweep the entry if it wasn't updated this frame. It's
769            // possible that a `ViewVisibility` component was removed and
770            // re-added in the same frame.
771            if occupied_entry.get().last_change_tick != last_change_tick {
772                occupied_entry.remove();
773            }
774        }
775    }
776
777    material_instances
778        .current_change_tick
779        .set(last_change_tick.get() + 1);
780}
781
782pub fn extract_entities_needs_specialization<M>(
783    entities_needing_specialization: Extract<Res<EntitiesNeedingSpecialization<M>>>,
784    material_instances: Res<RenderMaterialInstances>,
785    mut entity_specialization_ticks: ResMut<EntitySpecializationTicks>,
786    mut removed_mesh_material_components: Extract<RemovedComponents<MeshMaterial3d<M>>>,
787    mut specialized_material_pipeline_cache: ResMut<SpecializedMaterialPipelineCache>,
788    mut specialized_prepass_material_pipeline_cache: Option<
789        ResMut<SpecializedPrepassMaterialPipelineCache>,
790    >,
791    mut specialized_shadow_material_pipeline_cache: Option<
792        ResMut<SpecializedShadowMaterialPipelineCache>,
793    >,
794    views: Query<&ExtractedView>,
795    ticks: SystemChangeTick,
796) where
797    M: Material,
798{
799    // Clean up any despawned entities, we do this first in case the removed material was re-added
800    // the same frame, thus will appear both in the removed components list and have been added to
801    // the `EntitiesNeedingSpecialization` collection by triggering the `Changed` filter
802    //
803    // Additionally, we need to make sure that we are careful about materials that could have changed
804    // type, e.g. from a `StandardMaterial` to a `CustomMaterial`, as this will also appear in the
805    // removed components list. As such, we make sure that this system runs after `MaterialExtractionSystems`
806    // so that the `RenderMaterialInstances` bookkeeping has already been done, and we can check if the entity
807    // still has a valid material instance.
808    for entity in removed_mesh_material_components.read() {
809        if material_instances
810            .instances
811            .contains_key(&MainEntity::from(entity))
812        {
813            continue;
814        }
815
816        entity_specialization_ticks.remove(&MainEntity::from(entity));
817        for view in views {
818            if let Some(cache) =
819                specialized_material_pipeline_cache.get_mut(&view.retained_view_entity)
820            {
821                cache.remove(&MainEntity::from(entity));
822            }
823            if let Some(cache) = specialized_prepass_material_pipeline_cache
824                .as_mut()
825                .and_then(|c| c.get_mut(&view.retained_view_entity))
826            {
827                cache.remove(&MainEntity::from(entity));
828            }
829            if let Some(cache) = specialized_shadow_material_pipeline_cache
830                .as_mut()
831                .and_then(|c| c.get_mut(&view.retained_view_entity))
832            {
833                cache.remove(&MainEntity::from(entity));
834            }
835        }
836    }
837
838    for entity in entities_needing_specialization.iter() {
839        // Update the entity's specialization tick with this run's tick
840        entity_specialization_ticks.insert((*entity).into(), ticks.this_run());
841    }
842}
843
844#[derive(Resource, Deref, DerefMut, Clone, Debug)]
845pub struct EntitiesNeedingSpecialization<M> {
846    #[deref]
847    pub entities: Vec<Entity>,
848    _marker: PhantomData<M>,
849}
850
851impl<M> Default for EntitiesNeedingSpecialization<M> {
852    fn default() -> Self {
853        Self {
854            entities: Default::default(),
855            _marker: Default::default(),
856        }
857    }
858}
859
860#[derive(Resource, Deref, DerefMut, Default, Clone, Debug)]
861pub struct EntitySpecializationTicks {
862    #[deref]
863    pub entities: MainEntityHashMap<Tick>,
864}
865
866/// Stores the [`SpecializedMaterialViewPipelineCache`] for each view.
867#[derive(Resource, Deref, DerefMut, Default)]
868pub struct SpecializedMaterialPipelineCache {
869    // view entity -> view pipeline cache
870    #[deref]
871    map: HashMap<RetainedViewEntity, SpecializedMaterialViewPipelineCache>,
872}
873
874/// Stores the cached render pipeline ID for each entity in a single view, as
875/// well as the last time it was changed.
876#[derive(Deref, DerefMut, Default)]
877pub struct SpecializedMaterialViewPipelineCache {
878    // material entity -> (tick, pipeline_id)
879    #[deref]
880    map: MainEntityHashMap<(Tick, CachedRenderPipelineId)>,
881}
882
883pub fn check_entities_needing_specialization<M>(
884    needs_specialization: Query<
885        Entity,
886        (
887            Or<(
888                Changed<Mesh3d>,
889                AssetChanged<Mesh3d>,
890                Changed<MeshMaterial3d<M>>,
891                AssetChanged<MeshMaterial3d<M>>,
892            )>,
893            With<MeshMaterial3d<M>>,
894        ),
895    >,
896    mut par_local: Local<Parallel<Vec<Entity>>>,
897    mut entities_needing_specialization: ResMut<EntitiesNeedingSpecialization<M>>,
898) where
899    M: Material,
900{
901    entities_needing_specialization.clear();
902
903    needs_specialization
904        .par_iter()
905        .for_each(|entity| par_local.borrow_local_mut().push(entity));
906
907    par_local.drain_into(&mut entities_needing_specialization);
908}
909
910pub fn specialize_material_meshes(
911    render_meshes: Res<RenderAssets<RenderMesh>>,
912    render_materials: Res<ErasedRenderAssets<PreparedMaterial>>,
913    render_mesh_instances: Res<RenderMeshInstances>,
914    render_material_instances: Res<RenderMaterialInstances>,
915    render_lightmaps: Res<RenderLightmaps>,
916    render_visibility_ranges: Res<RenderVisibilityRanges>,
917    (
918        opaque_render_phases,
919        alpha_mask_render_phases,
920        transmissive_render_phases,
921        transparent_render_phases,
922    ): (
923        Res<ViewBinnedRenderPhases<Opaque3d>>,
924        Res<ViewBinnedRenderPhases<AlphaMask3d>>,
925        Res<ViewSortedRenderPhases<Transmissive3d>>,
926        Res<ViewSortedRenderPhases<Transparent3d>>,
927    ),
928    views: Query<(&ExtractedView, &RenderVisibleEntities)>,
929    view_key_cache: Res<ViewKeyCache>,
930    entity_specialization_ticks: Res<EntitySpecializationTicks>,
931    view_specialization_ticks: Res<ViewSpecializationTicks>,
932    mut specialized_material_pipeline_cache: ResMut<SpecializedMaterialPipelineCache>,
933    mut pipelines: ResMut<SpecializedMeshPipelines<MaterialPipelineSpecializer>>,
934    pipeline: Res<MaterialPipeline>,
935    pipeline_cache: Res<PipelineCache>,
936    ticks: SystemChangeTick,
937) {
938    // Record the retained IDs of all shadow views so that we can expire old
939    // pipeline IDs.
940    let mut all_views: HashSet<RetainedViewEntity, FixedHasher> = HashSet::default();
941
942    for (view, visible_entities) in &views {
943        all_views.insert(view.retained_view_entity);
944
945        if !transparent_render_phases.contains_key(&view.retained_view_entity)
946            && !opaque_render_phases.contains_key(&view.retained_view_entity)
947            && !alpha_mask_render_phases.contains_key(&view.retained_view_entity)
948            && !transmissive_render_phases.contains_key(&view.retained_view_entity)
949        {
950            continue;
951        }
952
953        let Some(view_key) = view_key_cache.get(&view.retained_view_entity) else {
954            continue;
955        };
956
957        let view_tick = view_specialization_ticks
958            .get(&view.retained_view_entity)
959            .unwrap();
960        let view_specialized_material_pipeline_cache = specialized_material_pipeline_cache
961            .entry(view.retained_view_entity)
962            .or_default();
963
964        for (_, visible_entity) in visible_entities.iter::<Mesh3d>() {
965            let Some(material_instance) = render_material_instances.instances.get(visible_entity)
966            else {
967                continue;
968            };
969            let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity)
970            else {
971                continue;
972            };
973            let entity_tick = entity_specialization_ticks.get(visible_entity).unwrap();
974            let last_specialized_tick = view_specialized_material_pipeline_cache
975                .get(visible_entity)
976                .map(|(tick, _)| *tick);
977            let needs_specialization = last_specialized_tick.is_none_or(|tick| {
978                view_tick.is_newer_than(tick, ticks.this_run())
979                    || entity_tick.is_newer_than(tick, ticks.this_run())
980            });
981            if !needs_specialization {
982                continue;
983            }
984            let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else {
985                continue;
986            };
987            let Some(material) = render_materials.get(material_instance.asset_id) else {
988                continue;
989            };
990
991            let mut mesh_pipeline_key_bits = material.properties.mesh_pipeline_key_bits;
992            mesh_pipeline_key_bits.insert(alpha_mode_pipeline_key(
993                material.properties.alpha_mode,
994                &Msaa::from_samples(view_key.msaa_samples()),
995            ));
996            let mut mesh_key = *view_key
997                | MeshPipelineKey::from_bits_retain(mesh.key_bits.bits())
998                | mesh_pipeline_key_bits;
999
1000            if let Some(lightmap) = render_lightmaps.render_lightmaps.get(visible_entity) {
1001                mesh_key |= MeshPipelineKey::LIGHTMAPPED;
1002
1003                if lightmap.bicubic_sampling {
1004                    mesh_key |= MeshPipelineKey::LIGHTMAP_BICUBIC_SAMPLING;
1005                }
1006            }
1007
1008            if render_visibility_ranges.entity_has_crossfading_visibility_ranges(*visible_entity) {
1009                mesh_key |= MeshPipelineKey::VISIBILITY_RANGE_DITHER;
1010            }
1011
1012            if view_key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) {
1013                // If the previous frame have skins or morph targets, note that.
1014                if mesh_instance
1015                    .flags
1016                    .contains(RenderMeshInstanceFlags::HAS_PREVIOUS_SKIN)
1017                {
1018                    mesh_key |= MeshPipelineKey::HAS_PREVIOUS_SKIN;
1019                }
1020                if mesh_instance
1021                    .flags
1022                    .contains(RenderMeshInstanceFlags::HAS_PREVIOUS_MORPH)
1023                {
1024                    mesh_key |= MeshPipelineKey::HAS_PREVIOUS_MORPH;
1025                }
1026            }
1027
1028            let erased_key = ErasedMaterialPipelineKey {
1029                type_id: material_instance.asset_id.type_id(),
1030                mesh_key,
1031                material_key: material.properties.material_key.clone(),
1032            };
1033            let material_pipeline_specializer = MaterialPipelineSpecializer {
1034                pipeline: pipeline.clone(),
1035                properties: material.properties.clone(),
1036            };
1037            let pipeline_id = pipelines.specialize(
1038                &pipeline_cache,
1039                &material_pipeline_specializer,
1040                erased_key,
1041                &mesh.layout,
1042            );
1043            let pipeline_id = match pipeline_id {
1044                Ok(id) => id,
1045                Err(err) => {
1046                    error!("{}", err);
1047                    continue;
1048                }
1049            };
1050
1051            view_specialized_material_pipeline_cache
1052                .insert(*visible_entity, (ticks.this_run(), pipeline_id));
1053        }
1054    }
1055
1056    // Delete specialized pipelines belonging to views that have expired.
1057    specialized_material_pipeline_cache
1058        .retain(|retained_view_entity, _| all_views.contains(retained_view_entity));
1059}
1060
1061/// For each view, iterates over all the meshes visible from that view and adds
1062/// them to [`BinnedRenderPhase`]s or [`SortedRenderPhase`]s as appropriate.
1063pub fn queue_material_meshes(
1064    render_materials: Res<ErasedRenderAssets<PreparedMaterial>>,
1065    render_mesh_instances: Res<RenderMeshInstances>,
1066    render_material_instances: Res<RenderMaterialInstances>,
1067    mesh_allocator: Res<MeshAllocator>,
1068    gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
1069    mut opaque_render_phases: ResMut<ViewBinnedRenderPhases<Opaque3d>>,
1070    mut alpha_mask_render_phases: ResMut<ViewBinnedRenderPhases<AlphaMask3d>>,
1071    mut transmissive_render_phases: ResMut<ViewSortedRenderPhases<Transmissive3d>>,
1072    mut transparent_render_phases: ResMut<ViewSortedRenderPhases<Transparent3d>>,
1073    views: Query<(&ExtractedView, &RenderVisibleEntities)>,
1074    specialized_material_pipeline_cache: ResMut<SpecializedMaterialPipelineCache>,
1075) {
1076    for (view, visible_entities) in &views {
1077        let (
1078            Some(opaque_phase),
1079            Some(alpha_mask_phase),
1080            Some(transmissive_phase),
1081            Some(transparent_phase),
1082        ) = (
1083            opaque_render_phases.get_mut(&view.retained_view_entity),
1084            alpha_mask_render_phases.get_mut(&view.retained_view_entity),
1085            transmissive_render_phases.get_mut(&view.retained_view_entity),
1086            transparent_render_phases.get_mut(&view.retained_view_entity),
1087        )
1088        else {
1089            continue;
1090        };
1091
1092        let Some(view_specialized_material_pipeline_cache) =
1093            specialized_material_pipeline_cache.get(&view.retained_view_entity)
1094        else {
1095            continue;
1096        };
1097
1098        let rangefinder = view.rangefinder3d();
1099        for (render_entity, visible_entity) in visible_entities.iter::<Mesh3d>() {
1100            let Some((current_change_tick, pipeline_id)) = view_specialized_material_pipeline_cache
1101                .get(visible_entity)
1102                .map(|(current_change_tick, pipeline_id)| (*current_change_tick, *pipeline_id))
1103            else {
1104                continue;
1105            };
1106
1107            // Skip the entity if it's cached in a bin and up to date.
1108            if opaque_phase.validate_cached_entity(*visible_entity, current_change_tick)
1109                || alpha_mask_phase.validate_cached_entity(*visible_entity, current_change_tick)
1110            {
1111                continue;
1112            }
1113
1114            let Some(material_instance) = render_material_instances.instances.get(visible_entity)
1115            else {
1116                continue;
1117            };
1118            let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity)
1119            else {
1120                continue;
1121            };
1122            let Some(material) = render_materials.get(material_instance.asset_id) else {
1123                continue;
1124            };
1125
1126            // Fetch the slabs that this mesh resides in.
1127            let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
1128            let Some(draw_function) = material.properties.get_draw_function(MaterialDrawFunction)
1129            else {
1130                continue;
1131            };
1132
1133            match material.properties.render_phase_type {
1134                RenderPhaseType::Transmissive => {
1135                    let distance = rangefinder.distance_translation(&mesh_instance.translation)
1136                        + material.properties.depth_bias;
1137                    transmissive_phase.add(Transmissive3d {
1138                        entity: (*render_entity, *visible_entity),
1139                        draw_function,
1140                        pipeline: pipeline_id,
1141                        distance,
1142                        batch_range: 0..1,
1143                        extra_index: PhaseItemExtraIndex::None,
1144                        indexed: index_slab.is_some(),
1145                    });
1146                }
1147                RenderPhaseType::Opaque => {
1148                    if material.properties.render_method == OpaqueRendererMethod::Deferred {
1149                        // Even though we aren't going to insert the entity into
1150                        // a bin, we still want to update its cache entry. That
1151                        // way, we know we don't need to re-examine it in future
1152                        // frames.
1153                        opaque_phase.update_cache(*visible_entity, None, current_change_tick);
1154                        continue;
1155                    }
1156                    let batch_set_key = Opaque3dBatchSetKey {
1157                        pipeline: pipeline_id,
1158                        draw_function,
1159                        material_bind_group_index: Some(material.binding.group.0),
1160                        vertex_slab: vertex_slab.unwrap_or_default(),
1161                        index_slab,
1162                        lightmap_slab: mesh_instance.shared.lightmap_slab_index.map(|index| *index),
1163                    };
1164                    let bin_key = Opaque3dBinKey {
1165                        asset_id: mesh_instance.mesh_asset_id.into(),
1166                    };
1167                    opaque_phase.add(
1168                        batch_set_key,
1169                        bin_key,
1170                        (*render_entity, *visible_entity),
1171                        mesh_instance.current_uniform_index,
1172                        BinnedRenderPhaseType::mesh(
1173                            mesh_instance.should_batch(),
1174                            &gpu_preprocessing_support,
1175                        ),
1176                        current_change_tick,
1177                    );
1178                }
1179                // Alpha mask
1180                RenderPhaseType::AlphaMask => {
1181                    let batch_set_key = OpaqueNoLightmap3dBatchSetKey {
1182                        draw_function,
1183                        pipeline: pipeline_id,
1184                        material_bind_group_index: Some(material.binding.group.0),
1185                        vertex_slab: vertex_slab.unwrap_or_default(),
1186                        index_slab,
1187                    };
1188                    let bin_key = OpaqueNoLightmap3dBinKey {
1189                        asset_id: mesh_instance.mesh_asset_id.into(),
1190                    };
1191                    alpha_mask_phase.add(
1192                        batch_set_key,
1193                        bin_key,
1194                        (*render_entity, *visible_entity),
1195                        mesh_instance.current_uniform_index,
1196                        BinnedRenderPhaseType::mesh(
1197                            mesh_instance.should_batch(),
1198                            &gpu_preprocessing_support,
1199                        ),
1200                        current_change_tick,
1201                    );
1202                }
1203                RenderPhaseType::Transparent => {
1204                    let distance = rangefinder.distance_translation(&mesh_instance.translation)
1205                        + material.properties.depth_bias;
1206                    transparent_phase.add(Transparent3d {
1207                        entity: (*render_entity, *visible_entity),
1208                        draw_function,
1209                        pipeline: pipeline_id,
1210                        distance,
1211                        batch_range: 0..1,
1212                        extra_index: PhaseItemExtraIndex::None,
1213                        indexed: index_slab.is_some(),
1214                    });
1215                }
1216            }
1217        }
1218    }
1219}
1220
1221/// Default render method used for opaque materials.
1222#[derive(Default, Resource, Clone, Debug, ExtractResource, Reflect)]
1223#[reflect(Resource, Default, Debug, Clone)]
1224pub struct DefaultOpaqueRendererMethod(OpaqueRendererMethod);
1225
1226impl DefaultOpaqueRendererMethod {
1227    pub fn forward() -> Self {
1228        DefaultOpaqueRendererMethod(OpaqueRendererMethod::Forward)
1229    }
1230
1231    pub fn deferred() -> Self {
1232        DefaultOpaqueRendererMethod(OpaqueRendererMethod::Deferred)
1233    }
1234
1235    pub fn set_to_forward(&mut self) {
1236        self.0 = OpaqueRendererMethod::Forward;
1237    }
1238
1239    pub fn set_to_deferred(&mut self) {
1240        self.0 = OpaqueRendererMethod::Deferred;
1241    }
1242}
1243
1244/// Render method used for opaque materials.
1245///
1246/// The forward rendering main pass draws each mesh entity and shades it according to its
1247/// corresponding material and the lights that affect it. Some render features like Screen Space
1248/// Ambient Occlusion require running depth and normal prepasses, that are 'deferred'-like
1249/// prepasses over all mesh entities to populate depth and normal textures. This means that when
1250/// using render features that require running prepasses, multiple passes over all visible geometry
1251/// are required. This can be slow if there is a lot of geometry that cannot be batched into few
1252/// draws.
1253///
1254/// Deferred rendering runs a prepass to gather not only geometric information like depth and
1255/// normals, but also all the material properties like base color, emissive color, reflectance,
1256/// metalness, etc, and writes them into a deferred 'g-buffer' texture. The deferred main pass is
1257/// then a fullscreen pass that reads data from these textures and executes shading. This allows
1258/// for one pass over geometry, but is at the cost of not being able to use MSAA, and has heavier
1259/// bandwidth usage which can be unsuitable for low end mobile or other bandwidth-constrained devices.
1260///
1261/// If a material indicates `OpaqueRendererMethod::Auto`, `DefaultOpaqueRendererMethod` will be used.
1262#[derive(Default, Clone, Copy, Debug, PartialEq, Reflect)]
1263#[reflect(Default, Clone, PartialEq)]
1264pub enum OpaqueRendererMethod {
1265    #[default]
1266    Forward,
1267    Deferred,
1268    Auto,
1269}
1270
1271#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1272pub struct MaterialVertexShader;
1273
1274#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1275pub struct MaterialFragmentShader;
1276
1277#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1278pub struct PrepassVertexShader;
1279
1280#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1281pub struct PrepassFragmentShader;
1282
1283#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1284pub struct DeferredVertexShader;
1285
1286#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1287pub struct DeferredFragmentShader;
1288
1289#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1290pub struct MeshletFragmentShader;
1291
1292#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1293pub struct MeshletPrepassFragmentShader;
1294
1295#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1296pub struct MeshletDeferredFragmentShader;
1297
1298#[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1299pub struct MaterialDrawFunction;
1300
1301#[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1302pub struct PrepassDrawFunction;
1303
1304#[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1305pub struct DeferredDrawFunction;
1306
1307#[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1308pub struct ShadowsDrawFunction;
1309
1310#[derive(Debug)]
1311pub struct ErasedMaterialKey {
1312    type_id: TypeId,
1313    hash: u64,
1314    value: Box<dyn Any + Send + Sync>,
1315    vtable: Arc<ErasedMaterialKeyVTable>,
1316}
1317
1318#[derive(Debug)]
1319pub struct ErasedMaterialKeyVTable {
1320    clone_fn: fn(&dyn Any) -> Box<dyn Any + Send + Sync>,
1321    partial_eq_fn: fn(&dyn Any, &dyn Any) -> bool,
1322}
1323
1324impl ErasedMaterialKey {
1325    pub fn new<T>(material_key: T) -> Self
1326    where
1327        T: Clone + Hash + PartialEq + Send + Sync + 'static,
1328    {
1329        let type_id = TypeId::of::<T>();
1330        let hash = FixedHasher::hash_one(&FixedHasher, &material_key);
1331
1332        fn clone<T: Clone + Send + Sync + 'static>(any: &dyn Any) -> Box<dyn Any + Send + Sync> {
1333            Box::new(any.downcast_ref::<T>().unwrap().clone())
1334        }
1335        fn partial_eq<T: PartialEq + 'static>(a: &dyn Any, b: &dyn Any) -> bool {
1336            a.downcast_ref::<T>().unwrap() == b.downcast_ref::<T>().unwrap()
1337        }
1338
1339        Self {
1340            type_id,
1341            hash,
1342            value: Box::new(material_key),
1343            vtable: Arc::new(ErasedMaterialKeyVTable {
1344                clone_fn: clone::<T>,
1345                partial_eq_fn: partial_eq::<T>,
1346            }),
1347        }
1348    }
1349
1350    pub fn to_key<T: Clone + 'static>(&self) -> T {
1351        debug_assert_eq!(self.type_id, TypeId::of::<T>());
1352        self.value.downcast_ref::<T>().unwrap().clone()
1353    }
1354}
1355
1356impl PartialEq for ErasedMaterialKey {
1357    fn eq(&self, other: &Self) -> bool {
1358        self.type_id == other.type_id
1359            && (self.vtable.partial_eq_fn)(self.value.as_ref(), other.value.as_ref())
1360    }
1361}
1362
1363impl Eq for ErasedMaterialKey {}
1364
1365impl Clone for ErasedMaterialKey {
1366    fn clone(&self) -> Self {
1367        Self {
1368            type_id: self.type_id,
1369            hash: self.hash,
1370            value: (self.vtable.clone_fn)(self.value.as_ref()),
1371            vtable: self.vtable.clone(),
1372        }
1373    }
1374}
1375
1376impl Hash for ErasedMaterialKey {
1377    fn hash<H: Hasher>(&self, state: &mut H) {
1378        self.type_id.hash(state);
1379        self.hash.hash(state);
1380    }
1381}
1382
1383impl Default for ErasedMaterialKey {
1384    fn default() -> Self {
1385        Self::new(())
1386    }
1387}
1388
1389/// Common [`Material`] properties, calculated for a specific material instance.
1390#[derive(Default)]
1391pub struct MaterialProperties {
1392    /// Is this material should be rendered by the deferred renderer when.
1393    /// [`AlphaMode::Opaque`] or [`AlphaMode::Mask`]
1394    pub render_method: OpaqueRendererMethod,
1395    /// The [`AlphaMode`] of this material.
1396    pub alpha_mode: AlphaMode,
1397    /// The bits in the [`MeshPipelineKey`] for this material.
1398    ///
1399    /// These are precalculated so that we can just "or" them together in
1400    /// [`queue_material_meshes`].
1401    pub mesh_pipeline_key_bits: MeshPipelineKey,
1402    /// Add a bias to the view depth of the mesh which can be used to force a specific render order
1403    /// for meshes with equal depth, to avoid z-fighting.
1404    /// The bias is in depth-texture units so large values may be needed to overcome small depth differences.
1405    pub depth_bias: f32,
1406    /// Whether the material would like to read from [`ViewTransmissionTexture`](bevy_core_pipeline::core_3d::ViewTransmissionTexture).
1407    ///
1408    /// This allows taking color output from the [`Opaque3d`] pass as an input, (for screen-space transmission) but requires
1409    /// rendering to take place in a separate [`Transmissive3d`] pass.
1410    pub reads_view_transmission_texture: bool,
1411    pub render_phase_type: RenderPhaseType,
1412    pub material_layout: Option<BindGroupLayout>,
1413    /// Backing array is a size of 4 because the `StandardMaterial` needs 4 draw functions by default
1414    pub draw_functions: SmallVec<[(InternedDrawFunctionLabel, DrawFunctionId); 4]>,
1415    /// Backing array is a size of 3 because the `StandardMaterial` has 3 custom shaders (`frag`, `prepass_frag`, `deferred_frag`) which is the
1416    /// most common use case
1417    pub shaders: SmallVec<[(InternedShaderLabel, Handle<Shader>); 3]>,
1418    /// Whether this material *actually* uses bindless resources, taking the
1419    /// platform support (or lack thereof) of bindless resources into account.
1420    pub bindless: bool,
1421    pub specialize: Option<
1422        fn(
1423            &MaterialPipeline,
1424            &mut RenderPipelineDescriptor,
1425            &MeshVertexBufferLayoutRef,
1426            ErasedMaterialPipelineKey,
1427        ) -> Result<(), SpecializedMeshPipelineError>,
1428    >,
1429    /// The key for this material, typically a bitfield of flags that are used to modify
1430    /// the pipeline descriptor used for this material.
1431    pub material_key: ErasedMaterialKey,
1432    /// Whether shadows are enabled for this material
1433    pub shadows_enabled: bool,
1434    /// Whether prepass is enabled for this material
1435    pub prepass_enabled: bool,
1436}
1437
1438impl MaterialProperties {
1439    pub fn get_shader(&self, label: impl ShaderLabel) -> Option<Handle<Shader>> {
1440        self.shaders
1441            .iter()
1442            .find(|(inner_label, _)| inner_label == &label.intern())
1443            .map(|(_, shader)| shader)
1444            .cloned()
1445    }
1446
1447    pub fn add_shader(&mut self, label: impl ShaderLabel, shader: Handle<Shader>) {
1448        self.shaders.push((label.intern(), shader));
1449    }
1450
1451    pub fn get_draw_function(&self, label: impl DrawFunctionLabel) -> Option<DrawFunctionId> {
1452        self.draw_functions
1453            .iter()
1454            .find(|(inner_label, _)| inner_label == &label.intern())
1455            .map(|(_, shader)| shader)
1456            .cloned()
1457    }
1458
1459    pub fn add_draw_function(
1460        &mut self,
1461        label: impl DrawFunctionLabel,
1462        draw_function: DrawFunctionId,
1463    ) {
1464        self.draw_functions.push((label.intern(), draw_function));
1465    }
1466}
1467
1468#[derive(Clone, Copy, Default)]
1469pub enum RenderPhaseType {
1470    #[default]
1471    Opaque,
1472    AlphaMask,
1473    Transmissive,
1474    Transparent,
1475}
1476
1477/// A resource that maps each untyped material ID to its binding.
1478///
1479/// This duplicates information in `RenderAssets<M>`, but it doesn't have the
1480/// `M` type parameter, so it can be used in untyped contexts like
1481/// [`crate::render::mesh::collect_meshes_for_gpu_building`].
1482#[derive(Resource, Default, Deref, DerefMut)]
1483pub struct RenderMaterialBindings(HashMap<UntypedAssetId, MaterialBindingId>);
1484
1485/// Data prepared for a [`Material`] instance.
1486pub struct PreparedMaterial {
1487    pub binding: MaterialBindingId,
1488    pub properties: Arc<MaterialProperties>,
1489}
1490
1491// orphan rules T_T
1492impl<M: Material> ErasedRenderAsset for MeshMaterial3d<M>
1493where
1494    M::Data: PartialEq + Eq + Hash + Clone,
1495{
1496    type SourceAsset = M;
1497    type ErasedAsset = PreparedMaterial;
1498
1499    type Param = (
1500        SRes<RenderDevice>,
1501        SRes<DefaultOpaqueRendererMethod>,
1502        SResMut<MaterialBindGroupAllocators>,
1503        SResMut<RenderMaterialBindings>,
1504        SRes<DrawFunctions<Opaque3d>>,
1505        SRes<DrawFunctions<AlphaMask3d>>,
1506        SRes<DrawFunctions<Transmissive3d>>,
1507        SRes<DrawFunctions<Transparent3d>>,
1508        SRes<DrawFunctions<Opaque3dPrepass>>,
1509        SRes<DrawFunctions<AlphaMask3dPrepass>>,
1510        SRes<DrawFunctions<Opaque3dDeferred>>,
1511        SRes<DrawFunctions<AlphaMask3dDeferred>>,
1512        SRes<DrawFunctions<Shadow>>,
1513        SRes<AssetServer>,
1514        (
1515            Option<SRes<ShadowsEnabled<M>>>,
1516            Option<SRes<PrepassEnabled<M>>>,
1517            M::Param,
1518        ),
1519    );
1520
1521    fn prepare_asset(
1522        material: Self::SourceAsset,
1523        material_id: AssetId<Self::SourceAsset>,
1524        (
1525            render_device,
1526            default_opaque_render_method,
1527            bind_group_allocators,
1528            render_material_bindings,
1529            opaque_draw_functions,
1530            alpha_mask_draw_functions,
1531            transmissive_draw_functions,
1532            transparent_draw_functions,
1533            opaque_prepass_draw_functions,
1534            alpha_mask_prepass_draw_functions,
1535            opaque_deferred_draw_functions,
1536            alpha_mask_deferred_draw_functions,
1537            shadow_draw_functions,
1538            asset_server,
1539            (shadows_enabled, prepass_enabled, material_param),
1540        ): &mut SystemParamItem<Self::Param>,
1541    ) -> Result<Self::ErasedAsset, PrepareAssetError<Self::SourceAsset>> {
1542        let material_layout = M::bind_group_layout(render_device);
1543
1544        let shadows_enabled = shadows_enabled.is_some();
1545        let prepass_enabled = prepass_enabled.is_some();
1546
1547        let draw_opaque_pbr = opaque_draw_functions.read().id::<DrawMaterial>();
1548        let draw_alpha_mask_pbr = alpha_mask_draw_functions.read().id::<DrawMaterial>();
1549        let draw_transmissive_pbr = transmissive_draw_functions.read().id::<DrawMaterial>();
1550        let draw_transparent_pbr = transparent_draw_functions.read().id::<DrawMaterial>();
1551        let draw_opaque_prepass = opaque_prepass_draw_functions.read().get_id::<DrawPrepass>();
1552        let draw_alpha_mask_prepass = alpha_mask_prepass_draw_functions
1553            .read()
1554            .get_id::<DrawPrepass>();
1555        let draw_opaque_deferred = opaque_deferred_draw_functions
1556            .read()
1557            .get_id::<DrawPrepass>();
1558        let draw_alpha_mask_deferred = alpha_mask_deferred_draw_functions
1559            .read()
1560            .get_id::<DrawPrepass>();
1561        let shadow_draw_function_id = shadow_draw_functions.read().get_id::<DrawPrepass>();
1562
1563        let render_method = match material.opaque_render_method() {
1564            OpaqueRendererMethod::Forward => OpaqueRendererMethod::Forward,
1565            OpaqueRendererMethod::Deferred => OpaqueRendererMethod::Deferred,
1566            OpaqueRendererMethod::Auto => default_opaque_render_method.0,
1567        };
1568
1569        let mut mesh_pipeline_key_bits = MeshPipelineKey::empty();
1570        mesh_pipeline_key_bits.set(
1571            MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE,
1572            material.reads_view_transmission_texture(),
1573        );
1574
1575        let reads_view_transmission_texture =
1576            mesh_pipeline_key_bits.contains(MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE);
1577
1578        let render_phase_type = match material.alpha_mode() {
1579            AlphaMode::Blend | AlphaMode::Premultiplied | AlphaMode::Add | AlphaMode::Multiply => {
1580                RenderPhaseType::Transparent
1581            }
1582            _ if reads_view_transmission_texture => RenderPhaseType::Transmissive,
1583            AlphaMode::Opaque | AlphaMode::AlphaToCoverage => RenderPhaseType::Opaque,
1584            AlphaMode::Mask(_) => RenderPhaseType::AlphaMask,
1585        };
1586
1587        let draw_function_id = match render_phase_type {
1588            RenderPhaseType::Opaque => draw_opaque_pbr,
1589            RenderPhaseType::AlphaMask => draw_alpha_mask_pbr,
1590            RenderPhaseType::Transmissive => draw_transmissive_pbr,
1591            RenderPhaseType::Transparent => draw_transparent_pbr,
1592        };
1593        let prepass_draw_function_id = match render_phase_type {
1594            RenderPhaseType::Opaque => draw_opaque_prepass,
1595            RenderPhaseType::AlphaMask => draw_alpha_mask_prepass,
1596            _ => None,
1597        };
1598        let deferred_draw_function_id = match render_phase_type {
1599            RenderPhaseType::Opaque => draw_opaque_deferred,
1600            RenderPhaseType::AlphaMask => draw_alpha_mask_deferred,
1601            _ => None,
1602        };
1603
1604        let mut draw_functions = SmallVec::new();
1605        draw_functions.push((MaterialDrawFunction.intern(), draw_function_id));
1606        if let Some(prepass_draw_function_id) = prepass_draw_function_id {
1607            draw_functions.push((PrepassDrawFunction.intern(), prepass_draw_function_id));
1608        }
1609        if let Some(deferred_draw_function_id) = deferred_draw_function_id {
1610            draw_functions.push((DeferredDrawFunction.intern(), deferred_draw_function_id));
1611        }
1612        if let Some(shadow_draw_function_id) = shadow_draw_function_id {
1613            draw_functions.push((ShadowsDrawFunction.intern(), shadow_draw_function_id));
1614        }
1615
1616        let mut shaders = SmallVec::new();
1617        let mut add_shader = |label: InternedShaderLabel, shader_ref: ShaderRef| {
1618            let mayber_shader = match shader_ref {
1619                ShaderRef::Default => None,
1620                ShaderRef::Handle(handle) => Some(handle),
1621                ShaderRef::Path(path) => Some(asset_server.load(path)),
1622            };
1623            if let Some(shader) = mayber_shader {
1624                shaders.push((label, shader));
1625            }
1626        };
1627        add_shader(MaterialVertexShader.intern(), M::vertex_shader());
1628        add_shader(MaterialFragmentShader.intern(), M::fragment_shader());
1629        add_shader(PrepassVertexShader.intern(), M::prepass_vertex_shader());
1630        add_shader(PrepassFragmentShader.intern(), M::prepass_fragment_shader());
1631        add_shader(DeferredVertexShader.intern(), M::deferred_vertex_shader());
1632        add_shader(
1633            DeferredFragmentShader.intern(),
1634            M::deferred_fragment_shader(),
1635        );
1636
1637        #[cfg(feature = "meshlet")]
1638        {
1639            add_shader(
1640                MeshletFragmentShader.intern(),
1641                M::meshlet_mesh_fragment_shader(),
1642            );
1643            add_shader(
1644                MeshletPrepassFragmentShader.intern(),
1645                M::meshlet_mesh_prepass_fragment_shader(),
1646            );
1647            add_shader(
1648                MeshletDeferredFragmentShader.intern(),
1649                M::meshlet_mesh_deferred_fragment_shader(),
1650            );
1651        }
1652
1653        let bindless = material_uses_bindless_resources::<M>(render_device);
1654        let bind_group_data = material.bind_group_data();
1655        let material_key = ErasedMaterialKey::new(bind_group_data);
1656        fn specialize<M: Material>(
1657            pipeline: &MaterialPipeline,
1658            descriptor: &mut RenderPipelineDescriptor,
1659            mesh_layout: &MeshVertexBufferLayoutRef,
1660            erased_key: ErasedMaterialPipelineKey,
1661        ) -> Result<(), SpecializedMeshPipelineError>
1662        where
1663            M::Data: Hash + Clone,
1664        {
1665            let material_key = erased_key.material_key.to_key();
1666            M::specialize(
1667                pipeline,
1668                descriptor,
1669                mesh_layout,
1670                MaterialPipelineKey {
1671                    mesh_key: erased_key.mesh_key,
1672                    bind_group_data: material_key,
1673                },
1674            )
1675        }
1676
1677        match material.unprepared_bind_group(&material_layout, render_device, material_param, false)
1678        {
1679            Ok(unprepared) => {
1680                let bind_group_allocator =
1681                    bind_group_allocators.get_mut(&TypeId::of::<M>()).unwrap();
1682                // Allocate or update the material.
1683                let binding = match render_material_bindings.entry(material_id.into()) {
1684                    Entry::Occupied(mut occupied_entry) => {
1685                        // TODO: Have a fast path that doesn't require
1686                        // recreating the bind group if only buffer contents
1687                        // change. For now, we just delete and recreate the bind
1688                        // group.
1689                        bind_group_allocator.free(*occupied_entry.get());
1690                        let new_binding =
1691                            bind_group_allocator.allocate_unprepared(unprepared, &material_layout);
1692                        *occupied_entry.get_mut() = new_binding;
1693                        new_binding
1694                    }
1695                    Entry::Vacant(vacant_entry) => *vacant_entry.insert(
1696                        bind_group_allocator.allocate_unprepared(unprepared, &material_layout),
1697                    ),
1698                };
1699
1700                Ok(PreparedMaterial {
1701                    binding,
1702                    properties: Arc::new(MaterialProperties {
1703                        alpha_mode: material.alpha_mode(),
1704                        depth_bias: material.depth_bias(),
1705                        reads_view_transmission_texture,
1706                        render_phase_type,
1707                        render_method,
1708                        mesh_pipeline_key_bits,
1709                        material_layout: Some(material_layout),
1710                        draw_functions,
1711                        shaders,
1712                        bindless,
1713                        specialize: Some(specialize::<M>),
1714                        material_key,
1715                        shadows_enabled,
1716                        prepass_enabled,
1717                    }),
1718                })
1719            }
1720
1721            Err(AsBindGroupError::RetryNextUpdate) => {
1722                Err(PrepareAssetError::RetryNextUpdate(material))
1723            }
1724
1725            Err(AsBindGroupError::CreateBindGroupDirectly) => {
1726                // This material has opted out of automatic bind group creation
1727                // and is requesting a fully-custom bind group. Invoke
1728                // `as_bind_group` as requested, and store the resulting bind
1729                // group in the slot.
1730                match material.as_bind_group(&material_layout, render_device, material_param) {
1731                    Ok(prepared_bind_group) => {
1732                        let bind_group_allocator =
1733                            bind_group_allocators.get_mut(&TypeId::of::<M>()).unwrap();
1734                        // Store the resulting bind group directly in the slot.
1735                        let material_binding_id =
1736                            bind_group_allocator.allocate_prepared(prepared_bind_group);
1737                        render_material_bindings.insert(material_id.into(), material_binding_id);
1738
1739                        Ok(PreparedMaterial {
1740                            binding: material_binding_id,
1741                            properties: Arc::new(MaterialProperties {
1742                                alpha_mode: material.alpha_mode(),
1743                                depth_bias: material.depth_bias(),
1744                                reads_view_transmission_texture,
1745                                render_phase_type,
1746                                render_method,
1747                                mesh_pipeline_key_bits,
1748                                material_layout: Some(material_layout),
1749                                draw_functions,
1750                                shaders,
1751                                bindless,
1752                                specialize: Some(specialize::<M>),
1753                                material_key,
1754                                shadows_enabled,
1755                                prepass_enabled,
1756                            }),
1757                        })
1758                    }
1759
1760                    Err(AsBindGroupError::RetryNextUpdate) => {
1761                        Err(PrepareAssetError::RetryNextUpdate(material))
1762                    }
1763
1764                    Err(other) => Err(PrepareAssetError::AsBindGroupError(other)),
1765                }
1766            }
1767
1768            Err(other) => Err(PrepareAssetError::AsBindGroupError(other)),
1769        }
1770    }
1771
1772    fn unload_asset(
1773        source_asset: AssetId<Self::SourceAsset>,
1774        (_, _, bind_group_allocators, render_material_bindings, ..): &mut SystemParamItem<
1775            Self::Param,
1776        >,
1777    ) {
1778        let Some(material_binding_id) = render_material_bindings.remove(&source_asset.untyped())
1779        else {
1780            return;
1781        };
1782        let bind_group_allactor = bind_group_allocators.get_mut(&TypeId::of::<M>()).unwrap();
1783        bind_group_allactor.free(material_binding_id);
1784    }
1785}
1786
1787/// Creates and/or recreates any bind groups that contain materials that were
1788/// modified this frame.
1789pub fn prepare_material_bind_groups(
1790    mut allocators: ResMut<MaterialBindGroupAllocators>,
1791    render_device: Res<RenderDevice>,
1792    fallback_image: Res<FallbackImage>,
1793    fallback_resources: Res<FallbackBindlessResources>,
1794) {
1795    for (_, allocator) in allocators.iter_mut() {
1796        allocator.prepare_bind_groups(&render_device, &fallback_resources, &fallback_image);
1797    }
1798}
1799
1800/// Uploads the contents of all buffers that the [`MaterialBindGroupAllocator`]
1801/// manages to the GPU.
1802///
1803/// Non-bindless allocators don't currently manage any buffers, so this method
1804/// only has an effect for bindless allocators.
1805pub fn write_material_bind_group_buffers(
1806    mut allocators: ResMut<MaterialBindGroupAllocators>,
1807    render_device: Res<RenderDevice>,
1808    render_queue: Res<RenderQueue>,
1809) {
1810    for (_, allocator) in allocators.iter_mut() {
1811        allocator.write_buffers(&render_device, &render_queue);
1812    }
1813}
1814
1815/// Marker resource for whether shadows are enabled for this material type
1816#[derive(Resource, Debug)]
1817pub struct ShadowsEnabled<M: Material>(PhantomData<M>);
1818
1819impl<M: Material> Default for ShadowsEnabled<M> {
1820    fn default() -> Self {
1821        Self(PhantomData)
1822    }
1823}