Skip to main content

bevy_pbr/
material.rs

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