use crate::material_bind_groups::{
FallbackBindlessResources, MaterialBindGroupAllocator, MaterialBindingId,
};
use crate::*;
use alloc::sync::Arc;
use bevy_asset::prelude::AssetChanged;
use bevy_asset::{Asset, AssetEventSystems, AssetId, AssetServer, UntypedAssetId};
use bevy_camera::visibility::ViewVisibility;
use bevy_core_pipeline::deferred::{AlphaMask3dDeferred, Opaque3dDeferred};
use bevy_core_pipeline::prepass::{AlphaMask3dPrepass, Opaque3dPrepass};
use bevy_core_pipeline::{
core_3d::{AlphaMask3d, Opaque3d, Opaque3dBatchSetKey, Opaque3dBinKey, Transparent3d},
prepass::{OpaqueNoLightmap3dBatchSetKey, OpaqueNoLightmap3dBinKey},
tonemapping::Tonemapping,
};
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::change_detection::Tick;
use bevy_ecs::system::{SystemChangeTick, SystemParam};
use bevy_ecs::{
prelude::*,
system::{
lifetimeless::{SRes, SResMut},
SystemParamItem, SystemState,
},
};
use bevy_material::{
key::{ErasedMaterialKey, ErasedMaterialPipelineKey, ErasedMeshPipelineKey},
labels::{DrawFunctionLabel, InternedShaderLabel, ShaderLabel},
MaterialProperties, OpaqueRendererMethod, RenderPhaseType,
};
use bevy_mesh::{
mark_3d_meshes_as_changed_if_their_assets_changed, Mesh3d, MeshVertexBufferLayoutRef,
};
use bevy_platform::collections::hash_map::Entry;
use bevy_platform::collections::{HashMap, HashSet};
use bevy_platform::hash::FixedHasher;
use bevy_reflect::std_traits::ReflectDefault;
use bevy_reflect::Reflect;
use bevy_render::camera::extract_cameras;
use bevy_render::erased_render_asset::{
ErasedRenderAsset, ErasedRenderAssetPlugin, ErasedRenderAssets, PrepareAssetError,
};
use bevy_render::render_asset::{prepare_assets, RenderAssets};
use bevy_render::renderer::RenderQueue;
use bevy_render::RenderStartup;
use bevy_render::{
batching::gpu_preprocessing::GpuPreprocessingSupport,
extract_resource::ExtractResource,
mesh::RenderMesh,
prelude::*,
render_phase::*,
render_resource::*,
renderer::RenderDevice,
sync_world::MainEntity,
view::{ExtractedView, Msaa, RenderVisibilityRanges, RetainedViewEntity},
Extract,
};
use bevy_render::{mesh::allocator::MeshAllocator, sync_world::MainEntityHashMap};
use bevy_render::{texture::FallbackImage, view::RenderVisibleEntities};
use bevy_shader::ShaderDefVal;
use bevy_utils::Parallel;
use core::{
any::{Any, TypeId},
hash::Hash,
marker::PhantomData,
};
use smallvec::SmallVec;
use tracing::error;
pub const MATERIAL_BIND_GROUP_INDEX: usize = 3;
pub trait Material: Asset + AsBindGroup + Clone + Sized {
fn vertex_shader() -> ShaderRef {
ShaderRef::Default
}
fn fragment_shader() -> ShaderRef {
ShaderRef::Default
}
#[inline]
fn alpha_mode(&self) -> AlphaMode {
AlphaMode::Opaque
}
#[inline]
fn opaque_render_method(&self) -> OpaqueRendererMethod {
OpaqueRendererMethod::Forward
}
#[inline]
fn depth_bias(&self) -> f32 {
0.0
}
#[inline]
fn reads_view_transmission_texture(&self) -> bool {
false
}
#[inline]
fn enable_prepass() -> bool {
true
}
#[inline]
fn enable_shadows() -> bool {
true
}
fn prepass_vertex_shader() -> ShaderRef {
ShaderRef::Default
}
fn prepass_fragment_shader() -> ShaderRef {
ShaderRef::Default
}
fn deferred_vertex_shader() -> ShaderRef {
ShaderRef::Default
}
fn deferred_fragment_shader() -> ShaderRef {
ShaderRef::Default
}
#[cfg(feature = "meshlet")]
fn meshlet_mesh_fragment_shader() -> ShaderRef {
ShaderRef::Default
}
#[cfg(feature = "meshlet")]
fn meshlet_mesh_prepass_fragment_shader() -> ShaderRef {
ShaderRef::Default
}
#[cfg(feature = "meshlet")]
fn meshlet_mesh_deferred_fragment_shader() -> ShaderRef {
ShaderRef::Default
}
#[expect(
unused_variables,
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."
)]
#[inline]
fn specialize(
pipeline: &MaterialPipeline,
descriptor: &mut RenderPipelineDescriptor,
layout: &MeshVertexBufferLayoutRef,
key: MaterialPipelineKey<Self>,
) -> Result<(), SpecializedMeshPipelineError> {
Ok(())
}
}
#[derive(Default)]
pub struct MaterialsPlugin {
pub debug_flags: RenderDebugFlags,
}
impl Plugin for MaterialsPlugin {
fn build(&self, app: &mut App) {
app.add_plugins((PrepassPipelinePlugin, PrepassPlugin::new(self.debug_flags)));
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
.init_resource::<EntitySpecializationTicks>()
.init_resource::<SpecializedMaterialPipelineCache>()
.init_resource::<SpecializedMeshPipelines<MaterialPipelineSpecializer>>()
.init_resource::<LightKeyCache>()
.init_resource::<LightSpecializationTicks>()
.init_resource::<SpecializedShadowMaterialPipelineCache>()
.init_resource::<DrawFunctions<Shadow>>()
.init_resource::<RenderMaterialInstances>()
.init_resource::<MaterialBindGroupAllocators>()
.add_render_command::<Shadow, DrawPrepass>()
.add_render_command::<Shadow, DrawDepthOnlyPrepass>()
.add_render_command::<Transparent3d, DrawMaterial>()
.add_render_command::<Opaque3d, DrawMaterial>()
.add_render_command::<AlphaMask3d, DrawMaterial>()
.add_systems(RenderStartup, init_material_pipeline)
.add_systems(
Render,
(
specialize_material_meshes
.in_set(RenderSystems::PrepareMeshes)
.after(prepare_assets::<RenderMesh>)
.after(collect_meshes_for_gpu_building)
.after(set_mesh_motion_vector_flags),
queue_material_meshes.in_set(RenderSystems::QueueMeshes),
),
)
.add_systems(
Render,
(
prepare_material_bind_groups,
write_material_bind_group_buffers,
)
.chain()
.in_set(RenderSystems::PrepareBindGroups),
)
.add_systems(
Render,
(
check_views_lights_need_specialization.in_set(RenderSystems::PrepareAssets),
specialize_shadows
.in_set(RenderSystems::ManageViews)
.after(prepare_lights),
queue_shadows.in_set(RenderSystems::QueueMeshes),
),
);
}
}
}
pub struct MaterialPlugin<M: Material> {
pub debug_flags: RenderDebugFlags,
pub _marker: PhantomData<M>,
}
impl<M: Material> Default for MaterialPlugin<M> {
fn default() -> Self {
Self {
debug_flags: RenderDebugFlags::default(),
_marker: Default::default(),
}
}
}
impl<M: Material> Plugin for MaterialPlugin<M>
where
M::Data: PartialEq + Eq + Hash + Clone,
{
fn build(&self, app: &mut App) {
app.init_asset::<M>()
.register_type::<MeshMaterial3d<M>>()
.init_resource::<EntitiesNeedingSpecialization<M>>()
.add_plugins((ErasedRenderAssetPlugin::<MeshMaterial3d<M>>::default(),))
.add_systems(
PostUpdate,
(
mark_meshes_as_changed_if_their_materials_changed::<M>.ambiguous_with_all(),
check_entities_needing_specialization::<M>.after(AssetEventSystems),
)
.after(mark_3d_meshes_as_changed_if_their_assets_changed),
);
if M::enable_shadows() {
app.add_systems(
PostUpdate,
check_light_entities_needing_specialization::<M>
.after(check_entities_needing_specialization::<M>),
);
}
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
.add_systems(RenderStartup, add_material_bind_group_allocator::<M>)
.add_systems(
ExtractSchedule,
(
extract_mesh_materials::<M>.in_set(MaterialExtractionSystems),
early_sweep_material_instances::<M>
.after(MaterialExtractionSystems)
.before(late_sweep_material_instances),
extract_entities_needs_specialization::<M>
.in_set(MaterialExtractEntitiesNeedingSpecializationSystems),
sweep_entities_needing_specialization::<M>
.after(MaterialExtractEntitiesNeedingSpecializationSystems)
.after(MaterialExtractionSystems)
.after(extract_cameras)
.before(late_sweep_material_instances),
),
);
}
}
}
fn add_material_bind_group_allocator<M: Material>(
render_device: Res<RenderDevice>,
mut bind_group_allocators: ResMut<MaterialBindGroupAllocators>,
) {
bind_group_allocators.insert(
TypeId::of::<M>(),
MaterialBindGroupAllocator::new(
&render_device,
M::label(),
material_uses_bindless_resources::<M>(&render_device)
.then(|| M::bindless_descriptor())
.flatten(),
M::bind_group_layout_descriptor(&render_device),
M::bindless_slot_count(),
),
);
}
pub(crate) static DUMMY_MESH_MATERIAL: AssetId<StandardMaterial> =
AssetId::<StandardMaterial>::invalid();
pub struct MaterialPipelineKey<M: Material> {
pub mesh_key: MeshPipelineKey,
pub bind_group_data: M::Data,
}
#[derive(Resource, Clone)]
pub struct MaterialPipeline {
pub mesh_pipeline: MeshPipeline,
}
pub struct MaterialPipelineSpecializer {
pub(crate) pipeline: MaterialPipeline,
pub(crate) properties: Arc<MaterialProperties>,
}
impl SpecializedMeshPipeline for MaterialPipelineSpecializer {
type Key = ErasedMaterialPipelineKey;
fn specialize(
&self,
key: Self::Key,
layout: &MeshVertexBufferLayoutRef,
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
let concrete_mesh_key: MeshPipelineKey = key.mesh_key.downcast();
let mut descriptor = self
.pipeline
.mesh_pipeline
.specialize(concrete_mesh_key, layout)?;
descriptor.vertex.shader_defs.push(ShaderDefVal::UInt(
"MATERIAL_BIND_GROUP".into(),
MATERIAL_BIND_GROUP_INDEX as u32,
));
if let Some(ref mut fragment) = descriptor.fragment {
fragment.shader_defs.push(ShaderDefVal::UInt(
"MATERIAL_BIND_GROUP".into(),
MATERIAL_BIND_GROUP_INDEX as u32,
));
};
if let Some(vertex_shader) = self.properties.get_shader(MaterialVertexShader) {
descriptor.vertex.shader = vertex_shader.clone();
}
if let Some(fragment_shader) = self.properties.get_shader(MaterialFragmentShader) {
descriptor.fragment.as_mut().unwrap().shader = fragment_shader.clone();
}
descriptor
.layout
.insert(3, self.properties.material_layout.as_ref().unwrap().clone());
if let Some(specialize) = self.properties.user_specialize {
specialize(&self.pipeline as &dyn Any, &mut descriptor, layout, key)?;
}
if self.properties.bindless {
descriptor.vertex.shader_defs.push("BINDLESS".into());
if let Some(ref mut fragment) = descriptor.fragment {
fragment.shader_defs.push("BINDLESS".into());
}
}
Ok(descriptor)
}
}
pub fn init_material_pipeline(mut commands: Commands, mesh_pipeline: Res<MeshPipeline>) {
commands.insert_resource(MaterialPipeline {
mesh_pipeline: mesh_pipeline.clone(),
});
}
pub type DrawMaterial = (
SetItemPipeline,
SetMeshViewBindGroup<0>,
SetMeshViewBindingArrayBindGroup<1>,
SetMeshBindGroup<2>,
SetMaterialBindGroup<MATERIAL_BIND_GROUP_INDEX>,
DrawMesh,
);
pub struct SetMaterialBindGroup<const I: usize>;
impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetMaterialBindGroup<I> {
type Param = (
SRes<ErasedRenderAssets<PreparedMaterial>>,
SRes<RenderMaterialInstances>,
SRes<MaterialBindGroupAllocators>,
);
type ViewQuery = ();
type ItemQuery = ();
#[inline]
fn render<'w>(
item: &P,
_view: (),
_item_query: Option<()>,
(materials, material_instances, material_bind_group_allocator): SystemParamItem<
'w,
'_,
Self::Param,
>,
pass: &mut TrackedRenderPass<'w>,
) -> RenderCommandResult {
let materials = materials.into_inner();
let material_instances = material_instances.into_inner();
let material_bind_group_allocators = material_bind_group_allocator.into_inner();
let Some(material_instance) = material_instances.instances.get(&item.main_entity()) else {
return RenderCommandResult::Skip;
};
let Some(material_bind_group_allocator) =
material_bind_group_allocators.get(&material_instance.asset_id.type_id())
else {
return RenderCommandResult::Skip;
};
let Some(material) = materials.get(material_instance.asset_id) else {
return RenderCommandResult::Skip;
};
let Some(material_bind_group) = material_bind_group_allocator.get(material.binding.group)
else {
return RenderCommandResult::Skip;
};
let Some(bind_group) = material_bind_group.bind_group() else {
return RenderCommandResult::Skip;
};
pass.set_bind_group(I, bind_group, &[]);
RenderCommandResult::Success
}
}
#[derive(Resource, Default)]
pub struct RenderMaterialInstances {
pub instances: MainEntityHashMap<RenderMaterialInstance>,
pub current_change_tick: Tick,
}
impl RenderMaterialInstances {
pub(crate) fn mesh_material(&self, entity: MainEntity) -> UntypedAssetId {
match self.instances.get(&entity) {
Some(render_instance) => render_instance.asset_id,
None => DUMMY_MESH_MATERIAL.into(),
}
}
}
pub struct RenderMaterialInstance {
pub asset_id: UntypedAssetId,
pub last_change_tick: Tick,
}
#[derive(SystemSet, Clone, PartialEq, Eq, Debug, Hash)]
pub struct MaterialExtractionSystems;
#[derive(SystemSet, Clone, PartialEq, Eq, Debug, Hash)]
pub struct MaterialExtractEntitiesNeedingSpecializationSystems;
pub const fn alpha_mode_pipeline_key(alpha_mode: AlphaMode, msaa: &Msaa) -> MeshPipelineKey {
match alpha_mode {
AlphaMode::Premultiplied | AlphaMode::Add => MeshPipelineKey::BLEND_PREMULTIPLIED_ALPHA,
AlphaMode::Blend => MeshPipelineKey::BLEND_ALPHA,
AlphaMode::Multiply => MeshPipelineKey::BLEND_MULTIPLY,
AlphaMode::Mask(_) => MeshPipelineKey::MAY_DISCARD,
AlphaMode::AlphaToCoverage => match *msaa {
Msaa::Off => MeshPipelineKey::MAY_DISCARD,
_ => MeshPipelineKey::BLEND_ALPHA_TO_COVERAGE,
},
_ => MeshPipelineKey::NONE,
}
}
pub const fn tonemapping_pipeline_key(tonemapping: Tonemapping) -> MeshPipelineKey {
match tonemapping {
Tonemapping::None => MeshPipelineKey::TONEMAP_METHOD_NONE,
Tonemapping::Reinhard => MeshPipelineKey::TONEMAP_METHOD_REINHARD,
Tonemapping::ReinhardLuminance => MeshPipelineKey::TONEMAP_METHOD_REINHARD_LUMINANCE,
Tonemapping::AcesFitted => MeshPipelineKey::TONEMAP_METHOD_ACES_FITTED,
Tonemapping::AgX => MeshPipelineKey::TONEMAP_METHOD_AGX,
Tonemapping::SomewhatBoringDisplayTransform => {
MeshPipelineKey::TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM
}
Tonemapping::TonyMcMapface => MeshPipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE,
Tonemapping::BlenderFilmic => MeshPipelineKey::TONEMAP_METHOD_BLENDER_FILMIC,
}
}
fn mark_meshes_as_changed_if_their_materials_changed<M>(
mut changed_meshes_query: Query<
&mut Mesh3d,
Or<(Changed<MeshMaterial3d<M>>, AssetChanged<MeshMaterial3d<M>>)>,
>,
) where
M: Material,
{
for mut mesh in &mut changed_meshes_query {
mesh.set_changed();
}
}
fn extract_mesh_materials<M: Material>(
mut material_instances: ResMut<RenderMaterialInstances>,
changed_meshes_query: Extract<
Query<
(Entity, &ViewVisibility, &MeshMaterial3d<M>),
Or<(Changed<ViewVisibility>, Changed<MeshMaterial3d<M>>)>,
>,
>,
) {
let last_change_tick = material_instances.current_change_tick;
for (entity, view_visibility, material) in &changed_meshes_query {
if view_visibility.get() {
material_instances.instances.insert(
entity.into(),
RenderMaterialInstance {
asset_id: material.id().untyped(),
last_change_tick,
},
);
} else {
material_instances
.instances
.remove(&MainEntity::from(entity));
}
}
}
fn early_sweep_material_instances<M>(
mut material_instances: ResMut<RenderMaterialInstances>,
mut removed_materials_query: Extract<RemovedComponents<MeshMaterial3d<M>>>,
) where
M: Material,
{
let last_change_tick = material_instances.current_change_tick;
for entity in removed_materials_query.read() {
if let Entry::Occupied(occupied_entry) = material_instances.instances.entry(entity.into()) {
if occupied_entry.get().last_change_tick != last_change_tick {
occupied_entry.remove();
}
}
}
}
pub fn late_sweep_material_instances(
mut material_instances: ResMut<RenderMaterialInstances>,
mut removed_meshes_query: Extract<RemovedComponents<Mesh3d>>,
) {
let last_change_tick = material_instances.current_change_tick;
for entity in removed_meshes_query.read() {
if let Entry::Occupied(occupied_entry) = material_instances.instances.entry(entity.into()) {
if occupied_entry.get().last_change_tick != last_change_tick {
occupied_entry.remove();
}
}
}
material_instances
.current_change_tick
.set(last_change_tick.get() + 1);
}
pub fn extract_entities_needs_specialization<M>(
entities_needing_specialization: Extract<Res<EntitiesNeedingSpecialization<M>>>,
mut entity_specialization_ticks: ResMut<EntitySpecializationTicks>,
render_material_instances: Res<RenderMaterialInstances>,
ticks: SystemChangeTick,
) where
M: Material,
{
for entity in entities_needing_specialization.iter() {
entity_specialization_ticks.insert(
(*entity).into(),
EntitySpecializationTickPair {
system_tick: ticks.this_run(),
material_instances_tick: render_material_instances.current_change_tick,
},
);
}
}
pub fn sweep_entities_needing_specialization<M>(
mut entity_specialization_ticks: ResMut<EntitySpecializationTicks>,
mut removed_mesh_material_components: Extract<RemovedComponents<MeshMaterial3d<M>>>,
mut specialized_material_pipeline_cache: ResMut<SpecializedMaterialPipelineCache>,
mut specialized_prepass_material_pipeline_cache: Option<
ResMut<SpecializedPrepassMaterialPipelineCache>,
>,
mut specialized_shadow_material_pipeline_cache: Option<
ResMut<SpecializedShadowMaterialPipelineCache>,
>,
render_material_instances: Res<RenderMaterialInstances>,
views: Query<&ExtractedView>,
) where
M: Material,
{
for entity in removed_mesh_material_components.read() {
if entity_specialization_ticks
.get(&MainEntity::from(entity))
.is_some_and(|ticks| {
ticks.material_instances_tick == render_material_instances.current_change_tick
})
{
continue;
}
entity_specialization_ticks.remove(&MainEntity::from(entity));
for view in views {
if let Some(cache) =
specialized_material_pipeline_cache.get_mut(&view.retained_view_entity)
{
cache.remove(&MainEntity::from(entity));
}
if let Some(cache) = specialized_prepass_material_pipeline_cache
.as_mut()
.and_then(|c| c.get_mut(&view.retained_view_entity))
{
cache.remove(&MainEntity::from(entity));
}
if let Some(cache) = specialized_shadow_material_pipeline_cache
.as_mut()
.and_then(|c| c.get_mut(&view.retained_view_entity))
{
cache.remove(&MainEntity::from(entity));
}
}
}
}
#[derive(Resource, Deref, DerefMut, Clone, Debug)]
pub struct EntitiesNeedingSpecialization<M> {
#[deref]
pub entities: Vec<Entity>,
_marker: PhantomData<M>,
}
impl<M> Default for EntitiesNeedingSpecialization<M> {
fn default() -> Self {
Self {
entities: Default::default(),
_marker: Default::default(),
}
}
}
#[derive(Resource, Deref, DerefMut, Default, Clone, Debug)]
pub struct EntitySpecializationTicks {
#[deref]
pub entities: MainEntityHashMap<EntitySpecializationTickPair>,
}
#[derive(Clone, Copy, Debug)]
pub struct EntitySpecializationTickPair {
pub system_tick: Tick,
pub material_instances_tick: Tick,
}
#[derive(Resource, Deref, DerefMut, Default)]
pub struct SpecializedMaterialPipelineCache {
#[deref]
map: HashMap<RetainedViewEntity, SpecializedMaterialViewPipelineCache>,
}
#[derive(Deref, DerefMut, Default)]
pub struct SpecializedMaterialViewPipelineCache {
#[deref]
map: MainEntityHashMap<(Tick, CachedRenderPipelineId)>,
}
pub fn check_entities_needing_specialization<M>(
needs_specialization: Query<
Entity,
(
Or<(
Changed<Mesh3d>,
AssetChanged<Mesh3d>,
Changed<MeshMaterial3d<M>>,
AssetChanged<MeshMaterial3d<M>>,
)>,
With<MeshMaterial3d<M>>,
),
>,
mut par_local: Local<Parallel<Vec<Entity>>>,
mut entities_needing_specialization: ResMut<EntitiesNeedingSpecialization<M>>,
) where
M: Material,
{
entities_needing_specialization.clear();
needs_specialization
.par_iter()
.for_each(|entity| par_local.borrow_local_mut().push(entity));
par_local.drain_into(&mut entities_needing_specialization);
}
pub(crate) struct SpecializationWorkItem {
visible_entity: MainEntity,
retained_view_entity: RetainedViewEntity,
mesh_key: MeshPipelineKey,
layout: MeshVertexBufferLayoutRef,
properties: Arc<MaterialProperties>,
material_type_id: TypeId,
}
#[derive(SystemParam)]
pub(crate) struct SpecializeMaterialMeshesSystemParam<'w, 's> {
render_meshes: Res<'w, RenderAssets<RenderMesh>>,
render_materials: Res<'w, ErasedRenderAssets<PreparedMaterial>>,
render_mesh_instances: Res<'w, RenderMeshInstances>,
render_material_instances: Res<'w, RenderMaterialInstances>,
render_lightmaps: Res<'w, RenderLightmaps>,
render_visibility_ranges: Res<'w, RenderVisibilityRanges>,
opaque_render_phases: Res<'w, ViewBinnedRenderPhases<Opaque3d>>,
alpha_mask_render_phases: Res<'w, ViewBinnedRenderPhases<AlphaMask3d>>,
transmissive_render_phases: Res<'w, ViewSortedRenderPhases<Transmissive3d>>,
transparent_render_phases: Res<'w, ViewSortedRenderPhases<Transparent3d>>,
views: Query<'w, 's, (&'static ExtractedView, &'static RenderVisibleEntities)>,
view_key_cache: Res<'w, ViewKeyCache>,
entity_specialization_ticks: Res<'w, EntitySpecializationTicks>,
view_specialization_ticks: Res<'w, ViewSpecializationTicks>,
specialized_material_pipeline_cache: Res<'w, SpecializedMaterialPipelineCache>,
this_run: SystemChangeTick,
}
pub(crate) fn specialize_material_meshes(
world: &mut World,
state: &mut SystemState<SpecializeMaterialMeshesSystemParam>,
mut work_items: Local<Vec<SpecializationWorkItem>>,
mut all_views: Local<HashSet<RetainedViewEntity, FixedHasher>>,
) {
work_items.clear();
all_views.clear();
let this_run;
{
let SpecializeMaterialMeshesSystemParam {
render_meshes,
render_materials,
render_mesh_instances,
render_material_instances,
render_lightmaps,
render_visibility_ranges,
opaque_render_phases,
alpha_mask_render_phases,
transmissive_render_phases,
transparent_render_phases,
views,
view_key_cache,
entity_specialization_ticks,
view_specialization_ticks,
specialized_material_pipeline_cache,
this_run: system_change_tick,
} = state.get(world);
this_run = system_change_tick.this_run();
for (view, visible_entities) in &views {
all_views.insert(view.retained_view_entity);
if !transparent_render_phases.contains_key(&view.retained_view_entity)
&& !opaque_render_phases.contains_key(&view.retained_view_entity)
&& !alpha_mask_render_phases.contains_key(&view.retained_view_entity)
&& !transmissive_render_phases.contains_key(&view.retained_view_entity)
{
continue;
}
let Some(view_key) = view_key_cache.get(&view.retained_view_entity) else {
continue;
};
let view_tick = view_specialization_ticks
.get(&view.retained_view_entity)
.unwrap();
let view_specialized_material_pipeline_cache =
specialized_material_pipeline_cache.get(&view.retained_view_entity);
for (_, visible_entity) in visible_entities.iter::<Mesh3d>() {
let Some(material_instance) =
render_material_instances.instances.get(visible_entity)
else {
continue;
};
let Some(mesh_instance) =
render_mesh_instances.render_mesh_queue_data(*visible_entity)
else {
continue;
};
let entity_tick = entity_specialization_ticks
.get(visible_entity)
.unwrap()
.system_tick;
let last_specialized_tick = view_specialized_material_pipeline_cache
.and_then(|cache| cache.get(visible_entity))
.map(|(tick, _)| *tick);
let needs_specialization = last_specialized_tick.is_none_or(|tick| {
view_tick.is_newer_than(tick, this_run)
|| entity_tick.is_newer_than(tick, this_run)
});
if !needs_specialization {
continue;
}
let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else {
continue;
};
let Some(material) = render_materials.get(material_instance.asset_id) else {
continue;
};
let mut mesh_pipeline_key_bits: MeshPipelineKey =
material.properties.mesh_pipeline_key_bits.downcast();
mesh_pipeline_key_bits.insert(alpha_mode_pipeline_key(
material.properties.alpha_mode,
&Msaa::from_samples(view_key.msaa_samples()),
));
let mut mesh_key = *view_key
| MeshPipelineKey::from_bits_retain(mesh.key_bits.bits())
| mesh_pipeline_key_bits;
if let Some(lightmap) = render_lightmaps.render_lightmaps.get(visible_entity) {
mesh_key |= MeshPipelineKey::LIGHTMAPPED;
if lightmap.bicubic_sampling {
mesh_key |= MeshPipelineKey::LIGHTMAP_BICUBIC_SAMPLING;
}
}
if render_visibility_ranges
.entity_has_crossfading_visibility_ranges(*visible_entity)
{
mesh_key |= MeshPipelineKey::VISIBILITY_RANGE_DITHER;
}
if view_key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) {
if mesh_instance
.flags
.contains(RenderMeshInstanceFlags::HAS_PREVIOUS_SKIN)
{
mesh_key |= MeshPipelineKey::HAS_PREVIOUS_SKIN;
}
if mesh_instance
.flags
.contains(RenderMeshInstanceFlags::HAS_PREVIOUS_MORPH)
{
mesh_key |= MeshPipelineKey::HAS_PREVIOUS_MORPH;
}
}
work_items.push(SpecializationWorkItem {
visible_entity: *visible_entity,
retained_view_entity: view.retained_view_entity,
mesh_key,
layout: mesh.layout.clone(),
properties: material.properties.clone(),
material_type_id: material_instance.asset_id.type_id(),
});
}
}
}
for item in work_items.drain(..) {
let key = ErasedMaterialPipelineKey {
type_id: item.material_type_id,
mesh_key: ErasedMeshPipelineKey::new(item.mesh_key),
material_key: item.properties.material_key.clone(),
};
let Some(base_specialize) = item.properties.base_specialize else {
continue;
};
match base_specialize(world, key, &item.layout, &item.properties) {
Ok(pipeline_id) => {
world
.resource_mut::<SpecializedMaterialPipelineCache>()
.entry(item.retained_view_entity)
.or_default()
.insert(item.visible_entity, (this_run, pipeline_id));
}
Err(err) => error!("{}", err),
}
}
world
.resource_mut::<SpecializedMaterialPipelineCache>()
.retain(|view, _| all_views.contains(view));
}
pub fn queue_material_meshes(
render_materials: Res<ErasedRenderAssets<PreparedMaterial>>,
render_mesh_instances: Res<RenderMeshInstances>,
render_material_instances: Res<RenderMaterialInstances>,
mesh_allocator: Res<MeshAllocator>,
gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
mut opaque_render_phases: ResMut<ViewBinnedRenderPhases<Opaque3d>>,
mut alpha_mask_render_phases: ResMut<ViewBinnedRenderPhases<AlphaMask3d>>,
mut transmissive_render_phases: ResMut<ViewSortedRenderPhases<Transmissive3d>>,
mut transparent_render_phases: ResMut<ViewSortedRenderPhases<Transparent3d>>,
views: Query<(&ExtractedView, &RenderVisibleEntities)>,
specialized_material_pipeline_cache: ResMut<SpecializedMaterialPipelineCache>,
) {
for (view, visible_entities) in &views {
let (
Some(opaque_phase),
Some(alpha_mask_phase),
Some(transmissive_phase),
Some(transparent_phase),
) = (
opaque_render_phases.get_mut(&view.retained_view_entity),
alpha_mask_render_phases.get_mut(&view.retained_view_entity),
transmissive_render_phases.get_mut(&view.retained_view_entity),
transparent_render_phases.get_mut(&view.retained_view_entity),
)
else {
continue;
};
let Some(view_specialized_material_pipeline_cache) =
specialized_material_pipeline_cache.get(&view.retained_view_entity)
else {
continue;
};
let rangefinder = view.rangefinder3d();
for (render_entity, visible_entity) in visible_entities.iter::<Mesh3d>() {
let Some((current_change_tick, pipeline_id)) = view_specialized_material_pipeline_cache
.get(visible_entity)
.map(|(current_change_tick, pipeline_id)| (*current_change_tick, *pipeline_id))
else {
continue;
};
if opaque_phase.validate_cached_entity(*visible_entity, current_change_tick)
|| alpha_mask_phase.validate_cached_entity(*visible_entity, current_change_tick)
{
continue;
}
let Some(material_instance) = render_material_instances.instances.get(visible_entity)
else {
continue;
};
let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity)
else {
continue;
};
let Some(material) = render_materials.get(material_instance.asset_id) else {
continue;
};
let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
match material.properties.render_phase_type {
RenderPhaseType::Transmissive => {
let distance = rangefinder.distance(&mesh_instance.center)
+ material.properties.depth_bias;
let Some(draw_function) = material
.properties
.get_draw_function(MainPassTransmissiveDrawFunction)
else {
continue;
};
transmissive_phase.add(Transmissive3d {
entity: (*render_entity, *visible_entity),
draw_function,
pipeline: pipeline_id,
distance,
batch_range: 0..1,
extra_index: PhaseItemExtraIndex::None,
indexed: index_slab.is_some(),
});
}
RenderPhaseType::Opaque => {
if material.properties.render_method == OpaqueRendererMethod::Deferred {
opaque_phase.update_cache(*visible_entity, None, current_change_tick);
continue;
}
let Some(draw_function) = material
.properties
.get_draw_function(MainPassOpaqueDrawFunction)
else {
continue;
};
let batch_set_key = Opaque3dBatchSetKey {
pipeline: pipeline_id,
draw_function,
material_bind_group_index: Some(material.binding.group.0),
vertex_slab: vertex_slab.unwrap_or_default(),
index_slab,
lightmap_slab: mesh_instance.shared.lightmap_slab_index.map(|index| *index),
};
let bin_key = Opaque3dBinKey {
asset_id: mesh_instance.mesh_asset_id.into(),
};
opaque_phase.add(
batch_set_key,
bin_key,
(*render_entity, *visible_entity),
mesh_instance.current_uniform_index,
BinnedRenderPhaseType::mesh(
mesh_instance.should_batch(),
&gpu_preprocessing_support,
),
current_change_tick,
);
}
RenderPhaseType::AlphaMask => {
let Some(draw_function) = material
.properties
.get_draw_function(MainPassAlphaMaskDrawFunction)
else {
continue;
};
let batch_set_key = OpaqueNoLightmap3dBatchSetKey {
draw_function,
pipeline: pipeline_id,
material_bind_group_index: Some(material.binding.group.0),
vertex_slab: vertex_slab.unwrap_or_default(),
index_slab,
};
let bin_key = OpaqueNoLightmap3dBinKey {
asset_id: mesh_instance.mesh_asset_id.into(),
};
alpha_mask_phase.add(
batch_set_key,
bin_key,
(*render_entity, *visible_entity),
mesh_instance.current_uniform_index,
BinnedRenderPhaseType::mesh(
mesh_instance.should_batch(),
&gpu_preprocessing_support,
),
current_change_tick,
);
}
RenderPhaseType::Transparent => {
let distance = rangefinder.distance(&mesh_instance.center)
+ material.properties.depth_bias;
let Some(draw_function) = material
.properties
.get_draw_function(MainPassTransparentDrawFunction)
else {
continue;
};
transparent_phase.add(Transparent3d {
entity: (*render_entity, *visible_entity),
draw_function,
pipeline: pipeline_id,
distance,
batch_range: 0..1,
extra_index: PhaseItemExtraIndex::None,
indexed: index_slab.is_some(),
});
}
}
}
}
}
#[derive(Default, Resource, Clone, Debug, ExtractResource, Reflect)]
#[reflect(Resource, Default, Debug, Clone)]
pub struct DefaultOpaqueRendererMethod(OpaqueRendererMethod);
impl DefaultOpaqueRendererMethod {
pub fn forward() -> Self {
DefaultOpaqueRendererMethod(OpaqueRendererMethod::Forward)
}
pub fn deferred() -> Self {
DefaultOpaqueRendererMethod(OpaqueRendererMethod::Deferred)
}
pub fn set_to_forward(&mut self) {
self.0 = OpaqueRendererMethod::Forward;
}
pub fn set_to_deferred(&mut self) {
self.0 = OpaqueRendererMethod::Deferred;
}
}
#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
pub struct MaterialVertexShader;
#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
pub struct MaterialFragmentShader;
#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
pub struct PrepassVertexShader;
#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
pub struct PrepassFragmentShader;
#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
pub struct DeferredVertexShader;
#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
pub struct DeferredFragmentShader;
#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
pub struct MeshletFragmentShader;
#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
pub struct MeshletPrepassFragmentShader;
#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
pub struct MeshletDeferredFragmentShader;
#[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
pub struct MainPassOpaqueDrawFunction;
#[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
pub struct MainPassAlphaMaskDrawFunction;
#[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
pub struct MainPassTransmissiveDrawFunction;
#[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
pub struct MainPassTransparentDrawFunction;
#[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
pub struct PrepassOpaqueDrawFunction;
#[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
pub struct PrepassAlphaMaskDrawFunction;
#[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
pub struct PrepassOpaqueDepthOnlyDrawFunction;
#[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
pub struct DeferredOpaqueDrawFunction;
#[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
pub struct DeferredAlphaMaskDrawFunction;
#[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
pub struct ShadowsDrawFunction;
#[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
pub struct ShadowsDepthOnlyDrawFunction;
#[derive(Resource, Default, Deref, DerefMut)]
pub struct RenderMaterialBindings(HashMap<UntypedAssetId, MaterialBindingId>);
pub struct PreparedMaterial {
pub binding: MaterialBindingId,
pub properties: Arc<MaterialProperties>,
}
pub fn base_specialize(
world: &mut World,
key: ErasedMaterialPipelineKey,
layout: &MeshVertexBufferLayoutRef,
properties: &Arc<MaterialProperties>,
) -> Result<CachedRenderPipelineId, SpecializedMeshPipelineError> {
world.resource_scope(
|world, mut pipelines: Mut<SpecializedMeshPipelines<MaterialPipelineSpecializer>>| {
let mesh_pipeline = world.resource::<MeshPipeline>().clone();
let pipeline_cache = world.resource::<PipelineCache>();
let specializer = MaterialPipelineSpecializer {
pipeline: MaterialPipeline { mesh_pipeline },
properties: properties.clone(),
};
pipelines.specialize(pipeline_cache, &specializer, key, layout)
},
)
}
fn prepass_specialize(
world: &mut World,
key: ErasedMaterialPipelineKey,
layout: &MeshVertexBufferLayoutRef,
properties: &Arc<MaterialProperties>,
) -> Result<CachedRenderPipelineId, SpecializedMeshPipelineError> {
world.resource_scope(
|world, mut pipelines: Mut<SpecializedMeshPipelines<PrepassPipelineSpecializer>>| {
let prepass_pipeline = world.resource::<PrepassPipeline>().clone();
let pipeline_cache = world.resource::<PipelineCache>();
let specializer = PrepassPipelineSpecializer {
pipeline: prepass_pipeline,
properties: properties.clone(),
};
pipelines.specialize(pipeline_cache, &specializer, key, layout)
},
)
}
fn user_specialize<M: Material>(
pipeline: &dyn Any,
descriptor: &mut RenderPipelineDescriptor,
mesh_layout: &MeshVertexBufferLayoutRef,
erased_key: ErasedMaterialPipelineKey,
) -> Result<(), SpecializedMeshPipelineError>
where
M::Data: Hash + Clone,
{
let pipeline = pipeline.downcast_ref::<MaterialPipeline>().unwrap();
let material_key = erased_key.material_key.to_key();
let mesh_key: MeshPipelineKey = erased_key.mesh_key.downcast();
M::specialize(
pipeline,
descriptor,
mesh_layout,
MaterialPipelineKey {
mesh_key,
bind_group_data: material_key,
},
)
}
impl<M: Material> ErasedRenderAsset for MeshMaterial3d<M>
where
M::Data: PartialEq + Eq + Hash + Clone,
{
type SourceAsset = M;
type ErasedAsset = PreparedMaterial;
type Param = (
SRes<RenderDevice>,
SRes<PipelineCache>,
SRes<DefaultOpaqueRendererMethod>,
SResMut<MaterialBindGroupAllocators>,
SResMut<RenderMaterialBindings>,
SRes<DrawFunctions<Opaque3d>>,
SRes<DrawFunctions<AlphaMask3d>>,
SRes<DrawFunctions<Transmissive3d>>,
SRes<DrawFunctions<Transparent3d>>,
SRes<DrawFunctions<Opaque3dPrepass>>,
SRes<DrawFunctions<AlphaMask3dPrepass>>,
SRes<DrawFunctions<Opaque3dDeferred>>,
SRes<DrawFunctions<AlphaMask3dDeferred>>,
SRes<DrawFunctions<Shadow>>,
SRes<AssetServer>,
M::Param,
);
fn prepare_asset(
material: Self::SourceAsset,
material_id: AssetId<Self::SourceAsset>,
(
render_device,
pipeline_cache,
default_opaque_render_method,
bind_group_allocators,
render_material_bindings,
opaque_draw_functions,
alpha_mask_draw_functions,
transmissive_draw_functions,
transparent_draw_functions,
opaque_prepass_draw_functions,
alpha_mask_prepass_draw_functions,
opaque_deferred_draw_functions,
alpha_mask_deferred_draw_functions,
shadow_draw_functions,
asset_server,
material_param,
): &mut SystemParamItem<Self::Param>,
) -> Result<Self::ErasedAsset, PrepareAssetError<Self::SourceAsset>> {
let material_layout = M::bind_group_layout_descriptor(render_device);
let actual_material_layout = pipeline_cache.get_bind_group_layout(&material_layout);
let binding = match material.unprepared_bind_group(
&actual_material_layout,
render_device,
material_param,
false,
) {
Ok(unprepared) => {
let bind_group_allocator =
bind_group_allocators.get_mut(&TypeId::of::<M>()).unwrap();
match render_material_bindings.entry(material_id.into()) {
Entry::Occupied(mut occupied_entry) => {
bind_group_allocator.free(*occupied_entry.get());
let new_binding =
bind_group_allocator.allocate_unprepared(unprepared, &material_layout);
*occupied_entry.get_mut() = new_binding;
new_binding
}
Entry::Vacant(vacant_entry) => *vacant_entry.insert(
bind_group_allocator.allocate_unprepared(unprepared, &material_layout),
),
}
}
Err(AsBindGroupError::RetryNextUpdate) => {
return Err(PrepareAssetError::RetryNextUpdate(material))
}
Err(AsBindGroupError::CreateBindGroupDirectly) => {
match material.as_bind_group(
&material_layout,
render_device,
pipeline_cache,
material_param,
) {
Ok(prepared_bind_group) => {
let bind_group_allocator =
bind_group_allocators.get_mut(&TypeId::of::<M>()).unwrap();
let material_binding_id =
bind_group_allocator.allocate_prepared(prepared_bind_group);
render_material_bindings.insert(material_id.into(), material_binding_id);
material_binding_id
}
Err(AsBindGroupError::RetryNextUpdate) => {
return Err(PrepareAssetError::RetryNextUpdate(material))
}
Err(other) => return Err(PrepareAssetError::AsBindGroupError(other)),
}
}
Err(other) => return Err(PrepareAssetError::AsBindGroupError(other)),
};
let shadows_enabled = M::enable_shadows();
let prepass_enabled = M::enable_prepass();
let draw_opaque_pbr = opaque_draw_functions.read().id::<DrawMaterial>();
let draw_alpha_mask_pbr = alpha_mask_draw_functions.read().id::<DrawMaterial>();
let draw_transmissive_pbr = transmissive_draw_functions.read().id::<DrawMaterial>();
let draw_transparent_pbr = transparent_draw_functions.read().id::<DrawMaterial>();
let draw_opaque_prepass = opaque_prepass_draw_functions.read().id::<DrawPrepass>();
let draw_alpha_mask_prepass = alpha_mask_prepass_draw_functions.read().id::<DrawPrepass>();
let draw_opaque_prepass_depth_only = opaque_prepass_draw_functions
.read()
.id::<DrawDepthOnlyPrepass>();
let draw_opaque_deferred = opaque_deferred_draw_functions.read().id::<DrawPrepass>();
let draw_alpha_mask_deferred = alpha_mask_deferred_draw_functions
.read()
.id::<DrawPrepass>();
let draw_shadows = shadow_draw_functions.read().id::<DrawPrepass>();
let draw_shadows_depth_only = shadow_draw_functions.read().id::<DrawDepthOnlyPrepass>();
let draw_functions = SmallVec::from_iter([
(MainPassOpaqueDrawFunction.intern(), draw_opaque_pbr),
(MainPassAlphaMaskDrawFunction.intern(), draw_alpha_mask_pbr),
(
MainPassTransmissiveDrawFunction.intern(),
draw_transmissive_pbr,
),
(
MainPassTransparentDrawFunction.intern(),
draw_transparent_pbr,
),
(PrepassOpaqueDrawFunction.intern(), draw_opaque_prepass),
(
PrepassAlphaMaskDrawFunction.intern(),
draw_alpha_mask_prepass,
),
(
PrepassOpaqueDepthOnlyDrawFunction.intern(),
draw_opaque_prepass_depth_only,
),
(DeferredOpaqueDrawFunction.intern(), draw_opaque_deferred),
(
DeferredAlphaMaskDrawFunction.intern(),
draw_alpha_mask_deferred,
),
(ShadowsDrawFunction.intern(), draw_shadows),
(
ShadowsDepthOnlyDrawFunction.intern(),
draw_shadows_depth_only,
),
]);
let render_method = match material.opaque_render_method() {
OpaqueRendererMethod::Forward => OpaqueRendererMethod::Forward,
OpaqueRendererMethod::Deferred => OpaqueRendererMethod::Deferred,
OpaqueRendererMethod::Auto => default_opaque_render_method.0,
};
let mut mesh_pipeline_key_bits = MeshPipelineKey::empty();
mesh_pipeline_key_bits.set(
MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE,
material.reads_view_transmission_texture(),
);
let reads_view_transmission_texture =
mesh_pipeline_key_bits.contains(MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE);
let mesh_pipeline_key_bits = ErasedMeshPipelineKey::new(mesh_pipeline_key_bits);
let render_phase_type = match material.alpha_mode() {
AlphaMode::Blend | AlphaMode::Premultiplied | AlphaMode::Add | AlphaMode::Multiply => {
RenderPhaseType::Transparent
}
_ if reads_view_transmission_texture => RenderPhaseType::Transmissive,
AlphaMode::Opaque | AlphaMode::AlphaToCoverage => RenderPhaseType::Opaque,
AlphaMode::Mask(_) => RenderPhaseType::AlphaMask,
};
let mut shaders = SmallVec::new();
let mut add_shader = |label: InternedShaderLabel, shader_ref: ShaderRef| {
let mayber_shader = match shader_ref {
ShaderRef::Default => None,
ShaderRef::Handle(handle) => Some(handle),
ShaderRef::Path(path) => Some(asset_server.load(path)),
};
if let Some(shader) = mayber_shader {
shaders.push((label, shader));
}
};
add_shader(MaterialVertexShader.intern(), M::vertex_shader());
add_shader(MaterialFragmentShader.intern(), M::fragment_shader());
add_shader(PrepassVertexShader.intern(), M::prepass_vertex_shader());
add_shader(PrepassFragmentShader.intern(), M::prepass_fragment_shader());
add_shader(DeferredVertexShader.intern(), M::deferred_vertex_shader());
add_shader(
DeferredFragmentShader.intern(),
M::deferred_fragment_shader(),
);
#[cfg(feature = "meshlet")]
{
add_shader(
MeshletFragmentShader.intern(),
M::meshlet_mesh_fragment_shader(),
);
add_shader(
MeshletPrepassFragmentShader.intern(),
M::meshlet_mesh_prepass_fragment_shader(),
);
add_shader(
MeshletDeferredFragmentShader.intern(),
M::meshlet_mesh_deferred_fragment_shader(),
);
}
let bindless = material_uses_bindless_resources::<M>(render_device);
let bind_group_data = material.bind_group_data();
let material_key = ErasedMaterialKey::new(bind_group_data);
Ok(PreparedMaterial {
binding,
properties: Arc::new(MaterialProperties {
alpha_mode: material.alpha_mode(),
depth_bias: material.depth_bias(),
reads_view_transmission_texture,
render_phase_type,
render_method,
mesh_pipeline_key_bits,
material_layout: Some(material_layout),
draw_functions,
shaders,
bindless,
base_specialize: Some(base_specialize),
prepass_specialize: Some(prepass_specialize),
user_specialize: Some(user_specialize::<M>),
material_key,
shadows_enabled,
prepass_enabled,
}),
})
}
fn unload_asset(
source_asset: AssetId<Self::SourceAsset>,
(_, _, _, bind_group_allocators, render_material_bindings, ..): &mut SystemParamItem<
Self::Param,
>,
) {
let Some(material_binding_id) = render_material_bindings.remove(&source_asset.untyped())
else {
return;
};
let bind_group_allactor = bind_group_allocators.get_mut(&TypeId::of::<M>()).unwrap();
bind_group_allactor.free(material_binding_id);
}
}
pub fn prepare_material_bind_groups(
mut allocators: ResMut<MaterialBindGroupAllocators>,
render_device: Res<RenderDevice>,
pipeline_cache: Res<PipelineCache>,
fallback_image: Res<FallbackImage>,
fallback_resources: Res<FallbackBindlessResources>,
) {
for (_, allocator) in allocators.iter_mut() {
allocator.prepare_bind_groups(
&render_device,
&pipeline_cache,
&fallback_resources,
&fallback_image,
);
}
}
pub fn write_material_bind_group_buffers(
mut allocators: ResMut<MaterialBindGroupAllocators>,
render_device: Res<RenderDevice>,
render_queue: Res<RenderQueue>,
) {
for (_, allocator) in allocators.iter_mut() {
allocator.write_buffers(&render_device, &render_queue);
}
}