Unify RenderMaterialInstances
and RenderMeshMaterialIds
, and fix an associated race condition. (#18734)
Currently, `RenderMaterialInstances` and `RenderMeshMaterialIds` are very similar render-world resources: the former maps main world meshes to typed material asset IDs, and the latter maps main world meshes to untyped material asset IDs. This is needlessly-complex and wasteful, so this patch unifies the two in favor of a single untyped `RenderMaterialInstances` resource. This patch also fixes a subtle issue that could cause mesh materials to be incorrect if a `MeshMaterial3d<A>` was removed and replaced with a `MeshMaterial3d<B>` material in the same frame. The problematic pattern looks like: 1. `extract_mesh_materials<B>` runs and, seeing the `Changed<MeshMaterial3d<B>>` condition, adds an entry mapping the mesh to the new material to the untyped `RenderMeshMaterialIds`. 2. `extract_mesh_materials<A>` runs and, seeing that the entity is present in `RemovedComponents<MeshMaterial3d<A>>`, removes the entry from `RenderMeshMaterialIds`. 3. The material slot is now empty, and the mesh will show up as whatever material happens to be in slot 0 in the material data slab. This commit fixes the issue by splitting out `extract_mesh_materials` into *three* phases: *extraction*, *early sweeping*, and *late sweeping*, which run in that order: 1. The *extraction* system, which runs for each material, updates `RenderMaterialInstances` records whenever `MeshMaterial3d` components change, and updates a change tick so that the following system will know not to remove it. 2. The *early sweeping* system, which runs for each material, processes entities present in `RemovedComponents<MeshMaterial3d>` and removes each such entity's record from `RenderMeshInstances` only if the extraction system didn't update it this frame. This system runs after *all* extraction systems have completed, fixing the race condition. 3. The *late sweeping* system, which runs only once regardless of the number of materials in the scene, processes entities present in `RemovedComponents<ViewVisibility>` and, as in the early sweeping phase, removes each such entity's record from `RenderMeshInstances` only if the extraction system didn't update it this frame. At the end, the late sweeping system updates the change tick. Because this pattern happens relatively frequently, I think this PR should land for 0.16.
This commit is contained in:
parent
f75078676b
commit
6c619397d5
@ -306,7 +306,7 @@ where
|
||||
.init_resource::<EntitySpecializationTicks<M>>()
|
||||
.init_resource::<SpecializedMaterialPipelineCache<M>>()
|
||||
.init_resource::<DrawFunctions<Shadow>>()
|
||||
.init_resource::<RenderMaterialInstances<M>>()
|
||||
.init_resource::<RenderMaterialInstances>()
|
||||
.add_render_command::<Shadow, DrawPrepass<M>>()
|
||||
.add_render_command::<Transmissive3d, DrawMaterial<M>>()
|
||||
.add_render_command::<Transparent3d, DrawMaterial<M>>()
|
||||
@ -316,7 +316,13 @@ where
|
||||
.add_systems(
|
||||
ExtractSchedule,
|
||||
(
|
||||
extract_mesh_materials::<M>.before(ExtractMeshesSet),
|
||||
(
|
||||
extract_mesh_materials::<M>,
|
||||
early_sweep_material_instances::<M>,
|
||||
)
|
||||
.chain()
|
||||
.before(late_sweep_material_instances)
|
||||
.before(ExtractMeshesSet),
|
||||
extract_entities_needs_specialization::<M>.after(extract_cameras),
|
||||
),
|
||||
)
|
||||
@ -404,6 +410,13 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// A dummy [`AssetId`] that we use as a placeholder whenever a mesh doesn't
|
||||
/// have a material.
|
||||
///
|
||||
/// See the comments in [`RenderMaterialInstances::mesh_material`] for more
|
||||
/// information.
|
||||
static DUMMY_MESH_MATERIAL: AssetId<StandardMaterial> = AssetId::<StandardMaterial>::invalid();
|
||||
|
||||
/// A key uniquely identifying a specialized [`MaterialPipeline`].
|
||||
pub struct MaterialPipelineKey<M: Material> {
|
||||
pub mesh_key: MeshPipelineKey,
|
||||
@ -542,7 +555,7 @@ pub struct SetMaterialBindGroup<M: Material, const I: usize>(PhantomData<M>);
|
||||
impl<P: PhaseItem, M: Material, const I: usize> RenderCommand<P> for SetMaterialBindGroup<M, I> {
|
||||
type Param = (
|
||||
SRes<RenderAssets<PreparedMaterial<M>>>,
|
||||
SRes<RenderMaterialInstances<M>>,
|
||||
SRes<RenderMaterialInstances>,
|
||||
SRes<MaterialBindGroupAllocator<M>>,
|
||||
);
|
||||
type ViewQuery = ();
|
||||
@ -564,10 +577,13 @@ impl<P: PhaseItem, M: Material, const I: usize> RenderCommand<P> for SetMaterial
|
||||
let material_instances = material_instances.into_inner();
|
||||
let material_bind_group_allocator = material_bind_group_allocator.into_inner();
|
||||
|
||||
let Some(material_asset_id) = material_instances.get(&item.main_entity()) else {
|
||||
let Some(material_instance) = material_instances.instances.get(&item.main_entity()) else {
|
||||
return RenderCommandResult::Skip;
|
||||
};
|
||||
let Some(material) = materials.get(*material_asset_id) else {
|
||||
let Ok(material_asset_id) = material_instance.asset_id.try_typed::<M>() else {
|
||||
return RenderCommandResult::Skip;
|
||||
};
|
||||
let Some(material) = materials.get(material_asset_id) else {
|
||||
return RenderCommandResult::Skip;
|
||||
};
|
||||
let Some(material_bind_group) = material_bind_group_allocator.get(material.binding.group)
|
||||
@ -582,16 +598,45 @@ impl<P: PhaseItem, M: Material, const I: usize> RenderCommand<P> for SetMaterial
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores all extracted instances of a [`Material`] in the render world.
|
||||
#[derive(Resource, Deref, DerefMut)]
|
||||
pub struct RenderMaterialInstances<M: Material>(pub MainEntityHashMap<AssetId<M>>);
|
||||
/// Stores all extracted instances of all [`Material`]s in the render world.
|
||||
#[derive(Resource, Default)]
|
||||
pub struct RenderMaterialInstances {
|
||||
/// Maps from each entity in the main world to the
|
||||
/// [`RenderMaterialInstance`] associated with it.
|
||||
pub instances: MainEntityHashMap<RenderMaterialInstance>,
|
||||
/// A monotonically-increasing counter, which we use to sweep
|
||||
/// [`RenderMaterialInstances::instances`] when the entities and/or required
|
||||
/// components are removed.
|
||||
current_change_tick: Tick,
|
||||
}
|
||||
|
||||
impl<M: Material> Default for RenderMaterialInstances<M> {
|
||||
fn default() -> Self {
|
||||
Self(Default::default())
|
||||
impl RenderMaterialInstances {
|
||||
/// Returns the mesh material ID for the entity with the given mesh, or a
|
||||
/// dummy mesh material ID if the mesh has no material ID.
|
||||
///
|
||||
/// Meshes almost always have materials, but in very specific circumstances
|
||||
/// involving custom pipelines they won't. (See the
|
||||
/// `specialized_mesh_pipelines` example.)
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The material associated with a single mesh instance in the main world.
|
||||
///
|
||||
/// Note that this uses an [`UntypedAssetId`] and isn't generic over the
|
||||
/// material type, for simplicity.
|
||||
pub struct RenderMaterialInstance {
|
||||
/// The material asset.
|
||||
pub(crate) asset_id: UntypedAssetId,
|
||||
/// The [`RenderMaterialInstances::current_change_tick`] at which this
|
||||
/// material instance was last modified.
|
||||
last_change_tick: Tick,
|
||||
}
|
||||
|
||||
pub const fn alpha_mode_pipeline_key(alpha_mode: AlphaMode, msaa: &Msaa) -> MeshPipelineKey {
|
||||
match alpha_mode {
|
||||
// Premultiplied and Add share the same pipeline key
|
||||
@ -648,7 +693,7 @@ pub const fn screen_space_specular_transmission_pipeline_key(
|
||||
///
|
||||
/// As [`crate::render::mesh::collect_meshes_for_gpu_building`] only considers
|
||||
/// meshes that were newly extracted, and it writes information from the
|
||||
/// [`RenderMeshMaterialIds`] into the
|
||||
/// [`RenderMaterialInstances`] into the
|
||||
/// [`crate::render::mesh::MeshInputUniform`], we must tell
|
||||
/// [`crate::render::mesh::extract_meshes_for_gpu_building`] to re-extract a
|
||||
/// mesh if its material changed. Otherwise, the material binding information in
|
||||
@ -669,42 +714,94 @@ fn mark_meshes_as_changed_if_their_materials_changed<M>(
|
||||
}
|
||||
}
|
||||
|
||||
/// Fills the [`RenderMaterialInstances`] and [`RenderMeshMaterialIds`]
|
||||
/// resources from the meshes in the scene.
|
||||
/// Fills the [`RenderMaterialInstances`] resources from the meshes in the
|
||||
/// scene.
|
||||
fn extract_mesh_materials<M: Material>(
|
||||
mut material_instances: ResMut<RenderMaterialInstances<M>>,
|
||||
mut material_ids: ResMut<RenderMeshMaterialIds>,
|
||||
mut material_instances: ResMut<RenderMaterialInstances>,
|
||||
changed_meshes_query: Extract<
|
||||
Query<
|
||||
(Entity, &ViewVisibility, &MeshMaterial3d<M>),
|
||||
Or<(Changed<ViewVisibility>, Changed<MeshMaterial3d<M>>)>,
|
||||
>,
|
||||
>,
|
||||
mut removed_visibilities_query: Extract<RemovedComponents<ViewVisibility>>,
|
||||
mut removed_materials_query: Extract<RemovedComponents<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.insert(entity.into(), material.id());
|
||||
material_ids.insert(entity.into(), material.id().into());
|
||||
material_instances.instances.insert(
|
||||
entity.into(),
|
||||
RenderMaterialInstance {
|
||||
asset_id: material.id().untyped(),
|
||||
last_change_tick,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
material_instances.remove(&MainEntity::from(entity));
|
||||
material_ids.remove(entity.into());
|
||||
material_instances
|
||||
.instances
|
||||
.remove(&MainEntity::from(entity));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes mesh materials from [`RenderMaterialInstances`] when their
|
||||
/// [`MeshMaterial3d`] components are removed.
|
||||
///
|
||||
/// This is tricky because we have to deal with the case in which a material of
|
||||
/// type A was removed and replaced with a material of type B in the same frame
|
||||
/// (which is actually somewhat common of an operation). In this case, even
|
||||
/// though an entry will be present in `RemovedComponents<MeshMaterial3d<A>>`,
|
||||
/// we must not remove the entry in `RenderMaterialInstances` which corresponds
|
||||
/// to material B. To handle this case, we use change ticks to avoid removing
|
||||
/// the entry if it was updated this frame.
|
||||
///
|
||||
/// This is the first of two sweep phases. Because this phase runs once per
|
||||
/// material type, we need a second phase in order to guarantee that we only
|
||||
/// bump [`RenderMaterialInstances::current_change_tick`] once.
|
||||
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()) {
|
||||
// Only sweep the entry if it wasn't updated this frame.
|
||||
if occupied_entry.get().last_change_tick != last_change_tick {
|
||||
occupied_entry.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes mesh materials from [`RenderMaterialInstances`] when their
|
||||
/// [`ViewVisibility`] components are removed.
|
||||
///
|
||||
/// This runs after all invocations of [`early_sweep_material_instances`] and is
|
||||
/// responsible for bumping [`RenderMaterialInstances::current_change_tick`] in
|
||||
/// preparation for a new frame.
|
||||
fn late_sweep_material_instances(
|
||||
mut material_instances: ResMut<RenderMaterialInstances>,
|
||||
mut removed_visibilities_query: Extract<RemovedComponents<ViewVisibility>>,
|
||||
) {
|
||||
let last_change_tick = material_instances.current_change_tick;
|
||||
|
||||
for entity in removed_visibilities_query.read() {
|
||||
if let Entry::Occupied(occupied_entry) = material_instances.instances.entry(entity.into()) {
|
||||
// Only sweep the entry if it wasn't updated this frame. It's
|
||||
// possible that a `ViewVisibility` component was removed and
|
||||
// re-added in the same frame.
|
||||
if occupied_entry.get().last_change_tick != last_change_tick {
|
||||
occupied_entry.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for entity in removed_visibilities_query
|
||||
.read()
|
||||
.chain(removed_materials_query.read())
|
||||
{
|
||||
// Only queue a mesh for removal if we didn't pick it up above.
|
||||
// It's possible that a necessary component was removed and re-added in
|
||||
// the same frame.
|
||||
if !changed_meshes_query.contains(entity) {
|
||||
material_instances.remove(&MainEntity::from(entity));
|
||||
material_ids.remove(entity.into());
|
||||
}
|
||||
}
|
||||
material_instances
|
||||
.current_change_tick
|
||||
.set(last_change_tick.get() + 1);
|
||||
}
|
||||
|
||||
pub fn extract_entities_needs_specialization<M>(
|
||||
@ -852,7 +949,7 @@ pub fn specialize_material_meshes<M: Material>(
|
||||
render_meshes: Res<RenderAssets<RenderMesh>>,
|
||||
render_materials: Res<RenderAssets<PreparedMaterial<M>>>,
|
||||
render_mesh_instances: Res<RenderMeshInstances>,
|
||||
render_material_instances: Res<RenderMaterialInstances<M>>,
|
||||
render_material_instances: Res<RenderMaterialInstances>,
|
||||
render_lightmaps: Res<RenderLightmaps>,
|
||||
render_visibility_ranges: Res<RenderVisibilityRanges>,
|
||||
(
|
||||
@ -907,7 +1004,11 @@ pub fn specialize_material_meshes<M: Material>(
|
||||
.or_default();
|
||||
|
||||
for (_, visible_entity) in visible_entities.iter::<Mesh3d>() {
|
||||
let Some(material_asset_id) = render_material_instances.get(visible_entity) else {
|
||||
let Some(material_instance) = render_material_instances.instances.get(visible_entity)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Ok(material_asset_id) = material_instance.asset_id.try_typed::<M>() else {
|
||||
continue;
|
||||
};
|
||||
let entity_tick = entity_specialization_ticks.get(visible_entity).unwrap();
|
||||
@ -928,7 +1029,7 @@ pub fn specialize_material_meshes<M: Material>(
|
||||
let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else {
|
||||
continue;
|
||||
};
|
||||
let Some(material) = render_materials.get(*material_asset_id) else {
|
||||
let Some(material) = render_materials.get(material_asset_id) else {
|
||||
continue;
|
||||
};
|
||||
let Some(material_bind_group) =
|
||||
@ -1004,7 +1105,7 @@ pub fn specialize_material_meshes<M: Material>(
|
||||
pub fn queue_material_meshes<M: Material>(
|
||||
render_materials: Res<RenderAssets<PreparedMaterial<M>>>,
|
||||
render_mesh_instances: Res<RenderMeshInstances>,
|
||||
render_material_instances: Res<RenderMaterialInstances<M>>,
|
||||
render_material_instances: Res<RenderMaterialInstances>,
|
||||
mesh_allocator: Res<MeshAllocator>,
|
||||
gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
|
||||
mut opaque_render_phases: ResMut<ViewBinnedRenderPhases<Opaque3d>>,
|
||||
@ -1054,14 +1155,18 @@ pub fn queue_material_meshes<M: Material>(
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(material_asset_id) = render_material_instances.get(visible_entity) else {
|
||||
let Some(material_instance) = render_material_instances.instances.get(visible_entity)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Ok(material_asset_id) = material_instance.asset_id.try_typed::<M>() else {
|
||||
continue;
|
||||
};
|
||||
let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Some(material) = render_materials.get(*material_asset_id) else {
|
||||
let Some(material) = render_materials.get(material_asset_id) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
|
@ -2,7 +2,7 @@ use super::{meshlet_mesh_manager::MeshletMeshManager, MeshletMesh, MeshletMesh3d
|
||||
use crate::{
|
||||
Material, MaterialBindingId, MeshFlags, MeshTransforms, MeshUniform, NotShadowCaster,
|
||||
NotShadowReceiver, PreviousGlobalTransform, RenderMaterialBindings, RenderMaterialInstances,
|
||||
RenderMeshMaterialIds, StandardMaterial,
|
||||
StandardMaterial,
|
||||
};
|
||||
use bevy_asset::{AssetEvent, AssetId, AssetServer, Assets, UntypedAssetId};
|
||||
use bevy_ecs::{
|
||||
@ -90,7 +90,7 @@ impl InstanceManager {
|
||||
transform: &GlobalTransform,
|
||||
previous_transform: Option<&PreviousGlobalTransform>,
|
||||
render_layers: Option<&RenderLayers>,
|
||||
mesh_material_ids: &RenderMeshMaterialIds,
|
||||
mesh_material_ids: &RenderMaterialInstances,
|
||||
render_material_bindings: &RenderMaterialBindings,
|
||||
not_shadow_receiver: bool,
|
||||
not_shadow_caster: bool,
|
||||
@ -193,7 +193,7 @@ pub fn extract_meshlet_mesh_entities(
|
||||
mut instance_manager: ResMut<InstanceManager>,
|
||||
// TODO: Replace main_world and system_state when Extract<ResMut<Assets<MeshletMesh>>> is possible
|
||||
mut main_world: ResMut<MainWorld>,
|
||||
mesh_material_ids: Res<RenderMeshMaterialIds>,
|
||||
mesh_material_ids: Res<RenderMaterialInstances>,
|
||||
render_material_bindings: Res<RenderMaterialBindings>,
|
||||
mut system_state: Local<
|
||||
Option<
|
||||
@ -275,20 +275,22 @@ pub fn extract_meshlet_mesh_entities(
|
||||
/// and note that the material is used by at least one entity in the scene.
|
||||
pub fn queue_material_meshlet_meshes<M: Material>(
|
||||
mut instance_manager: ResMut<InstanceManager>,
|
||||
render_material_instances: Res<RenderMaterialInstances<M>>,
|
||||
render_material_instances: Res<RenderMaterialInstances>,
|
||||
) {
|
||||
let instance_manager = instance_manager.deref_mut();
|
||||
|
||||
for (i, (instance, _, _)) in instance_manager.instances.iter().enumerate() {
|
||||
if let Some(material_asset_id) = render_material_instances.get(instance) {
|
||||
if let Some(material_id) = instance_manager
|
||||
.material_id_lookup
|
||||
.get(&material_asset_id.untyped())
|
||||
{
|
||||
instance_manager
|
||||
.material_ids_present_in_scene
|
||||
.insert(*material_id);
|
||||
instance_manager.instance_material_ids.get_mut()[i] = *material_id;
|
||||
if let Some(material_instance) = render_material_instances.instances.get(instance) {
|
||||
if let Ok(material_asset_id) = material_instance.asset_id.try_typed::<M>() {
|
||||
if let Some(material_id) = instance_manager
|
||||
.material_id_lookup
|
||||
.get(&material_asset_id.untyped())
|
||||
{
|
||||
instance_manager
|
||||
.material_ids_present_in_scene
|
||||
.insert(*material_id);
|
||||
instance_manager.instance_material_ids.get_mut()[i] = *material_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ pub fn prepare_material_meshlet_meshes_main_opaque_pass<M: Material>(
|
||||
material_pipeline: Res<MaterialPipeline<M>>,
|
||||
mesh_pipeline: Res<MeshPipeline>,
|
||||
render_materials: Res<RenderAssets<PreparedMaterial<M>>>,
|
||||
render_material_instances: Res<RenderMaterialInstances<M>>,
|
||||
render_material_instances: Res<RenderMaterialInstances>,
|
||||
material_bind_group_allocator: Res<MaterialBindGroupAllocator<M>>,
|
||||
asset_server: Res<AssetServer>,
|
||||
mut mesh_vertex_buffer_layouts: ResMut<MeshVertexBufferLayouts>,
|
||||
@ -148,8 +148,13 @@ pub fn prepare_material_meshlet_meshes_main_opaque_pass<M: Material>(
|
||||
|
||||
view_key |= MeshPipelineKey::from_primitive_topology(PrimitiveTopology::TriangleList);
|
||||
|
||||
for material_id in render_material_instances.values().collect::<HashSet<_>>() {
|
||||
let Some(material) = render_materials.get(*material_id) else {
|
||||
for material_id in render_material_instances
|
||||
.instances
|
||||
.values()
|
||||
.flat_map(|instance| instance.asset_id.try_typed::<M>().ok())
|
||||
.collect::<HashSet<_>>()
|
||||
{
|
||||
let Some(material) = render_materials.get(material_id) else {
|
||||
continue;
|
||||
};
|
||||
let Some(material_bind_group) =
|
||||
@ -256,7 +261,7 @@ pub fn prepare_material_meshlet_meshes_prepass<M: Material>(
|
||||
pipeline_cache: Res<PipelineCache>,
|
||||
prepass_pipeline: Res<PrepassPipeline<M>>,
|
||||
render_materials: Res<RenderAssets<PreparedMaterial<M>>>,
|
||||
render_material_instances: Res<RenderMaterialInstances<M>>,
|
||||
render_material_instances: Res<RenderMaterialInstances>,
|
||||
mut mesh_vertex_buffer_layouts: ResMut<MeshVertexBufferLayouts>,
|
||||
material_bind_group_allocator: Res<MaterialBindGroupAllocator<M>>,
|
||||
asset_server: Res<AssetServer>,
|
||||
@ -293,8 +298,13 @@ pub fn prepare_material_meshlet_meshes_prepass<M: Material>(
|
||||
|
||||
view_key |= MeshPipelineKey::from_primitive_topology(PrimitiveTopology::TriangleList);
|
||||
|
||||
for material_id in render_material_instances.values().collect::<HashSet<_>>() {
|
||||
let Some(material) = render_materials.get(*material_id) else {
|
||||
for material_id in render_material_instances
|
||||
.instances
|
||||
.values()
|
||||
.flat_map(|instance| instance.asset_id.try_typed::<M>().ok())
|
||||
.collect::<HashSet<_>>()
|
||||
{
|
||||
let Some(material) = render_materials.get(material_id) else {
|
||||
continue;
|
||||
};
|
||||
let Some(material_bind_group) =
|
||||
|
@ -872,7 +872,7 @@ pub fn specialize_prepass_material_meshes<M>(
|
||||
render_meshes: Res<RenderAssets<RenderMesh>>,
|
||||
render_materials: Res<RenderAssets<PreparedMaterial<M>>>,
|
||||
render_mesh_instances: Res<RenderMeshInstances>,
|
||||
render_material_instances: Res<RenderMaterialInstances<M>>,
|
||||
render_material_instances: Res<RenderMaterialInstances>,
|
||||
render_lightmaps: Res<RenderLightmaps>,
|
||||
render_visibility_ranges: Res<RenderVisibilityRanges>,
|
||||
material_bind_group_allocator: Res<MaterialBindGroupAllocator<M>>,
|
||||
@ -938,7 +938,11 @@ pub fn specialize_prepass_material_meshes<M>(
|
||||
.or_default();
|
||||
|
||||
for (_, visible_entity) in visible_entities.iter::<Mesh3d>() {
|
||||
let Some(material_asset_id) = render_material_instances.get(visible_entity) else {
|
||||
let Some(material_instance) = render_material_instances.instances.get(visible_entity)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Ok(material_asset_id) = material_instance.asset_id.try_typed::<M>() else {
|
||||
continue;
|
||||
};
|
||||
let entity_tick = entity_specialization_ticks.get(visible_entity).unwrap();
|
||||
@ -956,7 +960,7 @@ pub fn specialize_prepass_material_meshes<M>(
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Some(material) = render_materials.get(*material_asset_id) else {
|
||||
let Some(material) = render_materials.get(material_asset_id) else {
|
||||
continue;
|
||||
};
|
||||
let Some(material_bind_group) =
|
||||
@ -1061,7 +1065,7 @@ pub fn specialize_prepass_material_meshes<M>(
|
||||
pub fn queue_prepass_material_meshes<M: Material>(
|
||||
render_mesh_instances: Res<RenderMeshInstances>,
|
||||
render_materials: Res<RenderAssets<PreparedMaterial<M>>>,
|
||||
render_material_instances: Res<RenderMaterialInstances<M>>,
|
||||
render_material_instances: Res<RenderMaterialInstances>,
|
||||
mesh_allocator: Res<MeshAllocator>,
|
||||
gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
|
||||
mut opaque_prepass_render_phases: ResMut<ViewBinnedRenderPhases<Opaque3dPrepass>>,
|
||||
@ -1121,14 +1125,18 @@ pub fn queue_prepass_material_meshes<M: Material>(
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(material_asset_id) = render_material_instances.get(visible_entity) else {
|
||||
let Some(material_instance) = render_material_instances.instances.get(visible_entity)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Ok(material_asset_id) = material_instance.asset_id.try_typed::<M>() else {
|
||||
continue;
|
||||
};
|
||||
let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Some(material) = render_materials.get(*material_asset_id) else {
|
||||
let Some(material) = render_materials.get(material_asset_id) else {
|
||||
continue;
|
||||
};
|
||||
let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
|
||||
|
@ -1729,7 +1729,7 @@ pub fn specialize_shadows<M: Material>(
|
||||
Res<RenderAssets<RenderMesh>>,
|
||||
Res<RenderMeshInstances>,
|
||||
Res<RenderAssets<PreparedMaterial<M>>>,
|
||||
Res<RenderMaterialInstances<M>>,
|
||||
Res<RenderMaterialInstances>,
|
||||
Res<MaterialBindGroupAllocator<M>>,
|
||||
),
|
||||
shadow_render_phases: Res<ViewBinnedRenderPhases<Shadow>>,
|
||||
@ -1809,7 +1809,12 @@ pub fn specialize_shadows<M: Material>(
|
||||
.or_default();
|
||||
|
||||
for (_, visible_entity) in visible_entities.iter().copied() {
|
||||
let Some(material_asset_id) = render_material_instances.get(&visible_entity) else {
|
||||
let Some(material_instances) =
|
||||
render_material_instances.instances.get(&visible_entity)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Ok(material_asset_id) = material_instances.asset_id.try_typed::<M>() else {
|
||||
continue;
|
||||
};
|
||||
let entity_tick = entity_specialization_ticks.get(&visible_entity).unwrap();
|
||||
@ -1823,7 +1828,7 @@ pub fn specialize_shadows<M: Material>(
|
||||
if !needs_specialization {
|
||||
continue;
|
||||
}
|
||||
let Some(material) = render_materials.get(*material_asset_id) else {
|
||||
let Some(material) = render_materials.get(material_asset_id) else {
|
||||
continue;
|
||||
};
|
||||
let Some(mesh_instance) =
|
||||
@ -1906,7 +1911,7 @@ pub fn queue_shadows<M: Material>(
|
||||
shadow_draw_functions: Res<DrawFunctions<Shadow>>,
|
||||
render_mesh_instances: Res<RenderMeshInstances>,
|
||||
render_materials: Res<RenderAssets<PreparedMaterial<M>>>,
|
||||
render_material_instances: Res<RenderMaterialInstances<M>>,
|
||||
render_material_instances: Res<RenderMaterialInstances>,
|
||||
mut shadow_render_phases: ResMut<ViewBinnedRenderPhases<Shadow>>,
|
||||
gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
|
||||
mesh_allocator: Res<MeshAllocator>,
|
||||
@ -1989,10 +1994,14 @@ pub fn queue_shadows<M: Material>(
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(material_asset_id) = render_material_instances.get(&main_entity) else {
|
||||
let Some(material_instance) = render_material_instances.instances.get(&main_entity)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Some(material) = render_materials.get(*material_asset_id) else {
|
||||
let Ok(material_asset_id) = material_instance.asset_id.try_typed::<M>() else {
|
||||
continue;
|
||||
};
|
||||
let Some(material) = render_materials.get(material_asset_id) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::material_bind_groups::{MaterialBindGroupIndex, MaterialBindGroupSlot};
|
||||
use allocator::MeshAllocator;
|
||||
use bevy_asset::{load_internal_asset, AssetId, UntypedAssetId};
|
||||
use bevy_asset::{load_internal_asset, AssetId};
|
||||
use bevy_core_pipeline::{
|
||||
core_3d::{AlphaMask3d, Opaque3d, Transmissive3d, Transparent3d, CORE_3D_DEPTH_FORMAT},
|
||||
deferred::{AlphaMask3dDeferred, Opaque3dDeferred},
|
||||
@ -193,7 +193,7 @@ impl Plugin for MeshRenderPlugin {
|
||||
.init_resource::<MorphUniforms>()
|
||||
.init_resource::<MorphIndices>()
|
||||
.init_resource::<MeshCullingDataBuffer>()
|
||||
.init_resource::<RenderMeshMaterialIds>()
|
||||
.init_resource::<RenderMaterialInstances>()
|
||||
.configure_sets(
|
||||
ExtractSchedule,
|
||||
ExtractMeshesSet.after(view::extract_visibility_ranges),
|
||||
@ -895,37 +895,6 @@ pub struct RenderMeshInstancesCpu(MainEntityHashMap<RenderMeshInstanceCpu>);
|
||||
#[derive(Default, Deref, DerefMut)]
|
||||
pub struct RenderMeshInstancesGpu(MainEntityHashMap<RenderMeshInstanceGpu>);
|
||||
|
||||
/// Maps each mesh instance to the material ID, and allocated binding ID,
|
||||
/// associated with that mesh instance.
|
||||
#[derive(Resource, Default)]
|
||||
pub struct RenderMeshMaterialIds {
|
||||
/// Maps the mesh instance to the material ID.
|
||||
mesh_to_material: MainEntityHashMap<UntypedAssetId>,
|
||||
}
|
||||
|
||||
impl RenderMeshMaterialIds {
|
||||
/// Returns the mesh material ID for the entity with the given mesh, or a
|
||||
/// dummy mesh material ID if the mesh has no material ID.
|
||||
///
|
||||
/// Meshes almost always have materials, but in very specific circumstances
|
||||
/// involving custom pipelines they won't. (See the
|
||||
/// `specialized_mesh_pipelines` example.)
|
||||
pub(crate) fn mesh_material(&self, entity: MainEntity) -> UntypedAssetId {
|
||||
self.mesh_to_material
|
||||
.get(&entity)
|
||||
.cloned()
|
||||
.unwrap_or(AssetId::<StandardMaterial>::invalid().into())
|
||||
}
|
||||
|
||||
pub(crate) fn insert(&mut self, mesh_entity: MainEntity, material_id: UntypedAssetId) {
|
||||
self.mesh_to_material.insert(mesh_entity, material_id);
|
||||
}
|
||||
|
||||
pub(crate) fn remove(&mut self, main_entity: MainEntity) {
|
||||
self.mesh_to_material.remove(&main_entity);
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderMeshInstances {
|
||||
/// Creates a new [`RenderMeshInstances`] instance.
|
||||
fn new(use_gpu_instance_buffer_builder: bool) -> RenderMeshInstances {
|
||||
@ -1128,7 +1097,7 @@ impl RenderMeshInstanceGpuBuilder {
|
||||
current_input_buffer: &mut InstanceInputUniformBuffer<MeshInputUniform>,
|
||||
previous_input_buffer: &mut InstanceInputUniformBuffer<MeshInputUniform>,
|
||||
mesh_allocator: &MeshAllocator,
|
||||
mesh_material_ids: &RenderMeshMaterialIds,
|
||||
mesh_material_ids: &RenderMaterialInstances,
|
||||
render_material_bindings: &RenderMaterialBindings,
|
||||
render_lightmaps: &RenderLightmaps,
|
||||
skin_uniforms: &SkinUniforms,
|
||||
@ -1673,7 +1642,7 @@ pub fn collect_meshes_for_gpu_building(
|
||||
mut mesh_culling_data_buffer: ResMut<MeshCullingDataBuffer>,
|
||||
mut render_mesh_instance_queues: ResMut<RenderMeshInstanceGpuQueues>,
|
||||
mesh_allocator: Res<MeshAllocator>,
|
||||
mesh_material_ids: Res<RenderMeshMaterialIds>,
|
||||
mesh_material_ids: Res<RenderMaterialInstances>,
|
||||
render_material_bindings: Res<RenderMaterialBindings>,
|
||||
render_lightmaps: Res<RenderLightmaps>,
|
||||
skin_uniforms: Res<SkinUniforms>,
|
||||
|
Loading…
Reference in New Issue
Block a user