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
59pub trait Material: Asset + AsBindGroup + Clone + Sized {
128 fn vertex_shader() -> ShaderRef {
131 ShaderRef::Default
132 }
133
134 fn fragment_shader() -> ShaderRef {
137 ShaderRef::Default
138 }
139
140 #[inline]
142 fn alpha_mode(&self) -> AlphaMode {
143 AlphaMode::Opaque
144 }
145
146 #[inline]
150 fn opaque_render_method(&self) -> OpaqueRendererMethod {
151 OpaqueRendererMethod::Forward
152 }
153
154 #[inline]
155 fn depth_bias(&self) -> f32 {
159 0.0
160 }
161
162 #[inline]
163 fn reads_view_transmission_texture(&self) -> bool {
168 false
169 }
170
171 fn prepass_vertex_shader() -> ShaderRef {
177 ShaderRef::Default
178 }
179
180 fn prepass_fragment_shader() -> ShaderRef {
186 ShaderRef::Default
187 }
188
189 fn deferred_vertex_shader() -> ShaderRef {
192 ShaderRef::Default
193 }
194
195 fn deferred_fragment_shader() -> ShaderRef {
198 ShaderRef::Default
199 }
200
201 #[cfg(feature = "meshlet")]
208 fn meshlet_mesh_fragment_shader() -> ShaderRef {
209 ShaderRef::Default
210 }
211
212 #[cfg(feature = "meshlet")]
219 fn meshlet_mesh_prepass_fragment_shader() -> ShaderRef {
220 ShaderRef::Default
221 }
222
223 #[cfg(feature = "meshlet")]
230 fn meshlet_mesh_deferred_fragment_shader() -> ShaderRef {
231 ShaderRef::Default
232 }
233
234 #[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
251pub struct MaterialPlugin<M: Material> {
254 pub prepass_enabled: bool,
260 pub shadows_enabled: bool,
262 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>
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 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
410pub(crate) static DUMMY_MESH_MATERIAL: AssetId<StandardMaterial> =
416 AssetId::<StandardMaterial>::invalid();
417
418pub 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#[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 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 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
551pub 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#[derive(Resource, Default)]
601pub struct RenderMaterialInstances {
602 pub instances: MainEntityHashMap<RenderMaterialInstance>,
605 current_change_tick: Tick,
609}
610
611impl RenderMaterialInstances {
612 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
626pub struct RenderMaterialInstance {
631 pub(crate) asset_id: UntypedAssetId,
633 last_change_tick: Tick,
636}
637
638#[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 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
692fn 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
719fn 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
749fn 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 if occupied_entry.get().last_change_tick != last_change_tick {
775 occupied_entry.remove();
776 }
777 }
778 }
779}
780
781pub(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 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 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 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#[derive(Resource, Deref, DerefMut)]
891pub struct SpecializedMaterialPipelineCache<M> {
892 #[deref]
894 map: HashMap<RetainedViewEntity, SpecializedMaterialViewPipelineCache<M>>,
895 marker: PhantomData<M>,
896}
897
898#[derive(Deref, DerefMut)]
901pub struct SpecializedMaterialViewPipelineCache<M> {
902 #[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 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 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 specialized_material_pipeline_cache
1105 .retain(|retained_view_entity, _| all_views.contains(retained_view_entity));
1106}
1107
1108pub 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 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 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 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 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#[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#[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
1319pub struct MaterialProperties {
1321 pub render_method: OpaqueRendererMethod,
1324 pub alpha_mode: AlphaMode,
1326 pub mesh_pipeline_key_bits: MeshPipelineKey,
1331 pub depth_bias: f32,
1335 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#[derive(Resource, Default, Deref, DerefMut)]
1360pub struct RenderMaterialBindings(HashMap<UntypedAssetId, MaterialBindingId>);
1361
1362pub 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 let binding = match render_material_bindings.entry(material_id.into()) {
1476 Entry::Occupied(mut occupied_entry) => {
1477 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 match material.as_bind_group(
1520 &pipeline.material_layout,
1521 render_device,
1522 material_param,
1523 ) {
1524 Ok(prepared_bind_group) => {
1525 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
1588pub 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
1601pub 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}