Skip to main content

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