bevy_pbr/
material.rs

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