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