Cold Specialization (#17567)
# Cold Specialization ## Objective An ongoing part of our quest to retain everything in the render world, cold-specialization aims to cache pipeline specialization so that pipeline IDs can be recomputed only when necessary, rather than every frame. This approach reduces redundant work in stable scenes, while still accommodating scenarios in which materials, views, or visibility might change, as well as unlocking future optimization work like retaining render bins. ## Solution Queue systems are split into a specialization system and queue system, the former of which only runs when necessary to compute a new pipeline id. Pipelines are invalidated using a combination of change detection and ECS ticks. ### The difficulty with change detection Detecting “what changed” can be tricky because pipeline specialization depends not only on the entity’s components (e.g., mesh, material, etc.) but also on which view (camera) it is rendering in. In other words, the cache key for a given pipeline id is a view entity/render entity pair. As such, it's not sufficient simply to react to change detection in order to specialize -- an entity could currently be out of view or could be rendered in the future in camera that is currently disabled or hasn't spawned yet. ### Why ticks? Ticks allow us to ensure correctness by allowing us to compare the last time a view or entity was updated compared to the cached pipeline id. This ensures that even if an entity was out of view or has never been seen in a given camera before we can still correctly determine whether it needs to be re-specialized or not. ## Testing TODO: Tested a bunch of different examples, need to test more. ## Migration Guide TODO - `AssetEvents` has been moved into the `PostUpdate` schedule. --------- Co-authored-by: Patrick Walton <pcwalton@mimiga.net>
This commit is contained in:
parent
be9b38e372
commit
2ea5e9b846
@ -32,7 +32,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use bevy_app::{Animation, App, Plugin, PostUpdate};
|
use bevy_app::{Animation, App, Plugin, PostUpdate};
|
||||||
use bevy_asset::{Asset, AssetApp, Assets};
|
use bevy_asset::{Asset, AssetApp, AssetEvents, Assets};
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
entity::{VisitEntities, VisitEntitiesMut},
|
entity::{VisitEntities, VisitEntitiesMut},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
@ -1244,7 +1244,7 @@ impl Plugin for AnimationPlugin {
|
|||||||
.add_systems(
|
.add_systems(
|
||||||
PostUpdate,
|
PostUpdate,
|
||||||
(
|
(
|
||||||
graph::thread_animation_graphs,
|
graph::thread_animation_graphs.before(AssetEvents),
|
||||||
advance_transitions,
|
advance_transitions,
|
||||||
advance_animations,
|
advance_animations,
|
||||||
// TODO: `animate_targets` can animate anything, so
|
// TODO: `animate_targets` can animate anything, so
|
||||||
|
@ -293,7 +293,7 @@ mod tests {
|
|||||||
use std::println;
|
use std::println;
|
||||||
|
|
||||||
use crate::{AssetApp, Assets};
|
use crate::{AssetApp, Assets};
|
||||||
use bevy_app::{App, AppExit, Last, Startup, TaskPoolPlugin, Update};
|
use bevy_app::{App, AppExit, PostUpdate, Startup, TaskPoolPlugin, Update};
|
||||||
use bevy_ecs::schedule::IntoSystemConfigs;
|
use bevy_ecs::schedule::IntoSystemConfigs;
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
component::Component,
|
component::Component,
|
||||||
@ -410,7 +410,7 @@ mod tests {
|
|||||||
.init_asset::<MyAsset>()
|
.init_asset::<MyAsset>()
|
||||||
.insert_resource(Counter(vec![0, 0, 0, 0]))
|
.insert_resource(Counter(vec![0, 0, 0, 0]))
|
||||||
.add_systems(Update, add_some)
|
.add_systems(Update, add_some)
|
||||||
.add_systems(Last, count_update.after(AssetEvents));
|
.add_systems(PostUpdate, count_update.after(AssetEvents));
|
||||||
|
|
||||||
// First run of the app, `add_systems(Startup…)` runs.
|
// First run of the app, `add_systems(Startup…)` runs.
|
||||||
app.update(); // run_count == 0
|
app.update(); // run_count == 0
|
||||||
@ -445,7 +445,7 @@ mod tests {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
.add_systems(Update, update_some)
|
.add_systems(Update, update_some)
|
||||||
.add_systems(Last, count_update.after(AssetEvents));
|
.add_systems(PostUpdate, count_update.after(AssetEvents));
|
||||||
|
|
||||||
// First run of the app, `add_systems(Startup…)` runs.
|
// First run of the app, `add_systems(Startup…)` runs.
|
||||||
app.update(); // run_count == 0
|
app.update(); // run_count == 0
|
||||||
|
@ -212,7 +212,7 @@ use alloc::{
|
|||||||
sync::Arc,
|
sync::Arc,
|
||||||
vec::Vec,
|
vec::Vec,
|
||||||
};
|
};
|
||||||
use bevy_app::{App, Last, Plugin, PreUpdate};
|
use bevy_app::{App, Plugin, PostUpdate, PreUpdate};
|
||||||
use bevy_ecs::prelude::Component;
|
use bevy_ecs::prelude::Component;
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
reflect::AppTypeRegistry,
|
reflect::AppTypeRegistry,
|
||||||
@ -580,7 +580,7 @@ impl AssetApp for App {
|
|||||||
.add_event::<AssetLoadFailedEvent<A>>()
|
.add_event::<AssetLoadFailedEvent<A>>()
|
||||||
.register_type::<Handle<A>>()
|
.register_type::<Handle<A>>()
|
||||||
.add_systems(
|
.add_systems(
|
||||||
Last,
|
PostUpdate,
|
||||||
Assets::<A>::asset_events
|
Assets::<A>::asset_events
|
||||||
.run_if(Assets::<A>::asset_events_condition)
|
.run_if(Assets::<A>::asset_events_condition)
|
||||||
.in_set(AssetEvents),
|
.in_set(AssetEvents),
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
use self::{irradiance_volume::IrradianceVolume, prelude::EnvironmentMapLight};
|
|
||||||
use crate::material_bind_groups::{MaterialBindGroupAllocator, MaterialBindingId};
|
use crate::material_bind_groups::{MaterialBindGroupAllocator, MaterialBindingId};
|
||||||
#[cfg(feature = "meshlet")]
|
#[cfg(feature = "meshlet")]
|
||||||
use crate::meshlet::{
|
use crate::meshlet::{
|
||||||
@ -6,20 +5,22 @@ use crate::meshlet::{
|
|||||||
InstanceManager,
|
InstanceManager,
|
||||||
};
|
};
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use bevy_asset::{Asset, AssetId, AssetServer, UntypedAssetId};
|
use bevy_asset::prelude::AssetChanged;
|
||||||
|
use bevy_asset::{Asset, AssetEvents, AssetId, AssetServer, UntypedAssetId};
|
||||||
|
use bevy_core_pipeline::deferred::{AlphaMask3dDeferred, Opaque3dDeferred};
|
||||||
|
use bevy_core_pipeline::prepass::{AlphaMask3dPrepass, Opaque3dPrepass};
|
||||||
use bevy_core_pipeline::{
|
use bevy_core_pipeline::{
|
||||||
core_3d::{
|
core_3d::{
|
||||||
AlphaMask3d, Camera3d, Opaque3d, Opaque3dBatchSetKey, Opaque3dBinKey,
|
AlphaMask3d, Opaque3d, Opaque3dBatchSetKey, Opaque3dBinKey, ScreenSpaceTransmissionQuality,
|
||||||
ScreenSpaceTransmissionQuality, Transmissive3d, Transparent3d,
|
Transmissive3d, Transparent3d,
|
||||||
},
|
},
|
||||||
oit::OrderIndependentTransparencySettings,
|
prepass::{OpaqueNoLightmap3dBatchSetKey, OpaqueNoLightmap3dBinKey},
|
||||||
prepass::{
|
tonemapping::Tonemapping,
|
||||||
DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass,
|
|
||||||
OpaqueNoLightmap3dBatchSetKey, OpaqueNoLightmap3dBinKey,
|
|
||||||
},
|
|
||||||
tonemapping::{DebandDither, Tonemapping},
|
|
||||||
};
|
};
|
||||||
use bevy_derive::{Deref, DerefMut};
|
use bevy_derive::{Deref, DerefMut};
|
||||||
|
use bevy_ecs::component::Tick;
|
||||||
|
use bevy_ecs::entity::EntityHash;
|
||||||
|
use bevy_ecs::system::SystemChangeTick;
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
system::{
|
system::{
|
||||||
@ -30,11 +31,11 @@ use bevy_ecs::{
|
|||||||
use bevy_platform_support::collections::HashMap;
|
use bevy_platform_support::collections::HashMap;
|
||||||
use bevy_reflect::std_traits::ReflectDefault;
|
use bevy_reflect::std_traits::ReflectDefault;
|
||||||
use bevy_reflect::Reflect;
|
use bevy_reflect::Reflect;
|
||||||
|
use bevy_render::mesh::mark_3d_meshes_as_changed_if_their_assets_changed;
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
batching::gpu_preprocessing::GpuPreprocessingSupport,
|
batching::gpu_preprocessing::GpuPreprocessingSupport,
|
||||||
camera::TemporalJitter,
|
|
||||||
extract_resource::ExtractResource,
|
extract_resource::ExtractResource,
|
||||||
mesh::{self, Mesh3d, MeshVertexBufferLayoutRef, RenderMesh},
|
mesh::{Mesh3d, MeshVertexBufferLayoutRef, RenderMesh},
|
||||||
render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets},
|
render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets},
|
||||||
render_phase::*,
|
render_phase::*,
|
||||||
render_resource::*,
|
render_resource::*,
|
||||||
@ -271,16 +272,29 @@ where
|
|||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.init_asset::<M>()
|
app.init_asset::<M>()
|
||||||
.register_type::<MeshMaterial3d<M>>()
|
.register_type::<MeshMaterial3d<M>>()
|
||||||
.add_plugins(RenderAssetPlugin::<PreparedMaterial<M>>::default())
|
.init_resource::<EntitiesNeedingSpecialization<M>>()
|
||||||
|
.add_plugins((RenderAssetPlugin::<PreparedMaterial<M>>::default(),))
|
||||||
.add_systems(
|
.add_systems(
|
||||||
PostUpdate,
|
PostUpdate,
|
||||||
mark_meshes_as_changed_if_their_materials_changed::<M>
|
(
|
||||||
.ambiguous_with_all()
|
mark_meshes_as_changed_if_their_materials_changed::<M>.ambiguous_with_all(),
|
||||||
.after(mesh::mark_3d_meshes_as_changed_if_their_assets_changed),
|
check_entities_needing_specialization::<M>.after(AssetEvents),
|
||||||
|
)
|
||||||
|
.after(mark_3d_meshes_as_changed_if_their_assets_changed),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if self.shadows_enabled {
|
||||||
|
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) {
|
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||||
render_app
|
render_app
|
||||||
|
.init_resource::<EntitySpecializationTicks<M>>()
|
||||||
|
.init_resource::<SpecializedMaterialPipelineCache<M>>()
|
||||||
.init_resource::<DrawFunctions<Shadow>>()
|
.init_resource::<DrawFunctions<Shadow>>()
|
||||||
.init_resource::<RenderMaterialInstances<M>>()
|
.init_resource::<RenderMaterialInstances<M>>()
|
||||||
.add_render_command::<Shadow, DrawPrepass<M>>()
|
.add_render_command::<Shadow, DrawPrepass<M>>()
|
||||||
@ -291,13 +305,22 @@ where
|
|||||||
.init_resource::<SpecializedMeshPipelines<MaterialPipeline<M>>>()
|
.init_resource::<SpecializedMeshPipelines<MaterialPipeline<M>>>()
|
||||||
.add_systems(
|
.add_systems(
|
||||||
ExtractSchedule,
|
ExtractSchedule,
|
||||||
|
(
|
||||||
extract_mesh_materials::<M>.before(ExtractMeshesSet),
|
extract_mesh_materials::<M>.before(ExtractMeshesSet),
|
||||||
|
extract_entities_needs_specialization::<M>,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
Render,
|
Render,
|
||||||
|
(
|
||||||
|
specialize_material_meshes::<M>
|
||||||
|
.in_set(RenderSet::PrepareAssets)
|
||||||
|
.after(prepare_assets::<PreparedMaterial<M>>)
|
||||||
|
.after(prepare_assets::<RenderMesh>),
|
||||||
queue_material_meshes::<M>
|
queue_material_meshes::<M>
|
||||||
.in_set(RenderSet::QueueMeshes)
|
.in_set(RenderSet::QueueMeshes)
|
||||||
.after(prepare_assets::<PreparedMaterial<M>>),
|
.after(prepare_assets::<PreparedMaterial<M>>),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
Render,
|
Render,
|
||||||
@ -307,11 +330,21 @@ where
|
|||||||
);
|
);
|
||||||
|
|
||||||
if self.shadows_enabled {
|
if self.shadows_enabled {
|
||||||
render_app.add_systems(
|
render_app
|
||||||
|
.init_resource::<LightKeyCache>()
|
||||||
|
.init_resource::<LightSpecializationTicks>()
|
||||||
|
.init_resource::<SpecializedShadowMaterialPipelineCache<M>>()
|
||||||
|
.add_systems(
|
||||||
Render,
|
Render,
|
||||||
|
(
|
||||||
|
check_views_lights_need_specialization.in_set(RenderSet::PrepareAssets),
|
||||||
|
specialize_shadows::<M>
|
||||||
|
.in_set(RenderSet::PrepareAssets)
|
||||||
|
.after(prepare_assets::<PreparedMaterial<M>>),
|
||||||
queue_shadows::<M>
|
queue_shadows::<M>
|
||||||
.in_set(RenderSet::QueueMeshes)
|
.in_set(RenderSet::QueueMeshes)
|
||||||
.after(prepare_assets::<PreparedMaterial<M>>),
|
.after(prepare_assets::<PreparedMaterial<M>>),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -653,180 +686,146 @@ fn extract_mesh_materials<M: Material>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// For each view, iterates over all the meshes visible from that view and adds
|
pub fn extract_entities_needs_specialization<M>(
|
||||||
/// them to [`BinnedRenderPhase`]s or [`SortedRenderPhase`]s as appropriate.
|
entities_needing_specialization: Extract<Res<EntitiesNeedingSpecialization<M>>>,
|
||||||
pub fn queue_material_meshes<M: Material>(
|
mut entity_specialization_ticks: ResMut<EntitySpecializationTicks<M>>,
|
||||||
(
|
ticks: SystemChangeTick,
|
||||||
opaque_draw_functions,
|
) where
|
||||||
alpha_mask_draw_functions,
|
M: Material,
|
||||||
transmissive_draw_functions,
|
{
|
||||||
transparent_draw_functions,
|
for entity in entities_needing_specialization.iter() {
|
||||||
): (
|
// Update the entity's specialization tick with this run's tick
|
||||||
Res<DrawFunctions<Opaque3d>>,
|
entity_specialization_ticks.insert((*entity).into(), ticks.this_run());
|
||||||
Res<DrawFunctions<AlphaMask3d>>,
|
}
|
||||||
Res<DrawFunctions<Transmissive3d>>,
|
}
|
||||||
Res<DrawFunctions<Transparent3d>>,
|
|
||||||
),
|
#[derive(Resource, Deref, DerefMut, Clone, Debug)]
|
||||||
material_pipeline: Res<MaterialPipeline<M>>,
|
pub struct EntitiesNeedingSpecialization<M> {
|
||||||
mut pipelines: ResMut<SpecializedMeshPipelines<MaterialPipeline<M>>>,
|
#[deref]
|
||||||
pipeline_cache: Res<PipelineCache>,
|
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, Clone, Debug)]
|
||||||
|
pub struct EntitySpecializationTicks<M> {
|
||||||
|
#[deref]
|
||||||
|
pub entities: MainEntityHashMap<Tick>,
|
||||||
|
_marker: PhantomData<M>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M> Default for EntitySpecializationTicks<M> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
entities: MainEntityHashMap::default(),
|
||||||
|
_marker: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Resource, Deref, DerefMut)]
|
||||||
|
pub struct SpecializedMaterialPipelineCache<M> {
|
||||||
|
// (view_entity, material_entity) -> (tick, pipeline_id)
|
||||||
|
#[deref]
|
||||||
|
map: HashMap<(MainEntity, MainEntity), (Tick, CachedRenderPipelineId), EntityHash>,
|
||||||
|
marker: PhantomData<M>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M> Default for SpecializedMaterialPipelineCache<M> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
map: HashMap::default(),
|
||||||
|
marker: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_entities_needing_specialization<M>(
|
||||||
|
needs_specialization: Query<
|
||||||
|
Entity,
|
||||||
|
Or<(
|
||||||
|
Changed<Mesh3d>,
|
||||||
|
AssetChanged<Mesh3d>,
|
||||||
|
Changed<MeshMaterial3d<M>>,
|
||||||
|
AssetChanged<MeshMaterial3d<M>>,
|
||||||
|
)>,
|
||||||
|
>,
|
||||||
|
mut entities_needing_specialization: ResMut<EntitiesNeedingSpecialization<M>>,
|
||||||
|
) where
|
||||||
|
M: Material,
|
||||||
|
{
|
||||||
|
entities_needing_specialization.clear();
|
||||||
|
for entity in &needs_specialization {
|
||||||
|
entities_needing_specialization.push(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn specialize_material_meshes<M: Material>(
|
||||||
render_meshes: Res<RenderAssets<RenderMesh>>,
|
render_meshes: Res<RenderAssets<RenderMesh>>,
|
||||||
render_materials: Res<RenderAssets<PreparedMaterial<M>>>,
|
render_materials: Res<RenderAssets<PreparedMaterial<M>>>,
|
||||||
render_mesh_instances: Res<RenderMeshInstances>,
|
render_mesh_instances: Res<RenderMeshInstances>,
|
||||||
render_material_instances: Res<RenderMaterialInstances<M>>,
|
render_material_instances: Res<RenderMaterialInstances<M>>,
|
||||||
render_lightmaps: Res<RenderLightmaps>,
|
render_lightmaps: Res<RenderLightmaps>,
|
||||||
render_visibility_ranges: Res<RenderVisibilityRanges>,
|
render_visibility_ranges: Res<RenderVisibilityRanges>,
|
||||||
(mesh_allocator, material_bind_group_allocator, gpu_preprocessing_support): (
|
(
|
||||||
Res<MeshAllocator>,
|
material_bind_group_allocator,
|
||||||
|
opaque_render_phases,
|
||||||
|
alpha_mask_render_phases,
|
||||||
|
transmissive_render_phases,
|
||||||
|
transparent_render_phases,
|
||||||
|
): (
|
||||||
Res<MaterialBindGroupAllocator<M>>,
|
Res<MaterialBindGroupAllocator<M>>,
|
||||||
Res<GpuPreprocessingSupport>,
|
Res<ViewBinnedRenderPhases<Opaque3d>>,
|
||||||
|
Res<ViewBinnedRenderPhases<AlphaMask3d>>,
|
||||||
|
Res<ViewSortedRenderPhases<Transmissive3d>>,
|
||||||
|
Res<ViewSortedRenderPhases<Transparent3d>>,
|
||||||
),
|
),
|
||||||
mut opaque_render_phases: ResMut<ViewBinnedRenderPhases<Opaque3d>>,
|
views: Query<(&MainEntity, &ExtractedView, &RenderVisibleEntities)>,
|
||||||
mut alpha_mask_render_phases: ResMut<ViewBinnedRenderPhases<AlphaMask3d>>,
|
view_key_cache: Res<ViewKeyCache>,
|
||||||
mut transmissive_render_phases: ResMut<ViewSortedRenderPhases<Transmissive3d>>,
|
entity_specialization_ticks: Res<EntitySpecializationTicks<M>>,
|
||||||
mut transparent_render_phases: ResMut<ViewSortedRenderPhases<Transparent3d>>,
|
view_specialization_ticks: Res<ViewSpecializationTicks>,
|
||||||
views: Query<(
|
mut specialized_material_pipeline_cache: ResMut<SpecializedMaterialPipelineCache<M>>,
|
||||||
&ExtractedView,
|
mut pipelines: ResMut<SpecializedMeshPipelines<MaterialPipeline<M>>>,
|
||||||
&RenderVisibleEntities,
|
pipeline: Res<MaterialPipeline<M>>,
|
||||||
&Msaa,
|
pipeline_cache: Res<PipelineCache>,
|
||||||
Option<&Tonemapping>,
|
ticks: SystemChangeTick,
|
||||||
Option<&DebandDither>,
|
|
||||||
Option<&ShadowFilteringMethod>,
|
|
||||||
Has<ScreenSpaceAmbientOcclusion>,
|
|
||||||
(
|
|
||||||
Has<NormalPrepass>,
|
|
||||||
Has<DepthPrepass>,
|
|
||||||
Has<MotionVectorPrepass>,
|
|
||||||
Has<DeferredPrepass>,
|
|
||||||
),
|
|
||||||
Option<&Camera3d>,
|
|
||||||
Has<TemporalJitter>,
|
|
||||||
Option<&Projection>,
|
|
||||||
Has<DistanceFog>,
|
|
||||||
(
|
|
||||||
Has<RenderViewLightProbes<EnvironmentMapLight>>,
|
|
||||||
Has<RenderViewLightProbes<IrradianceVolume>>,
|
|
||||||
),
|
|
||||||
Has<OrderIndependentTransparencySettings>,
|
|
||||||
)>,
|
|
||||||
) where
|
) where
|
||||||
M::Data: PartialEq + Eq + Hash + Clone,
|
M::Data: PartialEq + Eq + Hash + Clone,
|
||||||
{
|
{
|
||||||
for (
|
for (view_entity, view, visible_entities) in &views {
|
||||||
view,
|
if !transparent_render_phases.contains_key(&view.retained_view_entity)
|
||||||
visible_entities,
|
&& !opaque_render_phases.contains_key(&view.retained_view_entity)
|
||||||
msaa,
|
&& !alpha_mask_render_phases.contains_key(&view.retained_view_entity)
|
||||||
tonemapping,
|
&& !transmissive_render_phases.contains_key(&view.retained_view_entity)
|
||||||
dither,
|
|
||||||
shadow_filter_method,
|
|
||||||
ssao,
|
|
||||||
(normal_prepass, depth_prepass, motion_vector_prepass, deferred_prepass),
|
|
||||||
camera_3d,
|
|
||||||
temporal_jitter,
|
|
||||||
projection,
|
|
||||||
distance_fog,
|
|
||||||
(has_environment_maps, has_irradiance_volumes),
|
|
||||||
has_oit,
|
|
||||||
) in &views
|
|
||||||
{
|
{
|
||||||
let (
|
continue;
|
||||||
Some(opaque_phase),
|
}
|
||||||
Some(alpha_mask_phase),
|
|
||||||
Some(transmissive_phase),
|
let Some(view_key) = view_key_cache.get(view_entity) else {
|
||||||
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;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let draw_opaque_pbr = opaque_draw_functions.read().id::<DrawMaterial<M>>();
|
for (_, visible_entity) in visible_entities.iter::<Mesh3d>() {
|
||||||
let draw_alpha_mask_pbr = alpha_mask_draw_functions.read().id::<DrawMaterial<M>>();
|
let view_tick = view_specialization_ticks.get(view_entity).unwrap();
|
||||||
let draw_transmissive_pbr = transmissive_draw_functions.read().id::<DrawMaterial<M>>();
|
let entity_tick = entity_specialization_ticks.get(visible_entity).unwrap();
|
||||||
let draw_transparent_pbr = transparent_draw_functions.read().id::<DrawMaterial<M>>();
|
let last_specialized_tick = specialized_material_pipeline_cache
|
||||||
|
.get(&(*view_entity, *visible_entity))
|
||||||
let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples())
|
.map(|(tick, _)| *tick);
|
||||||
| MeshPipelineKey::from_hdr(view.hdr);
|
let needs_specialization = last_specialized_tick.is_none_or(|tick| {
|
||||||
|
view_tick.is_newer_than(tick, ticks.this_run())
|
||||||
if normal_prepass {
|
|| entity_tick.is_newer_than(tick, ticks.this_run())
|
||||||
view_key |= MeshPipelineKey::NORMAL_PREPASS;
|
});
|
||||||
|
if !needs_specialization {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if depth_prepass {
|
|
||||||
view_key |= MeshPipelineKey::DEPTH_PREPASS;
|
|
||||||
}
|
|
||||||
|
|
||||||
if motion_vector_prepass {
|
|
||||||
view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS;
|
|
||||||
}
|
|
||||||
|
|
||||||
if deferred_prepass {
|
|
||||||
view_key |= MeshPipelineKey::DEFERRED_PREPASS;
|
|
||||||
}
|
|
||||||
|
|
||||||
if temporal_jitter {
|
|
||||||
view_key |= MeshPipelineKey::TEMPORAL_JITTER;
|
|
||||||
}
|
|
||||||
|
|
||||||
if has_environment_maps {
|
|
||||||
view_key |= MeshPipelineKey::ENVIRONMENT_MAP;
|
|
||||||
}
|
|
||||||
|
|
||||||
if has_irradiance_volumes {
|
|
||||||
view_key |= MeshPipelineKey::IRRADIANCE_VOLUME;
|
|
||||||
}
|
|
||||||
|
|
||||||
if has_oit {
|
|
||||||
view_key |= MeshPipelineKey::OIT_ENABLED;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(projection) = projection {
|
|
||||||
view_key |= match projection {
|
|
||||||
Projection::Perspective(_) => MeshPipelineKey::VIEW_PROJECTION_PERSPECTIVE,
|
|
||||||
Projection::Orthographic(_) => MeshPipelineKey::VIEW_PROJECTION_ORTHOGRAPHIC,
|
|
||||||
Projection::Custom(_) => MeshPipelineKey::VIEW_PROJECTION_NONSTANDARD,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
match shadow_filter_method.unwrap_or(&ShadowFilteringMethod::default()) {
|
|
||||||
ShadowFilteringMethod::Hardware2x2 => {
|
|
||||||
view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_HARDWARE_2X2;
|
|
||||||
}
|
|
||||||
ShadowFilteringMethod::Gaussian => {
|
|
||||||
view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_GAUSSIAN;
|
|
||||||
}
|
|
||||||
ShadowFilteringMethod::Temporal => {
|
|
||||||
view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_TEMPORAL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !view.hdr {
|
|
||||||
if let Some(tonemapping) = tonemapping {
|
|
||||||
view_key |= MeshPipelineKey::TONEMAP_IN_SHADER;
|
|
||||||
view_key |= tonemapping_pipeline_key(*tonemapping);
|
|
||||||
}
|
|
||||||
if let Some(DebandDither::Enabled) = dither {
|
|
||||||
view_key |= MeshPipelineKey::DEBAND_DITHER;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ssao {
|
|
||||||
view_key |= MeshPipelineKey::SCREEN_SPACE_AMBIENT_OCCLUSION;
|
|
||||||
}
|
|
||||||
if distance_fog {
|
|
||||||
view_key |= MeshPipelineKey::DISTANCE_FOG;
|
|
||||||
}
|
|
||||||
if let Some(camera_3d) = camera_3d {
|
|
||||||
view_key |= screen_space_specular_transmission_pipeline_key(
|
|
||||||
camera_3d.screen_space_specular_transmission_quality,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let rangefinder = view.rangefinder3d();
|
|
||||||
for (render_entity, visible_entity) in visible_entities.iter::<Mesh3d>() {
|
|
||||||
let Some(material_asset_id) = render_material_instances.get(visible_entity) else {
|
let Some(material_asset_id) = render_material_instances.get(visible_entity) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
@ -849,15 +848,13 @@ pub fn queue_material_meshes<M: Material>(
|
|||||||
let mut mesh_pipeline_key_bits = material.properties.mesh_pipeline_key_bits;
|
let mut mesh_pipeline_key_bits = material.properties.mesh_pipeline_key_bits;
|
||||||
mesh_pipeline_key_bits.insert(alpha_mode_pipeline_key(
|
mesh_pipeline_key_bits.insert(alpha_mode_pipeline_key(
|
||||||
material.properties.alpha_mode,
|
material.properties.alpha_mode,
|
||||||
msaa,
|
&Msaa::from_samples(view_key.msaa_samples()),
|
||||||
));
|
));
|
||||||
let mut mesh_key = view_key
|
let mut mesh_key = *view_key
|
||||||
| MeshPipelineKey::from_bits_retain(mesh.key_bits.bits())
|
| MeshPipelineKey::from_bits_retain(mesh.key_bits.bits())
|
||||||
| mesh_pipeline_key_bits;
|
| mesh_pipeline_key_bits;
|
||||||
|
|
||||||
let mut lightmap_slab = None;
|
|
||||||
if let Some(lightmap) = render_lightmaps.render_lightmaps.get(visible_entity) {
|
if let Some(lightmap) = render_lightmaps.render_lightmaps.get(visible_entity) {
|
||||||
lightmap_slab = Some(*lightmap.slab_index);
|
|
||||||
mesh_key |= MeshPipelineKey::LIGHTMAPPED;
|
mesh_key |= MeshPipelineKey::LIGHTMAPPED;
|
||||||
|
|
||||||
if lightmap.bicubic_sampling {
|
if lightmap.bicubic_sampling {
|
||||||
@ -869,7 +866,7 @@ pub fn queue_material_meshes<M: Material>(
|
|||||||
mesh_key |= MeshPipelineKey::VISIBILITY_RANGE_DITHER;
|
mesh_key |= MeshPipelineKey::VISIBILITY_RANGE_DITHER;
|
||||||
}
|
}
|
||||||
|
|
||||||
if motion_vector_prepass {
|
if view_key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) {
|
||||||
// If the previous frame have skins or morph targets, note that.
|
// If the previous frame have skins or morph targets, note that.
|
||||||
if mesh_instance
|
if mesh_instance
|
||||||
.flags
|
.flags
|
||||||
@ -885,17 +882,13 @@ pub fn queue_material_meshes<M: Material>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let pipeline_id = pipelines.specialize(
|
let key = MaterialPipelineKey {
|
||||||
&pipeline_cache,
|
|
||||||
&material_pipeline,
|
|
||||||
MaterialPipelineKey {
|
|
||||||
mesh_key,
|
mesh_key,
|
||||||
bind_group_data: material_bind_group
|
bind_group_data: material_bind_group
|
||||||
.get_extra_data(material.binding.slot)
|
.get_extra_data(material.binding.slot)
|
||||||
.clone(),
|
.clone(),
|
||||||
},
|
};
|
||||||
&mesh.layout,
|
let pipeline_id = pipelines.specialize(&pipeline_cache, &pipeline, key, &mesh.layout);
|
||||||
);
|
|
||||||
let pipeline_id = match pipeline_id {
|
let pipeline_id = match pipeline_id {
|
||||||
Ok(id) => id,
|
Ok(id) => id,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@ -904,33 +897,94 @@ pub fn queue_material_meshes<M: Material>(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
specialized_material_pipeline_cache.insert(
|
||||||
|
(*view_entity, *visible_entity),
|
||||||
|
(ticks.this_run(), pipeline_id),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// For each view, iterates over all the meshes visible from that view and adds
|
||||||
|
/// them to [`BinnedRenderPhase`]s or [`SortedRenderPhase`]s as appropriate.
|
||||||
|
pub fn queue_material_meshes<M: Material>(
|
||||||
|
render_materials: Res<RenderAssets<PreparedMaterial<M>>>,
|
||||||
|
render_mesh_instances: Res<RenderMeshInstances>,
|
||||||
|
render_material_instances: Res<RenderMaterialInstances<M>>,
|
||||||
|
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<(&MainEntity, &ExtractedView, &RenderVisibleEntities)>,
|
||||||
|
specialized_material_pipeline_cache: ResMut<SpecializedMaterialPipelineCache<M>>,
|
||||||
|
) where
|
||||||
|
M::Data: PartialEq + Eq + Hash + Clone,
|
||||||
|
{
|
||||||
|
for (view_entity, 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 rangefinder = view.rangefinder3d();
|
||||||
|
for (render_entity, visible_entity) in visible_entities.iter::<Mesh3d>() {
|
||||||
|
let Some(pipeline_id) = specialized_material_pipeline_cache
|
||||||
|
.get(&(*view_entity, *visible_entity))
|
||||||
|
.map(|(_, pipeline_id)| *pipeline_id)
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let Some(material_asset_id) = render_material_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_asset_id) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
// Fetch the slabs that this mesh resides in.
|
// Fetch the slabs that this mesh resides in.
|
||||||
let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
|
let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
|
||||||
|
|
||||||
match mesh_key
|
match material.properties.render_phase_type {
|
||||||
.intersection(MeshPipelineKey::BLEND_RESERVED_BITS | MeshPipelineKey::MAY_DISCARD)
|
RenderPhaseType::Transmissive => {
|
||||||
{
|
|
||||||
MeshPipelineKey::BLEND_OPAQUE | MeshPipelineKey::BLEND_ALPHA_TO_COVERAGE => {
|
|
||||||
if material.properties.reads_view_transmission_texture {
|
|
||||||
let distance = rangefinder.distance_translation(&mesh_instance.translation)
|
let distance = rangefinder.distance_translation(&mesh_instance.translation)
|
||||||
+ material.properties.depth_bias;
|
+ material.properties.depth_bias;
|
||||||
transmissive_phase.add(Transmissive3d {
|
transmissive_phase.add(Transmissive3d {
|
||||||
entity: (*render_entity, *visible_entity),
|
entity: (*render_entity, *visible_entity),
|
||||||
draw_function: draw_transmissive_pbr,
|
draw_function: material.properties.draw_function_id,
|
||||||
pipeline: pipeline_id,
|
pipeline: pipeline_id,
|
||||||
distance,
|
distance,
|
||||||
batch_range: 0..1,
|
batch_range: 0..1,
|
||||||
extra_index: PhaseItemExtraIndex::None,
|
extra_index: PhaseItemExtraIndex::None,
|
||||||
indexed: index_slab.is_some(),
|
indexed: index_slab.is_some(),
|
||||||
});
|
});
|
||||||
} else if material.properties.render_method == OpaqueRendererMethod::Forward {
|
}
|
||||||
|
RenderPhaseType::Opaque => {
|
||||||
|
if material.properties.render_method == OpaqueRendererMethod::Deferred {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let batch_set_key = Opaque3dBatchSetKey {
|
let batch_set_key = Opaque3dBatchSetKey {
|
||||||
pipeline: pipeline_id,
|
pipeline: pipeline_id,
|
||||||
draw_function: draw_opaque_pbr,
|
draw_function: material.properties.draw_function_id,
|
||||||
material_bind_group_index: Some(material.binding.group.0),
|
material_bind_group_index: Some(material.binding.group.0),
|
||||||
vertex_slab: vertex_slab.unwrap_or_default(),
|
vertex_slab: vertex_slab.unwrap_or_default(),
|
||||||
index_slab,
|
index_slab,
|
||||||
lightmap_slab,
|
lightmap_slab: mesh_instance.shared.lightmap_slab_index.map(|index| *index),
|
||||||
};
|
};
|
||||||
let bin_key = Opaque3dBinKey {
|
let bin_key = Opaque3dBinKey {
|
||||||
asset_id: mesh_instance.mesh_asset_id.into(),
|
asset_id: mesh_instance.mesh_asset_id.into(),
|
||||||
@ -945,24 +999,10 @@ pub fn queue_material_meshes<M: Material>(
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// Alpha mask
|
// Alpha mask
|
||||||
MeshPipelineKey::MAY_DISCARD => {
|
RenderPhaseType::AlphaMask => {
|
||||||
if material.properties.reads_view_transmission_texture {
|
|
||||||
let distance = rangefinder.distance_translation(&mesh_instance.translation)
|
|
||||||
+ material.properties.depth_bias;
|
|
||||||
transmissive_phase.add(Transmissive3d {
|
|
||||||
entity: (*render_entity, *visible_entity),
|
|
||||||
draw_function: draw_transmissive_pbr,
|
|
||||||
pipeline: pipeline_id,
|
|
||||||
distance,
|
|
||||||
batch_range: 0..1,
|
|
||||||
extra_index: PhaseItemExtraIndex::None,
|
|
||||||
indexed: index_slab.is_some(),
|
|
||||||
});
|
|
||||||
} else if material.properties.render_method == OpaqueRendererMethod::Forward {
|
|
||||||
let batch_set_key = OpaqueNoLightmap3dBatchSetKey {
|
let batch_set_key = OpaqueNoLightmap3dBatchSetKey {
|
||||||
draw_function: draw_alpha_mask_pbr,
|
draw_function: material.properties.draw_function_id,
|
||||||
pipeline: pipeline_id,
|
pipeline: pipeline_id,
|
||||||
material_bind_group_index: Some(material.binding.group.0),
|
material_bind_group_index: Some(material.binding.group.0),
|
||||||
vertex_slab: vertex_slab.unwrap_or_default(),
|
vertex_slab: vertex_slab.unwrap_or_default(),
|
||||||
@ -981,13 +1021,12 @@ pub fn queue_material_meshes<M: Material>(
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
RenderPhaseType::Transparent => {
|
||||||
_ => {
|
|
||||||
let distance = rangefinder.distance_translation(&mesh_instance.translation)
|
let distance = rangefinder.distance_translation(&mesh_instance.translation)
|
||||||
+ material.properties.depth_bias;
|
+ material.properties.depth_bias;
|
||||||
transparent_phase.add(Transparent3d {
|
transparent_phase.add(Transparent3d {
|
||||||
entity: (*render_entity, *visible_entity),
|
entity: (*render_entity, *visible_entity),
|
||||||
draw_function: draw_transparent_pbr,
|
draw_function: material.properties.draw_function_id,
|
||||||
pipeline: pipeline_id,
|
pipeline: pipeline_id,
|
||||||
distance,
|
distance,
|
||||||
batch_range: 0..1,
|
batch_range: 0..1,
|
||||||
@ -1070,6 +1109,18 @@ pub struct MaterialProperties {
|
|||||||
/// This allows taking color output from the [`Opaque3d`] pass as an input, (for screen-space transmission) but requires
|
/// This allows taking color output from the [`Opaque3d`] pass as an input, (for screen-space transmission) but requires
|
||||||
/// rendering to take place in a separate [`Transmissive3d`] pass.
|
/// rendering to take place in a separate [`Transmissive3d`] pass.
|
||||||
pub reads_view_transmission_texture: bool,
|
pub reads_view_transmission_texture: bool,
|
||||||
|
pub render_phase_type: RenderPhaseType,
|
||||||
|
pub draw_function_id: DrawFunctionId,
|
||||||
|
pub prepass_draw_function_id: Option<DrawFunctionId>,
|
||||||
|
pub deferred_draw_function_id: Option<DrawFunctionId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum RenderPhaseType {
|
||||||
|
Opaque,
|
||||||
|
AlphaMask,
|
||||||
|
Transmissive,
|
||||||
|
Transparent,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A resource that maps each untyped material ID to its binding.
|
/// A resource that maps each untyped material ID to its binding.
|
||||||
@ -1096,6 +1147,14 @@ impl<M: Material> RenderAsset for PreparedMaterial<M> {
|
|||||||
SRes<DefaultOpaqueRendererMethod>,
|
SRes<DefaultOpaqueRendererMethod>,
|
||||||
SResMut<MaterialBindGroupAllocator<M>>,
|
SResMut<MaterialBindGroupAllocator<M>>,
|
||||||
SResMut<RenderMaterialBindings>,
|
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>>,
|
||||||
M::Param,
|
M::Param,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1108,6 +1167,14 @@ impl<M: Material> RenderAsset for PreparedMaterial<M> {
|
|||||||
default_opaque_render_method,
|
default_opaque_render_method,
|
||||||
ref mut bind_group_allocator,
|
ref mut bind_group_allocator,
|
||||||
ref mut render_material_bindings,
|
ref mut 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,
|
||||||
ref mut material_param,
|
ref mut material_param,
|
||||||
): &mut SystemParamItem<Self::Param>,
|
): &mut SystemParamItem<Self::Param>,
|
||||||
) -> Result<Self, PrepareAssetError<Self::SourceAsset>> {
|
) -> Result<Self, PrepareAssetError<Self::SourceAsset>> {
|
||||||
@ -1116,17 +1183,64 @@ impl<M: Material> RenderAsset for PreparedMaterial<M> {
|
|||||||
.entry(material_id.into())
|
.entry(material_id.into())
|
||||||
.or_insert_with(|| bind_group_allocator.allocate());
|
.or_insert_with(|| bind_group_allocator.allocate());
|
||||||
|
|
||||||
let method = match material.opaque_render_method() {
|
let draw_opaque_pbr = opaque_draw_functions.read().id::<DrawMaterial<M>>();
|
||||||
|
let draw_alpha_mask_pbr = alpha_mask_draw_functions.read().id::<DrawMaterial<M>>();
|
||||||
|
let draw_transmissive_pbr = transmissive_draw_functions.read().id::<DrawMaterial<M>>();
|
||||||
|
let draw_transparent_pbr = transparent_draw_functions.read().id::<DrawMaterial<M>>();
|
||||||
|
let draw_opaque_prepass = opaque_prepass_draw_functions
|
||||||
|
.read()
|
||||||
|
.get_id::<DrawPrepass<M>>();
|
||||||
|
let draw_alpha_mask_prepass = alpha_mask_prepass_draw_functions
|
||||||
|
.read()
|
||||||
|
.get_id::<DrawPrepass<M>>();
|
||||||
|
let draw_opaque_deferred = opaque_deferred_draw_functions
|
||||||
|
.read()
|
||||||
|
.get_id::<DrawPrepass<M>>();
|
||||||
|
let draw_alpha_mask_deferred = alpha_mask_deferred_draw_functions
|
||||||
|
.read()
|
||||||
|
.get_id::<DrawPrepass<M>>();
|
||||||
|
|
||||||
|
let render_method = match material.opaque_render_method() {
|
||||||
OpaqueRendererMethod::Forward => OpaqueRendererMethod::Forward,
|
OpaqueRendererMethod::Forward => OpaqueRendererMethod::Forward,
|
||||||
OpaqueRendererMethod::Deferred => OpaqueRendererMethod::Deferred,
|
OpaqueRendererMethod::Deferred => OpaqueRendererMethod::Deferred,
|
||||||
OpaqueRendererMethod::Auto => default_opaque_render_method.0,
|
OpaqueRendererMethod::Auto => default_opaque_render_method.0,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut mesh_pipeline_key_bits = MeshPipelineKey::empty();
|
let mut mesh_pipeline_key_bits = MeshPipelineKey::empty();
|
||||||
mesh_pipeline_key_bits.set(
|
mesh_pipeline_key_bits.set(
|
||||||
MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE,
|
MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE,
|
||||||
material.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 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 draw_function_id = match render_phase_type {
|
||||||
|
RenderPhaseType::Opaque => draw_opaque_pbr,
|
||||||
|
RenderPhaseType::AlphaMask => draw_alpha_mask_pbr,
|
||||||
|
RenderPhaseType::Transmissive => draw_transmissive_pbr,
|
||||||
|
RenderPhaseType::Transparent => draw_transparent_pbr,
|
||||||
|
};
|
||||||
|
let prepass_draw_function_id = match render_phase_type {
|
||||||
|
RenderPhaseType::Opaque => draw_opaque_prepass,
|
||||||
|
RenderPhaseType::AlphaMask => draw_alpha_mask_prepass,
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
let deferred_draw_function_id = match render_phase_type {
|
||||||
|
RenderPhaseType::Opaque => draw_opaque_deferred,
|
||||||
|
RenderPhaseType::AlphaMask => draw_alpha_mask_deferred,
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
match material.unprepared_bind_group(
|
match material.unprepared_bind_group(
|
||||||
&pipeline.material_layout,
|
&pipeline.material_layout,
|
||||||
render_device,
|
render_device,
|
||||||
@ -1141,10 +1255,13 @@ impl<M: Material> RenderAsset for PreparedMaterial<M> {
|
|||||||
properties: MaterialProperties {
|
properties: MaterialProperties {
|
||||||
alpha_mode: material.alpha_mode(),
|
alpha_mode: material.alpha_mode(),
|
||||||
depth_bias: material.depth_bias(),
|
depth_bias: material.depth_bias(),
|
||||||
reads_view_transmission_texture: mesh_pipeline_key_bits
|
reads_view_transmission_texture,
|
||||||
.contains(MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE),
|
render_phase_type,
|
||||||
render_method: method,
|
draw_function_id,
|
||||||
|
prepass_draw_function_id,
|
||||||
|
render_method,
|
||||||
mesh_pipeline_key_bits,
|
mesh_pipeline_key_bits,
|
||||||
|
deferred_draw_function_id,
|
||||||
},
|
},
|
||||||
phantom: PhantomData,
|
phantom: PhantomData,
|
||||||
})
|
})
|
||||||
@ -1177,10 +1294,13 @@ impl<M: Material> RenderAsset for PreparedMaterial<M> {
|
|||||||
properties: MaterialProperties {
|
properties: MaterialProperties {
|
||||||
alpha_mode: material.alpha_mode(),
|
alpha_mode: material.alpha_mode(),
|
||||||
depth_bias: material.depth_bias(),
|
depth_bias: material.depth_bias(),
|
||||||
reads_view_transmission_texture: mesh_pipeline_key_bits
|
reads_view_transmission_texture,
|
||||||
.contains(MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE),
|
render_phase_type,
|
||||||
render_method: method,
|
draw_function_id,
|
||||||
|
prepass_draw_function_id,
|
||||||
|
render_method,
|
||||||
mesh_pipeline_key_bits,
|
mesh_pipeline_key_bits,
|
||||||
|
deferred_draw_function_id,
|
||||||
},
|
},
|
||||||
phantom: PhantomData,
|
phantom: PhantomData,
|
||||||
})
|
})
|
||||||
@ -1206,7 +1326,7 @@ impl<M: Material> RenderAsset for PreparedMaterial<M> {
|
|||||||
_,
|
_,
|
||||||
ref mut bind_group_allocator,
|
ref mut bind_group_allocator,
|
||||||
ref mut render_material_bindings,
|
ref mut render_material_bindings,
|
||||||
_,
|
..,
|
||||||
): &mut SystemParamItem<Self::Param>,
|
): &mut SystemParamItem<Self::Param>,
|
||||||
) {
|
) {
|
||||||
let Some(material_binding_id) = render_material_bindings.remove(&source_asset.untyped())
|
let Some(material_binding_id) = render_material_bindings.remove(&source_asset.untyped())
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::Material;
|
use crate::Material;
|
||||||
use bevy_asset::{AssetId, Handle};
|
use bevy_asset::{AsAssetId, AssetId, Handle};
|
||||||
use bevy_derive::{Deref, DerefMut};
|
use bevy_derive::{Deref, DerefMut};
|
||||||
use bevy_ecs::{component::Component, reflect::ReflectComponent};
|
use bevy_ecs::{component::Component, reflect::ReflectComponent};
|
||||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||||
@ -57,3 +57,11 @@ impl<M: Material> From<&MeshMaterial3d<M>> for AssetId<M> {
|
|||||||
material.id()
|
material.id()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<M: Material> AsAssetId for MeshMaterial3d<M> {
|
||||||
|
type Asset = M;
|
||||||
|
|
||||||
|
fn as_asset_id(&self) -> AssetId<Self::Asset> {
|
||||||
|
self.id()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -3,10 +3,11 @@ mod prepass_bindings;
|
|||||||
use crate::{
|
use crate::{
|
||||||
alpha_mode_pipeline_key, binding_arrays_are_usable, buffer_layout,
|
alpha_mode_pipeline_key, binding_arrays_are_usable, buffer_layout,
|
||||||
material_bind_groups::MaterialBindGroupAllocator, queue_material_meshes,
|
material_bind_groups::MaterialBindGroupAllocator, queue_material_meshes,
|
||||||
setup_morph_and_skinning_defs, skin, DrawMesh, Material, MaterialPipeline, MaterialPipelineKey,
|
setup_morph_and_skinning_defs, skin, DrawMesh, EntitySpecializationTicks, Material,
|
||||||
MeshLayouts, MeshPipeline, MeshPipelineKey, OpaqueRendererMethod, PreparedMaterial,
|
MaterialPipeline, MaterialPipelineKey, MeshLayouts, MeshPipeline, MeshPipelineKey,
|
||||||
RenderLightmaps, RenderMaterialInstances, RenderMeshInstanceFlags, RenderMeshInstances,
|
OpaqueRendererMethod, PreparedMaterial, RenderLightmaps, RenderMaterialInstances,
|
||||||
SetMaterialBindGroup, SetMeshBindGroup, ShadowView, StandardMaterial,
|
RenderMeshInstanceFlags, RenderMeshInstances, RenderPhaseType, SetMaterialBindGroup,
|
||||||
|
SetMeshBindGroup, ShadowView, StandardMaterial,
|
||||||
};
|
};
|
||||||
use bevy_app::{App, Plugin, PreUpdate};
|
use bevy_app::{App, Plugin, PreUpdate};
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
@ -53,7 +54,14 @@ use crate::meshlet::{
|
|||||||
MeshletMesh3d,
|
MeshletMesh3d,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use bevy_derive::{Deref, DerefMut};
|
||||||
|
use bevy_ecs::component::Tick;
|
||||||
|
use bevy_ecs::entity::EntityHash;
|
||||||
|
use bevy_ecs::system::SystemChangeTick;
|
||||||
|
use bevy_platform_support::collections::HashMap;
|
||||||
|
use bevy_render::sync_world::{MainEntity, MainEntityHashMap};
|
||||||
use bevy_render::view::RenderVisibleEntities;
|
use bevy_render::view::RenderVisibleEntities;
|
||||||
|
use bevy_render::RenderSet::PrepareAssets;
|
||||||
use core::{hash::Hash, marker::PhantomData};
|
use core::{hash::Hash, marker::PhantomData};
|
||||||
|
|
||||||
pub const PREPASS_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(921124473254008983);
|
pub const PREPASS_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(921124473254008983);
|
||||||
@ -184,17 +192,27 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
render_app
|
render_app
|
||||||
|
.init_resource::<ViewPrepassSpecializationTicks>()
|
||||||
|
.init_resource::<ViewKeyPrepassCache>()
|
||||||
|
.init_resource::<SpecializedPrepassMaterialPipelineCache<M>>()
|
||||||
.add_render_command::<Opaque3dPrepass, DrawPrepass<M>>()
|
.add_render_command::<Opaque3dPrepass, DrawPrepass<M>>()
|
||||||
.add_render_command::<AlphaMask3dPrepass, DrawPrepass<M>>()
|
.add_render_command::<AlphaMask3dPrepass, DrawPrepass<M>>()
|
||||||
.add_render_command::<Opaque3dDeferred, DrawPrepass<M>>()
|
.add_render_command::<Opaque3dDeferred, DrawPrepass<M>>()
|
||||||
.add_render_command::<AlphaMask3dDeferred, DrawPrepass<M>>()
|
.add_render_command::<AlphaMask3dDeferred, DrawPrepass<M>>()
|
||||||
.add_systems(
|
.add_systems(
|
||||||
Render,
|
Render,
|
||||||
|
(
|
||||||
|
check_prepass_views_need_specialization.in_set(PrepareAssets),
|
||||||
|
specialize_prepass_material_meshes::<M>
|
||||||
|
.in_set(PrepareAssets)
|
||||||
|
.after(prepare_assets::<PreparedMaterial<M>>)
|
||||||
|
.after(prepare_assets::<RenderMesh>),
|
||||||
queue_prepass_material_meshes::<M>
|
queue_prepass_material_meshes::<M>
|
||||||
.in_set(RenderSet::QueueMeshes)
|
.in_set(RenderSet::QueueMeshes)
|
||||||
.after(prepare_assets::<PreparedMaterial<M>>)
|
.after(prepare_assets::<PreparedMaterial<M>>)
|
||||||
// queue_material_meshes only writes to `material_bind_group_id`, which `queue_prepass_material_meshes` doesn't read
|
// queue_material_meshes only writes to `material_bind_group_id`, which `queue_prepass_material_meshes` doesn't read
|
||||||
.ambiguous_with(queue_material_meshes::<StandardMaterial>),
|
.ambiguous_with(queue_material_meshes::<StandardMaterial>),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
#[cfg(feature = "meshlet")]
|
#[cfg(feature = "meshlet")]
|
||||||
@ -775,97 +793,44 @@ pub fn prepare_prepass_view_bind_group<M: Material>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn queue_prepass_material_meshes<M: Material>(
|
#[derive(Resource, Deref, DerefMut)]
|
||||||
(
|
pub struct SpecializedPrepassMaterialPipelineCache<M> {
|
||||||
opaque_draw_functions,
|
// (view_entity, material_entity) -> (tick, pipeline_id)
|
||||||
alpha_mask_draw_functions,
|
#[deref]
|
||||||
opaque_deferred_draw_functions,
|
map: HashMap<(MainEntity, MainEntity), (Tick, CachedRenderPipelineId), EntityHash>,
|
||||||
alpha_mask_deferred_draw_functions,
|
marker: PhantomData<M>,
|
||||||
): (
|
}
|
||||||
Res<DrawFunctions<Opaque3dPrepass>>,
|
|
||||||
Res<DrawFunctions<AlphaMask3dPrepass>>,
|
impl<M> Default for SpecializedPrepassMaterialPipelineCache<M> {
|
||||||
Res<DrawFunctions<Opaque3dDeferred>>,
|
fn default() -> Self {
|
||||||
Res<DrawFunctions<AlphaMask3dDeferred>>,
|
Self {
|
||||||
),
|
map: HashMap::default(),
|
||||||
prepass_pipeline: Res<PrepassPipeline<M>>,
|
marker: PhantomData,
|
||||||
mut pipelines: ResMut<SpecializedMeshPipelines<PrepassPipeline<M>>>,
|
}
|
||||||
pipeline_cache: Res<PipelineCache>,
|
}
|
||||||
(render_meshes, render_mesh_instances): (
|
}
|
||||||
Res<RenderAssets<RenderMesh>>,
|
|
||||||
Res<RenderMeshInstances>,
|
#[derive(Resource, Deref, DerefMut, Default, Clone)]
|
||||||
),
|
pub struct ViewKeyPrepassCache(MainEntityHashMap<MeshPipelineKey>);
|
||||||
render_materials: Res<RenderAssets<PreparedMaterial<M>>>,
|
|
||||||
render_material_instances: Res<RenderMaterialInstances<M>>,
|
#[derive(Resource, Deref, DerefMut, Default, Clone)]
|
||||||
render_lightmaps: Res<RenderLightmaps>,
|
pub struct ViewPrepassSpecializationTicks(MainEntityHashMap<Tick>);
|
||||||
render_visibility_ranges: Res<RenderVisibilityRanges>,
|
|
||||||
(mesh_allocator, material_bind_group_allocator): (
|
pub fn check_prepass_views_need_specialization(
|
||||||
Res<MeshAllocator>,
|
mut view_key_cache: ResMut<ViewKeyPrepassCache>,
|
||||||
Res<MaterialBindGroupAllocator<M>>,
|
mut view_specialization_ticks: ResMut<ViewPrepassSpecializationTicks>,
|
||||||
),
|
mut views: Query<(
|
||||||
gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
|
&MainEntity,
|
||||||
mut opaque_prepass_render_phases: ResMut<ViewBinnedRenderPhases<Opaque3dPrepass>>,
|
|
||||||
mut alpha_mask_prepass_render_phases: ResMut<ViewBinnedRenderPhases<AlphaMask3dPrepass>>,
|
|
||||||
mut opaque_deferred_render_phases: ResMut<ViewBinnedRenderPhases<Opaque3dDeferred>>,
|
|
||||||
mut alpha_mask_deferred_render_phases: ResMut<ViewBinnedRenderPhases<AlphaMask3dDeferred>>,
|
|
||||||
views: Query<(
|
|
||||||
&ExtractedView,
|
|
||||||
&RenderVisibleEntities,
|
|
||||||
&Msaa,
|
&Msaa,
|
||||||
Option<&DepthPrepass>,
|
Option<&DepthPrepass>,
|
||||||
Option<&NormalPrepass>,
|
Option<&NormalPrepass>,
|
||||||
Option<&MotionVectorPrepass>,
|
Option<&MotionVectorPrepass>,
|
||||||
Option<&DeferredPrepass>,
|
|
||||||
)>,
|
)>,
|
||||||
) where
|
ticks: SystemChangeTick,
|
||||||
M::Data: PartialEq + Eq + Hash + Clone,
|
) {
|
||||||
{
|
for (view_entity, msaa, depth_prepass, normal_prepass, motion_vector_prepass) in
|
||||||
let opaque_draw_prepass = opaque_draw_functions
|
views.iter_mut()
|
||||||
.read()
|
|
||||||
.get_id::<DrawPrepass<M>>()
|
|
||||||
.unwrap();
|
|
||||||
let alpha_mask_draw_prepass = alpha_mask_draw_functions
|
|
||||||
.read()
|
|
||||||
.get_id::<DrawPrepass<M>>()
|
|
||||||
.unwrap();
|
|
||||||
let opaque_draw_deferred = opaque_deferred_draw_functions
|
|
||||||
.read()
|
|
||||||
.get_id::<DrawPrepass<M>>()
|
|
||||||
.unwrap();
|
|
||||||
let alpha_mask_draw_deferred = alpha_mask_deferred_draw_functions
|
|
||||||
.read()
|
|
||||||
.get_id::<DrawPrepass<M>>()
|
|
||||||
.unwrap();
|
|
||||||
for (
|
|
||||||
extracted_view,
|
|
||||||
visible_entities,
|
|
||||||
msaa,
|
|
||||||
depth_prepass,
|
|
||||||
normal_prepass,
|
|
||||||
motion_vector_prepass,
|
|
||||||
deferred_prepass,
|
|
||||||
) in &views
|
|
||||||
{
|
{
|
||||||
let (
|
|
||||||
mut opaque_phase,
|
|
||||||
mut alpha_mask_phase,
|
|
||||||
mut opaque_deferred_phase,
|
|
||||||
mut alpha_mask_deferred_phase,
|
|
||||||
) = (
|
|
||||||
opaque_prepass_render_phases.get_mut(&extracted_view.retained_view_entity),
|
|
||||||
alpha_mask_prepass_render_phases.get_mut(&extracted_view.retained_view_entity),
|
|
||||||
opaque_deferred_render_phases.get_mut(&extracted_view.retained_view_entity),
|
|
||||||
alpha_mask_deferred_render_phases.get_mut(&extracted_view.retained_view_entity),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Skip if there's no place to put the mesh.
|
|
||||||
if opaque_phase.is_none()
|
|
||||||
&& alpha_mask_phase.is_none()
|
|
||||||
&& opaque_deferred_phase.is_none()
|
|
||||||
&& alpha_mask_deferred_phase.is_none()
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples());
|
let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples());
|
||||||
if depth_prepass.is_some() {
|
if depth_prepass.is_some() {
|
||||||
view_key |= MeshPipelineKey::DEPTH_PREPASS;
|
view_key |= MeshPipelineKey::DEPTH_PREPASS;
|
||||||
@ -877,7 +842,102 @@ pub fn queue_prepass_material_meshes<M: Material>(
|
|||||||
view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS;
|
view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (render_entity, visible_entity) in visible_entities.iter::<Mesh3d>() {
|
if let Some(current_key) = view_key_cache.get_mut(view_entity) {
|
||||||
|
if *current_key != view_key {
|
||||||
|
view_key_cache.insert(*view_entity, view_key);
|
||||||
|
view_specialization_ticks.insert(*view_entity, ticks.this_run());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
view_key_cache.insert(*view_entity, view_key);
|
||||||
|
view_specialization_ticks.insert(*view_entity, ticks.this_run());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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_lightmaps: Res<RenderLightmaps>,
|
||||||
|
render_visibility_ranges: Res<RenderVisibilityRanges>,
|
||||||
|
material_bind_group_allocator: Res<MaterialBindGroupAllocator<M>>,
|
||||||
|
view_key_cache: Res<ViewKeyPrepassCache>,
|
||||||
|
views: Query<(
|
||||||
|
&MainEntity,
|
||||||
|
&ExtractedView,
|
||||||
|
&RenderVisibleEntities,
|
||||||
|
&Msaa,
|
||||||
|
Option<&MotionVectorPrepass>,
|
||||||
|
Option<&DeferredPrepass>,
|
||||||
|
)>,
|
||||||
|
(
|
||||||
|
opaque_prepass_render_phases,
|
||||||
|
alpha_mask_prepass_render_phases,
|
||||||
|
opaque_deferred_render_phases,
|
||||||
|
alpha_mask_deferred_render_phases,
|
||||||
|
): (
|
||||||
|
Res<ViewBinnedRenderPhases<Opaque3dPrepass>>,
|
||||||
|
Res<ViewBinnedRenderPhases<AlphaMask3dPrepass>>,
|
||||||
|
Res<ViewBinnedRenderPhases<Opaque3dDeferred>>,
|
||||||
|
Res<ViewBinnedRenderPhases<AlphaMask3dDeferred>>,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
mut specialized_material_pipeline_cache,
|
||||||
|
ticks,
|
||||||
|
prepass_pipeline,
|
||||||
|
mut pipelines,
|
||||||
|
pipeline_cache,
|
||||||
|
view_specialization_ticks,
|
||||||
|
entity_specialization_ticks,
|
||||||
|
): (
|
||||||
|
ResMut<SpecializedPrepassMaterialPipelineCache<M>>,
|
||||||
|
SystemChangeTick,
|
||||||
|
Res<PrepassPipeline<M>>,
|
||||||
|
ResMut<SpecializedMeshPipelines<PrepassPipeline<M>>>,
|
||||||
|
Res<PipelineCache>,
|
||||||
|
Res<ViewPrepassSpecializationTicks>,
|
||||||
|
Res<EntitySpecializationTicks<M>>,
|
||||||
|
),
|
||||||
|
) where
|
||||||
|
M: Material,
|
||||||
|
M::Data: PartialEq + Eq + Hash + Clone,
|
||||||
|
{
|
||||||
|
for (
|
||||||
|
view_entity,
|
||||||
|
extracted_view,
|
||||||
|
visible_entities,
|
||||||
|
msaa,
|
||||||
|
motion_vector_prepass,
|
||||||
|
deferred_prepass,
|
||||||
|
) in &views
|
||||||
|
{
|
||||||
|
if !opaque_deferred_render_phases.contains_key(&extracted_view.retained_view_entity)
|
||||||
|
&& !alpha_mask_deferred_render_phases.contains_key(&extracted_view.retained_view_entity)
|
||||||
|
&& !opaque_prepass_render_phases.contains_key(&extracted_view.retained_view_entity)
|
||||||
|
&& !alpha_mask_prepass_render_phases.contains_key(&extracted_view.retained_view_entity)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(view_key) = view_key_cache.get(view_entity) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (_, visible_entity) in visible_entities.iter::<Mesh3d>() {
|
||||||
|
let view_tick = view_specialization_ticks.get(view_entity).unwrap();
|
||||||
|
let entity_tick = entity_specialization_ticks.get(visible_entity).unwrap();
|
||||||
|
let last_specialized_tick = specialized_material_pipeline_cache
|
||||||
|
.get(&(*view_entity, *visible_entity))
|
||||||
|
.map(|(tick, _)| *tick);
|
||||||
|
let needs_specialization = last_specialized_tick.is_none_or(|tick| {
|
||||||
|
view_tick.is_newer_than(tick, ticks.this_run())
|
||||||
|
|| entity_tick.is_newer_than(tick, ticks.this_run())
|
||||||
|
});
|
||||||
|
if !needs_specialization {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let Some(material_asset_id) = render_material_instances.get(visible_entity) else {
|
let Some(material_asset_id) = render_material_instances.get(visible_entity) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
@ -897,7 +957,7 @@ pub fn queue_prepass_material_meshes<M: Material>(
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut mesh_key = view_key | MeshPipelineKey::from_bits_retain(mesh.key_bits.bits());
|
let mut mesh_key = *view_key | MeshPipelineKey::from_bits_retain(mesh.key_bits.bits());
|
||||||
|
|
||||||
let alpha_mode = material.properties.alpha_mode;
|
let alpha_mode = material.properties.alpha_mode;
|
||||||
match alpha_mode {
|
match alpha_mode {
|
||||||
@ -980,17 +1040,85 @@ pub fn queue_prepass_material_meshes<M: Material>(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
specialized_material_pipeline_cache.insert(
|
||||||
|
(*view_entity, *visible_entity),
|
||||||
|
(ticks.this_run(), pipeline_id),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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>>,
|
||||||
|
mesh_allocator: Res<MeshAllocator>,
|
||||||
|
gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
|
||||||
|
mut opaque_prepass_render_phases: ResMut<ViewBinnedRenderPhases<Opaque3dPrepass>>,
|
||||||
|
mut alpha_mask_prepass_render_phases: ResMut<ViewBinnedRenderPhases<AlphaMask3dPrepass>>,
|
||||||
|
mut opaque_deferred_render_phases: ResMut<ViewBinnedRenderPhases<Opaque3dDeferred>>,
|
||||||
|
mut alpha_mask_deferred_render_phases: ResMut<ViewBinnedRenderPhases<AlphaMask3dDeferred>>,
|
||||||
|
views: Query<(&MainEntity, &ExtractedView, &RenderVisibleEntities)>,
|
||||||
|
specialized_material_pipeline_cache: Res<SpecializedPrepassMaterialPipelineCache<M>>,
|
||||||
|
) where
|
||||||
|
M::Data: PartialEq + Eq + Hash + Clone,
|
||||||
|
{
|
||||||
|
for (view_entity, extracted_view, visible_entities) in &views {
|
||||||
|
let (
|
||||||
|
mut opaque_phase,
|
||||||
|
mut alpha_mask_phase,
|
||||||
|
mut opaque_deferred_phase,
|
||||||
|
mut alpha_mask_deferred_phase,
|
||||||
|
) = (
|
||||||
|
opaque_prepass_render_phases.get_mut(&extracted_view.retained_view_entity),
|
||||||
|
alpha_mask_prepass_render_phases.get_mut(&extracted_view.retained_view_entity),
|
||||||
|
opaque_deferred_render_phases.get_mut(&extracted_view.retained_view_entity),
|
||||||
|
alpha_mask_deferred_render_phases.get_mut(&extracted_view.retained_view_entity),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Skip if there's no place to put the mesh.
|
||||||
|
if opaque_phase.is_none()
|
||||||
|
&& alpha_mask_phase.is_none()
|
||||||
|
&& opaque_deferred_phase.is_none()
|
||||||
|
&& alpha_mask_deferred_phase.is_none()
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (render_entity, visible_entity) in visible_entities.iter::<Mesh3d>() {
|
||||||
|
let Some((_, pipeline_id)) =
|
||||||
|
specialized_material_pipeline_cache.get(&(*view_entity, *visible_entity))
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let Some(material_asset_id) = render_material_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_asset_id) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
|
let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
|
||||||
|
|
||||||
match mesh_key
|
let deferred = match material.properties.render_method {
|
||||||
.intersection(MeshPipelineKey::BLEND_RESERVED_BITS | MeshPipelineKey::MAY_DISCARD)
|
OpaqueRendererMethod::Forward => false,
|
||||||
{
|
OpaqueRendererMethod::Deferred => true,
|
||||||
MeshPipelineKey::BLEND_OPAQUE | MeshPipelineKey::BLEND_ALPHA_TO_COVERAGE => {
|
OpaqueRendererMethod::Auto => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
match material.properties.render_phase_type {
|
||||||
|
RenderPhaseType::Opaque => {
|
||||||
if deferred {
|
if deferred {
|
||||||
opaque_deferred_phase.as_mut().unwrap().add(
|
opaque_deferred_phase.as_mut().unwrap().add(
|
||||||
OpaqueNoLightmap3dBatchSetKey {
|
OpaqueNoLightmap3dBatchSetKey {
|
||||||
draw_function: opaque_draw_deferred,
|
draw_function: material
|
||||||
pipeline: pipeline_id,
|
.properties
|
||||||
|
.deferred_draw_function_id
|
||||||
|
.unwrap(),
|
||||||
|
pipeline: *pipeline_id,
|
||||||
material_bind_group_index: Some(material.binding.group.0),
|
material_bind_group_index: Some(material.binding.group.0),
|
||||||
vertex_slab: vertex_slab.unwrap_or_default(),
|
vertex_slab: vertex_slab.unwrap_or_default(),
|
||||||
index_slab,
|
index_slab,
|
||||||
@ -1009,8 +1137,11 @@ pub fn queue_prepass_material_meshes<M: Material>(
|
|||||||
mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
|
mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
|
||||||
opaque_phase.add(
|
opaque_phase.add(
|
||||||
OpaqueNoLightmap3dBatchSetKey {
|
OpaqueNoLightmap3dBatchSetKey {
|
||||||
draw_function: opaque_draw_prepass,
|
draw_function: material
|
||||||
pipeline: pipeline_id,
|
.properties
|
||||||
|
.prepass_draw_function_id
|
||||||
|
.unwrap(),
|
||||||
|
pipeline: *pipeline_id,
|
||||||
material_bind_group_index: Some(material.binding.group.0),
|
material_bind_group_index: Some(material.binding.group.0),
|
||||||
vertex_slab: vertex_slab.unwrap_or_default(),
|
vertex_slab: vertex_slab.unwrap_or_default(),
|
||||||
index_slab,
|
index_slab,
|
||||||
@ -1026,14 +1157,13 @@ pub fn queue_prepass_material_meshes<M: Material>(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Alpha mask
|
RenderPhaseType::AlphaMask => {
|
||||||
MeshPipelineKey::MAY_DISCARD => {
|
|
||||||
if deferred {
|
if deferred {
|
||||||
let (vertex_slab, index_slab) =
|
let (vertex_slab, index_slab) =
|
||||||
mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
|
mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
|
||||||
let batch_set_key = OpaqueNoLightmap3dBatchSetKey {
|
let batch_set_key = OpaqueNoLightmap3dBatchSetKey {
|
||||||
draw_function: alpha_mask_draw_deferred,
|
draw_function: material.properties.deferred_draw_function_id.unwrap(),
|
||||||
pipeline: pipeline_id,
|
pipeline: *pipeline_id,
|
||||||
material_bind_group_index: Some(material.binding.group.0),
|
material_bind_group_index: Some(material.binding.group.0),
|
||||||
vertex_slab: vertex_slab.unwrap_or_default(),
|
vertex_slab: vertex_slab.unwrap_or_default(),
|
||||||
index_slab,
|
index_slab,
|
||||||
@ -1054,8 +1184,8 @@ pub fn queue_prepass_material_meshes<M: Material>(
|
|||||||
let (vertex_slab, index_slab) =
|
let (vertex_slab, index_slab) =
|
||||||
mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
|
mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
|
||||||
let batch_set_key = OpaqueNoLightmap3dBatchSetKey {
|
let batch_set_key = OpaqueNoLightmap3dBatchSetKey {
|
||||||
draw_function: alpha_mask_draw_prepass,
|
draw_function: material.properties.prepass_draw_function_id.unwrap(),
|
||||||
pipeline: pipeline_id,
|
pipeline: *pipeline_id,
|
||||||
material_bind_group_index: Some(material.binding.group.0),
|
material_bind_group_index: Some(material.binding.group.0),
|
||||||
vertex_slab: vertex_slab.unwrap_or_default(),
|
vertex_slab: vertex_slab.unwrap_or_default(),
|
||||||
index_slab,
|
index_slab,
|
||||||
|
@ -5,6 +5,9 @@ use bevy_asset::UntypedAssetId;
|
|||||||
use bevy_color::ColorToComponents;
|
use bevy_color::ColorToComponents;
|
||||||
use bevy_core_pipeline::core_3d::{Camera3d, CORE_3D_DEPTH_FORMAT};
|
use bevy_core_pipeline::core_3d::{Camera3d, CORE_3D_DEPTH_FORMAT};
|
||||||
use bevy_derive::{Deref, DerefMut};
|
use bevy_derive::{Deref, DerefMut};
|
||||||
|
use bevy_ecs::component::Tick;
|
||||||
|
use bevy_ecs::entity::EntityHash;
|
||||||
|
use bevy_ecs::system::SystemChangeTick;
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
entity::{hash_map::EntityHashMap, hash_set::EntityHashSet},
|
entity::{hash_map::EntityHashMap, hash_set::EntityHashSet},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
@ -37,7 +40,7 @@ use bevy_render::{
|
|||||||
};
|
};
|
||||||
use bevy_transform::{components::GlobalTransform, prelude::Transform};
|
use bevy_transform::{components::GlobalTransform, prelude::Transform};
|
||||||
use bevy_utils::default;
|
use bevy_utils::default;
|
||||||
use core::{hash::Hash, ops::Range};
|
use core::{hash::Hash, marker::PhantomData, ops::Range};
|
||||||
#[cfg(feature = "trace")]
|
#[cfg(feature = "trace")]
|
||||||
use tracing::info_span;
|
use tracing::info_span;
|
||||||
use tracing::{error, warn};
|
use tracing::{error, warn};
|
||||||
@ -1580,25 +1583,99 @@ fn despawn_entities(commands: &mut Commands, entities: Vec<Entity>) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// For each shadow cascade, iterates over all the meshes "visible" from it and
|
// These will be extracted in the material extraction, which will also clear the needs_specialization
|
||||||
/// adds them to [`BinnedRenderPhase`]s or [`SortedRenderPhase`]s as
|
// collection.
|
||||||
/// appropriate.
|
pub fn check_light_entities_needing_specialization<M: Material>(
|
||||||
pub fn queue_shadows<M: Material>(
|
needs_specialization: Query<Entity, (With<MeshMaterial3d<M>>, Changed<NotShadowCaster>)>,
|
||||||
shadow_draw_functions: Res<DrawFunctions<Shadow>>,
|
mut entities_needing_specialization: ResMut<EntitiesNeedingSpecialization<M>>,
|
||||||
|
mut removed_components: RemovedComponents<NotShadowCaster>,
|
||||||
|
) {
|
||||||
|
for entity in &needs_specialization {
|
||||||
|
entities_needing_specialization.push(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
for removed in removed_components.read() {
|
||||||
|
entities_needing_specialization.entities.push(removed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Resource, Deref, DerefMut, Default, Debug, Clone)]
|
||||||
|
pub struct LightKeyCache(EntityHashMap<MeshPipelineKey>);
|
||||||
|
|
||||||
|
#[derive(Resource, Deref, DerefMut, Default, Debug, Clone)]
|
||||||
|
pub struct LightSpecializationTicks(EntityHashMap<Tick>);
|
||||||
|
|
||||||
|
#[derive(Resource, Deref, DerefMut)]
|
||||||
|
pub struct SpecializedShadowMaterialPipelineCache<M> {
|
||||||
|
// (view_light_entity, visible_entity) -> (tick, pipeline_id)
|
||||||
|
#[deref]
|
||||||
|
map: HashMap<(Entity, MainEntity), (Tick, CachedRenderPipelineId), EntityHash>,
|
||||||
|
marker: PhantomData<M>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M> Default for SpecializedShadowMaterialPipelineCache<M> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
map: HashMap::default(),
|
||||||
|
marker: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_views_lights_need_specialization(
|
||||||
|
view_lights: Query<(Entity, &ViewLightEntities), With<ExtractedView>>,
|
||||||
|
view_light_entities: Query<(&LightEntity, &ExtractedView)>,
|
||||||
|
shadow_render_phases: Res<ViewBinnedRenderPhases<Shadow>>,
|
||||||
|
mut light_key_cache: ResMut<LightKeyCache>,
|
||||||
|
mut light_specialization_ticks: ResMut<LightSpecializationTicks>,
|
||||||
|
ticks: SystemChangeTick,
|
||||||
|
) {
|
||||||
|
for (entity, view_lights) in &view_lights {
|
||||||
|
for view_light_entity in view_lights.lights.iter().copied() {
|
||||||
|
let Ok((light_entity, extracted_view_light)) =
|
||||||
|
view_light_entities.get(view_light_entity)
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if !shadow_render_phases.contains_key(&extracted_view_light.retained_view_entity) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let is_directional_light = matches!(light_entity, LightEntity::Directional { .. });
|
||||||
|
let mut light_key = MeshPipelineKey::DEPTH_PREPASS;
|
||||||
|
light_key.set(MeshPipelineKey::UNCLIPPED_DEPTH_ORTHO, is_directional_light);
|
||||||
|
if let Some(current_key) = light_key_cache.get_mut(&entity) {
|
||||||
|
if *current_key != light_key {
|
||||||
|
light_key_cache.insert(view_light_entity, light_key);
|
||||||
|
light_specialization_ticks.insert(view_light_entity, ticks.this_run());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
light_key_cache.insert(view_light_entity, light_key);
|
||||||
|
light_specialization_ticks.insert(view_light_entity, ticks.this_run());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn specialize_shadows<M: Material>(
|
||||||
prepass_pipeline: Res<PrepassPipeline<M>>,
|
prepass_pipeline: Res<PrepassPipeline<M>>,
|
||||||
(render_meshes, render_mesh_instances, render_materials, render_material_instances): (
|
(
|
||||||
|
render_meshes,
|
||||||
|
render_mesh_instances,
|
||||||
|
render_materials,
|
||||||
|
render_material_instances,
|
||||||
|
material_bind_group_allocator,
|
||||||
|
): (
|
||||||
Res<RenderAssets<RenderMesh>>,
|
Res<RenderAssets<RenderMesh>>,
|
||||||
Res<RenderMeshInstances>,
|
Res<RenderMeshInstances>,
|
||||||
Res<RenderAssets<PreparedMaterial<M>>>,
|
Res<RenderAssets<PreparedMaterial<M>>>,
|
||||||
Res<RenderMaterialInstances<M>>,
|
Res<RenderMaterialInstances<M>>,
|
||||||
|
Res<MaterialBindGroupAllocator<M>>,
|
||||||
),
|
),
|
||||||
material_bind_group_allocator: Res<MaterialBindGroupAllocator<M>>,
|
shadow_render_phases: Res<ViewBinnedRenderPhases<Shadow>>,
|
||||||
mut shadow_render_phases: ResMut<ViewBinnedRenderPhases<Shadow>>,
|
|
||||||
mut pipelines: ResMut<SpecializedMeshPipelines<PrepassPipeline<M>>>,
|
mut pipelines: ResMut<SpecializedMeshPipelines<PrepassPipeline<M>>>,
|
||||||
pipeline_cache: Res<PipelineCache>,
|
pipeline_cache: Res<PipelineCache>,
|
||||||
render_lightmaps: Res<RenderLightmaps>,
|
render_lightmaps: Res<RenderLightmaps>,
|
||||||
gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
|
|
||||||
mesh_allocator: Res<MeshAllocator>,
|
|
||||||
view_lights: Query<(Entity, &ViewLightEntities), With<ExtractedView>>,
|
view_lights: Query<(Entity, &ViewLightEntities), With<ExtractedView>>,
|
||||||
view_light_entities: Query<(&LightEntity, &ExtractedView)>,
|
view_light_entities: Query<(&LightEntity, &ExtractedView)>,
|
||||||
point_light_entities: Query<&RenderCubemapVisibleEntities, With<ExtractedPointLight>>,
|
point_light_entities: Query<&RenderCubemapVisibleEntities, With<ExtractedPointLight>>,
|
||||||
@ -1607,24 +1684,28 @@ pub fn queue_shadows<M: Material>(
|
|||||||
With<ExtractedDirectionalLight>,
|
With<ExtractedDirectionalLight>,
|
||||||
>,
|
>,
|
||||||
spot_light_entities: Query<&RenderVisibleMeshEntities, With<ExtractedPointLight>>,
|
spot_light_entities: Query<&RenderVisibleMeshEntities, With<ExtractedPointLight>>,
|
||||||
|
light_key_cache: Res<LightKeyCache>,
|
||||||
|
mut specialized_material_pipeline_cache: ResMut<SpecializedShadowMaterialPipelineCache<M>>,
|
||||||
|
light_specialization_ticks: Res<LightSpecializationTicks>,
|
||||||
|
entity_specialization_ticks: Res<EntitySpecializationTicks<M>>,
|
||||||
|
ticks: SystemChangeTick,
|
||||||
) where
|
) where
|
||||||
M::Data: PartialEq + Eq + Hash + Clone,
|
M::Data: PartialEq + Eq + Hash + Clone,
|
||||||
{
|
{
|
||||||
for (entity, view_lights) in &view_lights {
|
for (entity, view_lights) in &view_lights {
|
||||||
let draw_shadow_mesh = shadow_draw_functions.read().id::<DrawPrepass<M>>();
|
|
||||||
for view_light_entity in view_lights.lights.iter().copied() {
|
for view_light_entity in view_lights.lights.iter().copied() {
|
||||||
let Ok((light_entity, extracted_view_light)) =
|
let Ok((light_entity, extracted_view_light)) =
|
||||||
view_light_entities.get(view_light_entity)
|
view_light_entities.get(view_light_entity)
|
||||||
else {
|
else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let Some(shadow_phase) =
|
if !shadow_render_phases.contains_key(&extracted_view_light.retained_view_entity) {
|
||||||
shadow_render_phases.get_mut(&extracted_view_light.retained_view_entity)
|
continue;
|
||||||
else {
|
}
|
||||||
|
let Some(light_key) = light_key_cache.get(&view_light_entity) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let is_directional_light = matches!(light_entity, LightEntity::Directional { .. });
|
|
||||||
let visible_entities = match light_entity {
|
let visible_entities = match light_entity {
|
||||||
LightEntity::Directional {
|
LightEntity::Directional {
|
||||||
light_entity,
|
light_entity,
|
||||||
@ -1648,14 +1729,25 @@ pub fn queue_shadows<M: Material>(
|
|||||||
.get(*light_entity)
|
.get(*light_entity)
|
||||||
.expect("Failed to get spot light visible entities"),
|
.expect("Failed to get spot light visible entities"),
|
||||||
};
|
};
|
||||||
let mut light_key = MeshPipelineKey::DEPTH_PREPASS;
|
|
||||||
light_key.set(MeshPipelineKey::UNCLIPPED_DEPTH_ORTHO, is_directional_light);
|
|
||||||
|
|
||||||
// NOTE: Lights with shadow mapping disabled will have no visible entities
|
// NOTE: Lights with shadow mapping disabled will have no visible entities
|
||||||
// so no meshes will be queued
|
// so no meshes will be queued
|
||||||
|
|
||||||
for (entity, main_entity) in visible_entities.iter().copied() {
|
for (_, visible_entity) in visible_entities.iter().copied() {
|
||||||
let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(main_entity)
|
let view_tick = light_specialization_ticks.get(&view_light_entity).unwrap();
|
||||||
|
let entity_tick = entity_specialization_ticks.get(&visible_entity).unwrap();
|
||||||
|
let last_specialized_tick = specialized_material_pipeline_cache
|
||||||
|
.get(&(view_light_entity, visible_entity))
|
||||||
|
.map(|(tick, _)| *tick);
|
||||||
|
let needs_specialization = last_specialized_tick.is_none_or(|tick| {
|
||||||
|
view_tick.is_newer_than(tick, ticks.this_run())
|
||||||
|
|| entity_tick.is_newer_than(tick, ticks.this_run())
|
||||||
|
});
|
||||||
|
if !needs_specialization {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let Some(mesh_instance) =
|
||||||
|
render_mesh_instances.render_mesh_queue_data(visible_entity)
|
||||||
else {
|
else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
@ -1665,7 +1757,7 @@ pub fn queue_shadows<M: Material>(
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let Some(material_asset_id) = render_material_instances.get(&main_entity) else {
|
let Some(material_asset_id) = render_material_instances.get(&visible_entity) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let Some(material) = render_materials.get(*material_asset_id) else {
|
let Some(material) = render_materials.get(*material_asset_id) else {
|
||||||
@ -1681,14 +1773,17 @@ pub fn queue_shadows<M: Material>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut mesh_key =
|
let mut mesh_key =
|
||||||
light_key | MeshPipelineKey::from_bits_retain(mesh.key_bits.bits());
|
*light_key | MeshPipelineKey::from_bits_retain(mesh.key_bits.bits());
|
||||||
|
|
||||||
// Even though we don't use the lightmap in the shadow map, the
|
// Even though we don't use the lightmap in the shadow map, the
|
||||||
// `SetMeshBindGroup` render command will bind the data for it. So
|
// `SetMeshBindGroup` render command will bind the data for it. So
|
||||||
// we need to include the appropriate flag in the mesh pipeline key
|
// we need to include the appropriate flag in the mesh pipeline key
|
||||||
// to ensure that the necessary bind group layout entries are
|
// to ensure that the necessary bind group layout entries are
|
||||||
// present.
|
// present.
|
||||||
if render_lightmaps.render_lightmaps.contains_key(&main_entity) {
|
if render_lightmaps
|
||||||
|
.render_lightmaps
|
||||||
|
.contains_key(&visible_entity)
|
||||||
|
{
|
||||||
mesh_key |= MeshPipelineKey::LIGHTMAPPED;
|
mesh_key |= MeshPipelineKey::LIGHTMAPPED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1720,11 +1815,96 @@ pub fn queue_shadows<M: Material>(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
specialized_material_pipeline_cache.insert(
|
||||||
|
(view_light_entity, visible_entity),
|
||||||
|
(ticks.this_run(), pipeline_id),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// For each shadow cascade, iterates over all the meshes "visible" from it and
|
||||||
|
/// adds them to [`BinnedRenderPhase`]s or [`SortedRenderPhase`]s as
|
||||||
|
/// appropriate.
|
||||||
|
pub fn queue_shadows<M: Material>(
|
||||||
|
shadow_draw_functions: Res<DrawFunctions<Shadow>>,
|
||||||
|
render_mesh_instances: Res<RenderMeshInstances>,
|
||||||
|
mut shadow_render_phases: ResMut<ViewBinnedRenderPhases<Shadow>>,
|
||||||
|
gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
|
||||||
|
mesh_allocator: Res<MeshAllocator>,
|
||||||
|
view_lights: Query<(Entity, &ViewLightEntities), With<ExtractedView>>,
|
||||||
|
view_light_entities: Query<(&LightEntity, &ExtractedView)>,
|
||||||
|
point_light_entities: Query<&RenderCubemapVisibleEntities, With<ExtractedPointLight>>,
|
||||||
|
directional_light_entities: Query<
|
||||||
|
&RenderCascadesVisibleEntities,
|
||||||
|
With<ExtractedDirectionalLight>,
|
||||||
|
>,
|
||||||
|
spot_light_entities: Query<&RenderVisibleMeshEntities, With<ExtractedPointLight>>,
|
||||||
|
specialized_material_pipeline_cache: Res<SpecializedShadowMaterialPipelineCache<M>>,
|
||||||
|
) where
|
||||||
|
M::Data: PartialEq + Eq + Hash + Clone,
|
||||||
|
{
|
||||||
|
let draw_shadow_mesh = shadow_draw_functions.read().id::<DrawPrepass<M>>();
|
||||||
|
for (entity, view_lights) in &view_lights {
|
||||||
|
for view_light_entity in view_lights.lights.iter().copied() {
|
||||||
|
let Ok((light_entity, extracted_view_light)) =
|
||||||
|
view_light_entities.get(view_light_entity)
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let Some(shadow_phase) =
|
||||||
|
shadow_render_phases.get_mut(&extracted_view_light.retained_view_entity)
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let visible_entities = match light_entity {
|
||||||
|
LightEntity::Directional {
|
||||||
|
light_entity,
|
||||||
|
cascade_index,
|
||||||
|
} => directional_light_entities
|
||||||
|
.get(*light_entity)
|
||||||
|
.expect("Failed to get directional light visible entities")
|
||||||
|
.entities
|
||||||
|
.get(&entity)
|
||||||
|
.expect("Failed to get directional light visible entities for view")
|
||||||
|
.get(*cascade_index)
|
||||||
|
.expect("Failed to get directional light visible entities for cascade"),
|
||||||
|
LightEntity::Point {
|
||||||
|
light_entity,
|
||||||
|
face_index,
|
||||||
|
} => point_light_entities
|
||||||
|
.get(*light_entity)
|
||||||
|
.expect("Failed to get point light visible entities")
|
||||||
|
.get(*face_index),
|
||||||
|
LightEntity::Spot { light_entity } => spot_light_entities
|
||||||
|
.get(*light_entity)
|
||||||
|
.expect("Failed to get spot light visible entities"),
|
||||||
|
};
|
||||||
|
|
||||||
|
for (entity, main_entity) in visible_entities.iter().copied() {
|
||||||
|
let Some((_, pipeline_id)) =
|
||||||
|
specialized_material_pipeline_cache.get(&(view_light_entity, main_entity))
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(main_entity)
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if !mesh_instance
|
||||||
|
.flags
|
||||||
|
.contains(RenderMeshInstanceFlags::SHADOW_CASTER)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let (vertex_slab, index_slab) =
|
let (vertex_slab, index_slab) =
|
||||||
mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
|
mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
|
||||||
|
|
||||||
let batch_set_key = ShadowBatchSetKey {
|
let batch_set_key = ShadowBatchSetKey {
|
||||||
pipeline: pipeline_id,
|
pipeline: *pipeline_id,
|
||||||
draw_function: draw_shadow_mesh,
|
draw_function: draw_shadow_mesh,
|
||||||
vertex_slab: vertex_slab.unwrap_or_default(),
|
vertex_slab: vertex_slab.unwrap_or_default(),
|
||||||
index_slab,
|
index_slab,
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
use core::mem::size_of;
|
|
||||||
|
|
||||||
use crate::material_bind_groups::{MaterialBindGroupIndex, MaterialBindGroupSlot};
|
use crate::material_bind_groups::{MaterialBindGroupIndex, MaterialBindGroupSlot};
|
||||||
use allocator::MeshAllocator;
|
use allocator::MeshAllocator;
|
||||||
use bevy_asset::{load_internal_asset, AssetId, UntypedAssetId};
|
use bevy_asset::{load_internal_asset, AssetId, UntypedAssetId};
|
||||||
@ -46,10 +44,14 @@ use bevy_render::{
|
|||||||
};
|
};
|
||||||
use bevy_transform::components::GlobalTransform;
|
use bevy_transform::components::GlobalTransform;
|
||||||
use bevy_utils::{default, Parallel};
|
use bevy_utils::{default, Parallel};
|
||||||
|
use core::mem::size_of;
|
||||||
use material_bind_groups::MaterialBindingId;
|
use material_bind_groups::MaterialBindingId;
|
||||||
use render::skin::{self, SkinIndex};
|
use render::skin::{self, SkinIndex};
|
||||||
use tracing::{error, warn};
|
use tracing::{error, warn};
|
||||||
|
|
||||||
|
use self::irradiance_volume::IRRADIANCE_VOLUMES_ARE_USABLE;
|
||||||
|
use crate::environment_map::EnvironmentMapLight;
|
||||||
|
use crate::irradiance_volume::IrradianceVolume;
|
||||||
use crate::{
|
use crate::{
|
||||||
render::{
|
render::{
|
||||||
morph::{
|
morph::{
|
||||||
@ -60,14 +62,22 @@ use crate::{
|
|||||||
},
|
},
|
||||||
*,
|
*,
|
||||||
};
|
};
|
||||||
|
use bevy_core_pipeline::core_3d::Camera3d;
|
||||||
|
use bevy_core_pipeline::oit::OrderIndependentTransparencySettings;
|
||||||
|
use bevy_core_pipeline::prepass::{DeferredPrepass, DepthPrepass, NormalPrepass};
|
||||||
|
use bevy_core_pipeline::tonemapping::{DebandDither, Tonemapping};
|
||||||
|
use bevy_ecs::component::Tick;
|
||||||
|
use bevy_ecs::system::SystemChangeTick;
|
||||||
|
use bevy_render::camera::TemporalJitter;
|
||||||
|
use bevy_render::prelude::Msaa;
|
||||||
use bevy_render::sync_world::{MainEntity, MainEntityHashMap};
|
use bevy_render::sync_world::{MainEntity, MainEntityHashMap};
|
||||||
|
use bevy_render::view::ExtractedView;
|
||||||
|
use bevy_render::RenderSet::PrepareAssets;
|
||||||
use bytemuck::{Pod, Zeroable};
|
use bytemuck::{Pod, Zeroable};
|
||||||
use nonmax::{NonMaxU16, NonMaxU32};
|
use nonmax::{NonMaxU16, NonMaxU32};
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
use static_assertions::const_assert_eq;
|
use static_assertions::const_assert_eq;
|
||||||
|
|
||||||
use self::irradiance_volume::IRRADIANCE_VOLUMES_ARE_USABLE;
|
|
||||||
|
|
||||||
/// Provides support for rendering 3D meshes.
|
/// Provides support for rendering 3D meshes.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct MeshRenderPlugin {
|
pub struct MeshRenderPlugin {
|
||||||
@ -204,8 +214,14 @@ impl Plugin for MeshRenderPlugin {
|
|||||||
|
|
||||||
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
|
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||||
render_app
|
render_app
|
||||||
|
.init_resource::<ViewKeyCache>()
|
||||||
|
.init_resource::<ViewSpecializationTicks>()
|
||||||
.init_resource::<GpuPreprocessingSupport>()
|
.init_resource::<GpuPreprocessingSupport>()
|
||||||
.init_resource::<SkinUniforms>();
|
.init_resource::<SkinUniforms>()
|
||||||
|
.add_systems(
|
||||||
|
Render,
|
||||||
|
check_views_need_specialization.in_set(PrepareAssets),
|
||||||
|
);
|
||||||
|
|
||||||
let gpu_preprocessing_support =
|
let gpu_preprocessing_support =
|
||||||
render_app.world().resource::<GpuPreprocessingSupport>();
|
render_app.world().resource::<GpuPreprocessingSupport>();
|
||||||
@ -283,6 +299,143 @@ impl Plugin for MeshRenderPlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Resource, Deref, DerefMut, Default, Debug, Clone)]
|
||||||
|
pub struct ViewKeyCache(MainEntityHashMap<MeshPipelineKey>);
|
||||||
|
|
||||||
|
#[derive(Resource, Deref, DerefMut, Default, Debug, Clone)]
|
||||||
|
pub struct ViewSpecializationTicks(MainEntityHashMap<Tick>);
|
||||||
|
|
||||||
|
pub fn check_views_need_specialization(
|
||||||
|
mut view_key_cache: ResMut<ViewKeyCache>,
|
||||||
|
mut view_specialization_ticks: ResMut<ViewSpecializationTicks>,
|
||||||
|
mut views: Query<(
|
||||||
|
&MainEntity,
|
||||||
|
&ExtractedView,
|
||||||
|
&Msaa,
|
||||||
|
Option<&Tonemapping>,
|
||||||
|
Option<&DebandDither>,
|
||||||
|
Option<&ShadowFilteringMethod>,
|
||||||
|
Has<ScreenSpaceAmbientOcclusion>,
|
||||||
|
(
|
||||||
|
Has<NormalPrepass>,
|
||||||
|
Has<DepthPrepass>,
|
||||||
|
Has<MotionVectorPrepass>,
|
||||||
|
Has<DeferredPrepass>,
|
||||||
|
),
|
||||||
|
Option<&Camera3d>,
|
||||||
|
Has<TemporalJitter>,
|
||||||
|
Option<&Projection>,
|
||||||
|
Has<DistanceFog>,
|
||||||
|
(
|
||||||
|
Has<RenderViewLightProbes<EnvironmentMapLight>>,
|
||||||
|
Has<RenderViewLightProbes<IrradianceVolume>>,
|
||||||
|
),
|
||||||
|
Has<OrderIndependentTransparencySettings>,
|
||||||
|
)>,
|
||||||
|
ticks: SystemChangeTick,
|
||||||
|
) {
|
||||||
|
for (
|
||||||
|
view_entity,
|
||||||
|
view,
|
||||||
|
msaa,
|
||||||
|
tonemapping,
|
||||||
|
dither,
|
||||||
|
shadow_filter_method,
|
||||||
|
ssao,
|
||||||
|
(normal_prepass, depth_prepass, motion_vector_prepass, deferred_prepass),
|
||||||
|
camera_3d,
|
||||||
|
temporal_jitter,
|
||||||
|
projection,
|
||||||
|
distance_fog,
|
||||||
|
(has_environment_maps, has_irradiance_volumes),
|
||||||
|
has_oit,
|
||||||
|
) in views.iter_mut()
|
||||||
|
{
|
||||||
|
let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples())
|
||||||
|
| MeshPipelineKey::from_hdr(view.hdr);
|
||||||
|
|
||||||
|
if normal_prepass {
|
||||||
|
view_key |= MeshPipelineKey::NORMAL_PREPASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if depth_prepass {
|
||||||
|
view_key |= MeshPipelineKey::DEPTH_PREPASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if motion_vector_prepass {
|
||||||
|
view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if deferred_prepass {
|
||||||
|
view_key |= MeshPipelineKey::DEFERRED_PREPASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if temporal_jitter {
|
||||||
|
view_key |= MeshPipelineKey::TEMPORAL_JITTER;
|
||||||
|
}
|
||||||
|
|
||||||
|
if has_environment_maps {
|
||||||
|
view_key |= MeshPipelineKey::ENVIRONMENT_MAP;
|
||||||
|
}
|
||||||
|
|
||||||
|
if has_irradiance_volumes {
|
||||||
|
view_key |= MeshPipelineKey::IRRADIANCE_VOLUME;
|
||||||
|
}
|
||||||
|
|
||||||
|
if has_oit {
|
||||||
|
view_key |= MeshPipelineKey::OIT_ENABLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(projection) = projection {
|
||||||
|
view_key |= match projection {
|
||||||
|
Projection::Perspective(_) => MeshPipelineKey::VIEW_PROJECTION_PERSPECTIVE,
|
||||||
|
Projection::Orthographic(_) => MeshPipelineKey::VIEW_PROJECTION_ORTHOGRAPHIC,
|
||||||
|
Projection::Custom(_) => MeshPipelineKey::VIEW_PROJECTION_NONSTANDARD,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
match shadow_filter_method.unwrap_or(&ShadowFilteringMethod::default()) {
|
||||||
|
ShadowFilteringMethod::Hardware2x2 => {
|
||||||
|
view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_HARDWARE_2X2;
|
||||||
|
}
|
||||||
|
ShadowFilteringMethod::Gaussian => {
|
||||||
|
view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_GAUSSIAN;
|
||||||
|
}
|
||||||
|
ShadowFilteringMethod::Temporal => {
|
||||||
|
view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_TEMPORAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !view.hdr {
|
||||||
|
if let Some(tonemapping) = tonemapping {
|
||||||
|
view_key |= MeshPipelineKey::TONEMAP_IN_SHADER;
|
||||||
|
view_key |= tonemapping_pipeline_key(*tonemapping);
|
||||||
|
}
|
||||||
|
if let Some(DebandDither::Enabled) = dither {
|
||||||
|
view_key |= MeshPipelineKey::DEBAND_DITHER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ssao {
|
||||||
|
view_key |= MeshPipelineKey::SCREEN_SPACE_AMBIENT_OCCLUSION;
|
||||||
|
}
|
||||||
|
if distance_fog {
|
||||||
|
view_key |= MeshPipelineKey::DISTANCE_FOG;
|
||||||
|
}
|
||||||
|
if let Some(camera_3d) = camera_3d {
|
||||||
|
view_key |= screen_space_specular_transmission_pipeline_key(
|
||||||
|
camera_3d.screen_space_specular_transmission_quality,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if !view_key_cache
|
||||||
|
.get_mut(view_entity)
|
||||||
|
.is_some_and(|current_key| *current_key == view_key)
|
||||||
|
{
|
||||||
|
view_key_cache.insert(*view_entity, view_key);
|
||||||
|
view_specialization_ticks.insert(*view_entity, ticks.this_run());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct MeshTransforms {
|
pub struct MeshTransforms {
|
||||||
pub world_from_local: Affine3,
|
pub world_from_local: Affine3,
|
||||||
@ -568,6 +721,9 @@ pub struct RenderMeshInstanceShared {
|
|||||||
pub material_bindings_index: MaterialBindingId,
|
pub material_bindings_index: MaterialBindingId,
|
||||||
/// Various flags.
|
/// Various flags.
|
||||||
pub flags: RenderMeshInstanceFlags,
|
pub flags: RenderMeshInstanceFlags,
|
||||||
|
/// Index of the slab that the lightmap resides in, if a lightmap is
|
||||||
|
/// present.
|
||||||
|
pub lightmap_slab_index: Option<LightmapSlabIndex>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Information that is gathered during the parallel portion of mesh extraction
|
/// Information that is gathered during the parallel portion of mesh extraction
|
||||||
@ -666,6 +822,7 @@ impl RenderMeshInstanceShared {
|
|||||||
flags: mesh_instance_flags,
|
flags: mesh_instance_flags,
|
||||||
// This gets filled in later, during `RenderMeshGpuBuilder::update`.
|
// This gets filled in later, during `RenderMeshGpuBuilder::update`.
|
||||||
material_bindings_index: default(),
|
material_bindings_index: default(),
|
||||||
|
lightmap_slab_index: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -974,6 +1131,11 @@ impl RenderMeshInstanceGpuBuilder {
|
|||||||
Some(render_lightmap) => u16::from(*render_lightmap.slot_index),
|
Some(render_lightmap) => u16::from(*render_lightmap.slot_index),
|
||||||
None => u16::MAX,
|
None => u16::MAX,
|
||||||
};
|
};
|
||||||
|
let lightmap_slab_index = render_lightmaps
|
||||||
|
.render_lightmaps
|
||||||
|
.get(&entity)
|
||||||
|
.map(|lightmap| lightmap.slab_index);
|
||||||
|
self.shared.lightmap_slab_index = lightmap_slab_index;
|
||||||
|
|
||||||
// Create the mesh input uniform.
|
// Create the mesh input uniform.
|
||||||
let mut mesh_input_uniform = MeshInputUniform {
|
let mut mesh_input_uniform = MeshInputUniform {
|
||||||
|
@ -1134,6 +1134,7 @@ pub fn extract_cameras(
|
|||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut commands = commands.entity(render_entity);
|
let mut commands = commands.entity(render_entity);
|
||||||
commands.insert((
|
commands.insert((
|
||||||
ExtractedCamera {
|
ExtractedCamera {
|
||||||
|
@ -2,6 +2,7 @@ use core::fmt::Debug;
|
|||||||
|
|
||||||
use crate::{primitives::Frustum, view::VisibilitySystems};
|
use crate::{primitives::Frustum, view::VisibilitySystems};
|
||||||
use bevy_app::{App, Plugin, PostStartup, PostUpdate};
|
use bevy_app::{App, Plugin, PostStartup, PostUpdate};
|
||||||
|
use bevy_asset::AssetEvents;
|
||||||
use bevy_derive::{Deref, DerefMut};
|
use bevy_derive::{Deref, DerefMut};
|
||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::prelude::*;
|
||||||
use bevy_math::{ops, AspectRatio, Mat4, Rect, Vec2, Vec3A, Vec4};
|
use bevy_math::{ops, AspectRatio, Mat4, Rect, Vec2, Vec3A, Vec4};
|
||||||
@ -29,7 +30,9 @@ impl Plugin for CameraProjectionPlugin {
|
|||||||
.add_systems(
|
.add_systems(
|
||||||
PostUpdate,
|
PostUpdate,
|
||||||
(
|
(
|
||||||
crate::camera::camera_system.in_set(CameraUpdateSystem),
|
crate::camera::camera_system
|
||||||
|
.in_set(CameraUpdateSystem)
|
||||||
|
.before(AssetEvents),
|
||||||
crate::view::update_frusta
|
crate::view::update_frusta
|
||||||
.in_set(VisibilitySystems::UpdateFrusta)
|
.in_set(VisibilitySystems::UpdateFrusta)
|
||||||
.after(crate::camera::camera_system)
|
.after(crate::camera::camera_system)
|
||||||
|
@ -2,7 +2,7 @@ use crate::{
|
|||||||
mesh::Mesh,
|
mesh::Mesh,
|
||||||
view::{self, Visibility, VisibilityClass},
|
view::{self, Visibility, VisibilityClass},
|
||||||
};
|
};
|
||||||
use bevy_asset::{AssetEvent, AssetId, Handle};
|
use bevy_asset::{AsAssetId, AssetEvent, AssetId, Handle};
|
||||||
use bevy_derive::{Deref, DerefMut};
|
use bevy_derive::{Deref, DerefMut};
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
change_detection::DetectChangesMut, component::Component, event::EventReader, prelude::require,
|
change_detection::DetectChangesMut, component::Component, event::EventReader, prelude::require,
|
||||||
@ -58,6 +58,14 @@ impl From<&Mesh2d> for AssetId<Mesh> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AsAssetId for Mesh2d {
|
||||||
|
type Asset = Mesh;
|
||||||
|
|
||||||
|
fn as_asset_id(&self) -> AssetId<Self::Asset> {
|
||||||
|
self.id()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A component for 3D meshes. Requires a [`MeshMaterial3d`] to be rendered, commonly using a [`StandardMaterial`].
|
/// A component for 3D meshes. Requires a [`MeshMaterial3d`] to be rendered, commonly using a [`StandardMaterial`].
|
||||||
///
|
///
|
||||||
/// [`MeshMaterial3d`]: <https://docs.rs/bevy/latest/bevy/pbr/struct.MeshMaterial3d.html>
|
/// [`MeshMaterial3d`]: <https://docs.rs/bevy/latest/bevy/pbr/struct.MeshMaterial3d.html>
|
||||||
@ -106,6 +114,14 @@ impl From<&Mesh3d> for AssetId<Mesh> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AsAssetId for Mesh3d {
|
||||||
|
type Asset = Mesh;
|
||||||
|
|
||||||
|
fn as_asset_id(&self) -> AssetId<Self::Asset> {
|
||||||
|
self.id()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A system that marks a [`Mesh3d`] as changed if the associated [`Mesh`] asset
|
/// A system that marks a [`Mesh3d`] as changed if the associated [`Mesh`] asset
|
||||||
/// has changed.
|
/// has changed.
|
||||||
///
|
///
|
||||||
|
@ -13,7 +13,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use allocator::MeshAllocatorPlugin;
|
use allocator::MeshAllocatorPlugin;
|
||||||
use bevy_app::{App, Plugin, PostUpdate};
|
use bevy_app::{App, Plugin, PostUpdate};
|
||||||
use bevy_asset::{AssetApp, AssetId, RenderAssetUsages};
|
use bevy_asset::{AssetApp, AssetEvents, AssetId, RenderAssetUsages};
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
system::{
|
system::{
|
||||||
@ -72,7 +72,8 @@ impl Plugin for MeshPlugin {
|
|||||||
.add_systems(
|
.add_systems(
|
||||||
PostUpdate,
|
PostUpdate,
|
||||||
mark_3d_meshes_as_changed_if_their_assets_changed
|
mark_3d_meshes_as_changed_if_their_assets_changed
|
||||||
.ambiguous_with(VisibilitySystems::CalculateBounds),
|
.ambiguous_with(VisibilitySystems::CalculateBounds)
|
||||||
|
.before(AssetEvents),
|
||||||
);
|
);
|
||||||
|
|
||||||
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
|
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
|
||||||
|
@ -186,6 +186,16 @@ impl Msaa {
|
|||||||
pub fn samples(&self) -> u32 {
|
pub fn samples(&self) -> u32 {
|
||||||
*self as u32
|
*self as u32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_samples(samples: u32) -> Self {
|
||||||
|
match samples {
|
||||||
|
1 => Msaa::Off,
|
||||||
|
2 => Msaa::Sample2,
|
||||||
|
4 => Msaa::Sample4,
|
||||||
|
8 => Msaa::Sample8,
|
||||||
|
_ => panic!("Unsupported MSAA sample count: {}", samples),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An identifier for a view that is stable across frames.
|
/// An identifier for a view that is stable across frames.
|
||||||
|
@ -37,7 +37,7 @@ pub use sprite::*;
|
|||||||
pub use texture_slice::*;
|
pub use texture_slice::*;
|
||||||
|
|
||||||
use bevy_app::prelude::*;
|
use bevy_app::prelude::*;
|
||||||
use bevy_asset::{load_internal_asset, Assets, Handle};
|
use bevy_asset::{load_internal_asset, AssetEvents, Assets, Handle};
|
||||||
use bevy_core_pipeline::core_2d::Transparent2d;
|
use bevy_core_pipeline::core_2d::Transparent2d;
|
||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::prelude::*;
|
||||||
use bevy_image::{prelude::*, TextureAtlasPlugin};
|
use bevy_image::{prelude::*, TextureAtlasPlugin};
|
||||||
@ -115,7 +115,7 @@ impl Plugin for SpritePlugin {
|
|||||||
(
|
(
|
||||||
calculate_bounds_2d.in_set(VisibilitySystems::CalculateBounds),
|
calculate_bounds_2d.in_set(VisibilitySystems::CalculateBounds),
|
||||||
(
|
(
|
||||||
compute_slices_on_asset_event,
|
compute_slices_on_asset_event.before(AssetEvents),
|
||||||
compute_slices_on_sprite_change,
|
compute_slices_on_sprite_change,
|
||||||
)
|
)
|
||||||
.in_set(SpriteSystem::ComputeSlices),
|
.in_set(SpriteSystem::ComputeSlices),
|
||||||
|
@ -1,22 +1,29 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
DrawMesh2d, Mesh2d, Mesh2dPipeline, Mesh2dPipelineKey, RenderMesh2dInstances,
|
DrawMesh2d, Mesh2d, Mesh2dPipeline, Mesh2dPipelineKey, RenderMesh2dInstances,
|
||||||
SetMesh2dBindGroup, SetMesh2dViewBindGroup,
|
SetMesh2dBindGroup, SetMesh2dViewBindGroup, ViewKeyCache, ViewSpecializationTicks,
|
||||||
};
|
};
|
||||||
use bevy_app::{App, Plugin};
|
use bevy_app::{App, Plugin, PostUpdate};
|
||||||
use bevy_asset::{Asset, AssetApp, AssetId, AssetServer, Handle};
|
use bevy_asset::prelude::AssetChanged;
|
||||||
|
use bevy_asset::{AsAssetId, Asset, AssetApp, AssetEvents, AssetId, AssetServer, Handle};
|
||||||
use bevy_core_pipeline::{
|
use bevy_core_pipeline::{
|
||||||
core_2d::{
|
core_2d::{
|
||||||
AlphaMask2d, AlphaMask2dBinKey, BatchSetKey2d, Opaque2d, Opaque2dBinKey, Transparent2d,
|
AlphaMask2d, AlphaMask2dBinKey, BatchSetKey2d, Opaque2d, Opaque2dBinKey, Transparent2d,
|
||||||
},
|
},
|
||||||
tonemapping::{DebandDither, Tonemapping},
|
tonemapping::Tonemapping,
|
||||||
};
|
};
|
||||||
use bevy_derive::{Deref, DerefMut};
|
use bevy_derive::{Deref, DerefMut};
|
||||||
|
use bevy_ecs::component::Tick;
|
||||||
|
use bevy_ecs::entity::EntityHash;
|
||||||
|
use bevy_ecs::system::SystemChangeTick;
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
system::{lifetimeless::SRes, SystemParamItem},
|
system::{lifetimeless::SRes, SystemParamItem},
|
||||||
};
|
};
|
||||||
use bevy_math::FloatOrd;
|
use bevy_math::FloatOrd;
|
||||||
|
use bevy_platform_support::collections::HashMap;
|
||||||
use bevy_reflect::{prelude::ReflectDefault, Reflect};
|
use bevy_reflect::{prelude::ReflectDefault, Reflect};
|
||||||
|
use bevy_render::render_phase::DrawFunctionId;
|
||||||
|
use bevy_render::render_resource::CachedRenderPipelineId;
|
||||||
use bevy_render::view::RenderVisibleEntities;
|
use bevy_render::view::RenderVisibleEntities;
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
mesh::{MeshVertexBufferLayoutRef, RenderMesh},
|
mesh::{MeshVertexBufferLayoutRef, RenderMesh},
|
||||||
@ -35,7 +42,7 @@ use bevy_render::{
|
|||||||
},
|
},
|
||||||
renderer::RenderDevice,
|
renderer::RenderDevice,
|
||||||
sync_world::{MainEntity, MainEntityHashMap},
|
sync_world::{MainEntity, MainEntityHashMap},
|
||||||
view::{ExtractedView, Msaa, ViewVisibility},
|
view::{ExtractedView, ViewVisibility},
|
||||||
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
|
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
|
||||||
};
|
};
|
||||||
use core::{hash::Hash, marker::PhantomData};
|
use core::{hash::Hash, marker::PhantomData};
|
||||||
@ -202,6 +209,14 @@ impl<M: Material2d> From<&MeshMaterial2d<M>> for AssetId<M> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<M: Material2d> AsAssetId for MeshMaterial2d<M> {
|
||||||
|
type Asset = M;
|
||||||
|
|
||||||
|
fn as_asset_id(&self) -> AssetId<Self::Asset> {
|
||||||
|
self.id()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets how a 2d material's base color alpha channel is used for transparency.
|
/// Sets how a 2d material's base color alpha channel is used for transparency.
|
||||||
/// Currently, this only works with [`Mesh2d`]. Sprites are always transparent.
|
/// Currently, this only works with [`Mesh2d`]. Sprites are always transparent.
|
||||||
///
|
///
|
||||||
@ -244,22 +259,41 @@ where
|
|||||||
{
|
{
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.init_asset::<M>()
|
app.init_asset::<M>()
|
||||||
|
.init_resource::<EntitiesNeedingSpecialization<M>>()
|
||||||
.register_type::<MeshMaterial2d<M>>()
|
.register_type::<MeshMaterial2d<M>>()
|
||||||
.add_plugins(RenderAssetPlugin::<PreparedMaterial2d<M>>::default());
|
.add_plugins(RenderAssetPlugin::<PreparedMaterial2d<M>>::default())
|
||||||
|
.add_systems(
|
||||||
|
PostUpdate,
|
||||||
|
check_entities_needing_specialization::<M>.after(AssetEvents),
|
||||||
|
);
|
||||||
|
|
||||||
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
|
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||||
render_app
|
render_app
|
||||||
|
.init_resource::<EntitySpecializationTicks<M>>()
|
||||||
|
.init_resource::<SpecializedMaterial2dPipelineCache<M>>()
|
||||||
.add_render_command::<Opaque2d, DrawMaterial2d<M>>()
|
.add_render_command::<Opaque2d, DrawMaterial2d<M>>()
|
||||||
.add_render_command::<AlphaMask2d, DrawMaterial2d<M>>()
|
.add_render_command::<AlphaMask2d, DrawMaterial2d<M>>()
|
||||||
.add_render_command::<Transparent2d, DrawMaterial2d<M>>()
|
.add_render_command::<Transparent2d, DrawMaterial2d<M>>()
|
||||||
.init_resource::<RenderMaterial2dInstances<M>>()
|
.init_resource::<RenderMaterial2dInstances<M>>()
|
||||||
.init_resource::<SpecializedMeshPipelines<Material2dPipeline<M>>>()
|
.init_resource::<SpecializedMeshPipelines<Material2dPipeline<M>>>()
|
||||||
.add_systems(ExtractSchedule, extract_mesh_materials_2d::<M>)
|
.add_systems(
|
||||||
|
ExtractSchedule,
|
||||||
|
(
|
||||||
|
extract_entities_needs_specialization::<M>,
|
||||||
|
extract_mesh_materials_2d::<M>,
|
||||||
|
),
|
||||||
|
)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
Render,
|
Render,
|
||||||
|
(
|
||||||
|
specialize_material2d_meshes::<M>
|
||||||
|
.in_set(RenderSet::PrepareAssets)
|
||||||
|
.after(prepare_assets::<PreparedMaterial2d<M>>)
|
||||||
|
.after(prepare_assets::<RenderMesh>),
|
||||||
queue_material2d_meshes::<M>
|
queue_material2d_meshes::<M>
|
||||||
.in_set(RenderSet::QueueMeshes)
|
.in_set(RenderSet::QueueMeshes)
|
||||||
.after(prepare_assets::<PreparedMaterial2d<M>>),
|
.after(prepare_assets::<PreparedMaterial2d<M>>),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -511,10 +545,89 @@ pub const fn tonemapping_pipeline_key(tonemapping: Tonemapping) -> Mesh2dPipelin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn queue_material2d_meshes<M: Material2d>(
|
pub fn extract_entities_needs_specialization<M>(
|
||||||
opaque_draw_functions: Res<DrawFunctions<Opaque2d>>,
|
entities_needing_specialization: Extract<Res<EntitiesNeedingSpecialization<M>>>,
|
||||||
alpha_mask_draw_functions: Res<DrawFunctions<AlphaMask2d>>,
|
mut entity_specialization_ticks: ResMut<EntitySpecializationTicks<M>>,
|
||||||
transparent_draw_functions: Res<DrawFunctions<Transparent2d>>,
|
ticks: SystemChangeTick,
|
||||||
|
) where
|
||||||
|
M: Material2d,
|
||||||
|
{
|
||||||
|
for entity in entities_needing_specialization.iter() {
|
||||||
|
// Update the entity's specialization tick with this run's tick
|
||||||
|
entity_specialization_ticks.insert((*entity).into(), ticks.this_run());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Resource, Deref, DerefMut, 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(Clone, Resource, Deref, DerefMut, Debug)]
|
||||||
|
pub struct EntitySpecializationTicks<M> {
|
||||||
|
#[deref]
|
||||||
|
pub entities: MainEntityHashMap<Tick>,
|
||||||
|
_marker: PhantomData<M>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M> Default for EntitySpecializationTicks<M> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
entities: MainEntityHashMap::default(),
|
||||||
|
_marker: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Resource, Deref, DerefMut)]
|
||||||
|
pub struct SpecializedMaterial2dPipelineCache<M> {
|
||||||
|
// (view_entity, material_entity) -> (tick, pipeline_id)
|
||||||
|
#[deref]
|
||||||
|
map: HashMap<(MainEntity, MainEntity), (Tick, CachedRenderPipelineId), EntityHash>,
|
||||||
|
marker: PhantomData<M>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M> Default for SpecializedMaterial2dPipelineCache<M> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
map: HashMap::default(),
|
||||||
|
marker: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_entities_needing_specialization<M>(
|
||||||
|
needs_specialization: Query<
|
||||||
|
Entity,
|
||||||
|
Or<(
|
||||||
|
Changed<Mesh2d>,
|
||||||
|
AssetChanged<Mesh2d>,
|
||||||
|
Changed<MeshMaterial2d<M>>,
|
||||||
|
AssetChanged<MeshMaterial2d<M>>,
|
||||||
|
)>,
|
||||||
|
>,
|
||||||
|
mut entities_needing_specialization: ResMut<EntitiesNeedingSpecialization<M>>,
|
||||||
|
) where
|
||||||
|
M: Material2d,
|
||||||
|
{
|
||||||
|
entities_needing_specialization.clear();
|
||||||
|
for entity in &needs_specialization {
|
||||||
|
entities_needing_specialization.push(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn specialize_material2d_meshes<M: Material2d>(
|
||||||
material2d_pipeline: Res<Material2dPipeline<M>>,
|
material2d_pipeline: Res<Material2dPipeline<M>>,
|
||||||
mut pipelines: ResMut<SpecializedMeshPipelines<Material2dPipeline<M>>>,
|
mut pipelines: ResMut<SpecializedMeshPipelines<Material2dPipeline<M>>>,
|
||||||
pipeline_cache: Res<PipelineCache>,
|
pipeline_cache: Res<PipelineCache>,
|
||||||
@ -524,16 +637,15 @@ pub fn queue_material2d_meshes<M: Material2d>(
|
|||||||
),
|
),
|
||||||
mut render_mesh_instances: ResMut<RenderMesh2dInstances>,
|
mut render_mesh_instances: ResMut<RenderMesh2dInstances>,
|
||||||
render_material_instances: Res<RenderMaterial2dInstances<M>>,
|
render_material_instances: Res<RenderMaterial2dInstances<M>>,
|
||||||
mut transparent_render_phases: ResMut<ViewSortedRenderPhases<Transparent2d>>,
|
transparent_render_phases: Res<ViewSortedRenderPhases<Transparent2d>>,
|
||||||
mut opaque_render_phases: ResMut<ViewBinnedRenderPhases<Opaque2d>>,
|
opaque_render_phases: Res<ViewBinnedRenderPhases<Opaque2d>>,
|
||||||
mut alpha_mask_render_phases: ResMut<ViewBinnedRenderPhases<AlphaMask2d>>,
|
alpha_mask_render_phases: Res<ViewBinnedRenderPhases<AlphaMask2d>>,
|
||||||
views: Query<(
|
views: Query<(&MainEntity, &ExtractedView, &RenderVisibleEntities)>,
|
||||||
&ExtractedView,
|
view_key_cache: Res<ViewKeyCache>,
|
||||||
&RenderVisibleEntities,
|
entity_specialization_ticks: Res<EntitySpecializationTicks<M>>,
|
||||||
&Msaa,
|
view_specialization_ticks: Res<ViewSpecializationTicks>,
|
||||||
Option<&Tonemapping>,
|
ticks: SystemChangeTick,
|
||||||
Option<&DebandDither>,
|
mut specialized_material_pipeline_cache: ResMut<SpecializedMaterial2dPipelineCache<M>>,
|
||||||
)>,
|
|
||||||
) where
|
) where
|
||||||
M::Data: PartialEq + Eq + Hash + Clone,
|
M::Data: PartialEq + Eq + Hash + Clone,
|
||||||
{
|
{
|
||||||
@ -541,36 +653,32 @@ pub fn queue_material2d_meshes<M: Material2d>(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (view, visible_entities, msaa, tonemapping, dither) in &views {
|
for (view_entity, view, visible_entities) in &views {
|
||||||
let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity)
|
if !transparent_render_phases.contains_key(&view.retained_view_entity)
|
||||||
else {
|
&& !opaque_render_phases.contains_key(&view.retained_view_entity)
|
||||||
|
&& !alpha_mask_render_phases.contains_key(&view.retained_view_entity)
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
};
|
}
|
||||||
let Some(opaque_phase) = opaque_render_phases.get_mut(&view.retained_view_entity) else {
|
|
||||||
continue;
|
let Some(view_key) = view_key_cache.get(view_entity) else {
|
||||||
};
|
|
||||||
let Some(alpha_mask_phase) = alpha_mask_render_phases.get_mut(&view.retained_view_entity)
|
|
||||||
else {
|
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let draw_transparent_2d = transparent_draw_functions.read().id::<DrawMaterial2d<M>>();
|
for (_, visible_entity) in visible_entities.iter::<Mesh2d>() {
|
||||||
let draw_opaque_2d = opaque_draw_functions.read().id::<DrawMaterial2d<M>>();
|
let view_tick = view_specialization_ticks.get(view_entity).unwrap();
|
||||||
let draw_alpha_mask_2d = alpha_mask_draw_functions.read().id::<DrawMaterial2d<M>>();
|
let entity_tick = entity_specialization_ticks.get(visible_entity).unwrap();
|
||||||
|
let last_specialized_tick = specialized_material_pipeline_cache
|
||||||
|
.get(&(*view_entity, *visible_entity))
|
||||||
|
.map(|(tick, _)| *tick);
|
||||||
|
let needs_specialization = last_specialized_tick.is_none_or(|tick| {
|
||||||
|
view_tick.is_newer_than(tick, ticks.this_run())
|
||||||
|
|| entity_tick.is_newer_than(tick, ticks.this_run())
|
||||||
|
});
|
||||||
|
if !needs_specialization {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let mut view_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples())
|
|
||||||
| Mesh2dPipelineKey::from_hdr(view.hdr);
|
|
||||||
|
|
||||||
if !view.hdr {
|
|
||||||
if let Some(tonemapping) = tonemapping {
|
|
||||||
view_key |= Mesh2dPipelineKey::TONEMAP_IN_SHADER;
|
|
||||||
view_key |= tonemapping_pipeline_key(*tonemapping);
|
|
||||||
}
|
|
||||||
if let Some(DebandDither::Enabled) = dither {
|
|
||||||
view_key |= Mesh2dPipelineKey::DEBAND_DITHER;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (render_entity, visible_entity) in visible_entities.iter::<Mesh2d>() {
|
|
||||||
let Some(material_asset_id) = render_material_instances.get(visible_entity) else {
|
let Some(material_asset_id) = render_material_instances.get(visible_entity) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
@ -583,7 +691,7 @@ pub fn queue_material2d_meshes<M: Material2d>(
|
|||||||
let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else {
|
let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let mesh_key = view_key
|
let mesh_key = *view_key
|
||||||
| Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology())
|
| Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology())
|
||||||
| material_2d.properties.mesh_pipeline_key_bits;
|
| material_2d.properties.mesh_pipeline_key_bits;
|
||||||
|
|
||||||
@ -605,6 +713,66 @@ pub fn queue_material2d_meshes<M: Material2d>(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
specialized_material_pipeline_cache.insert(
|
||||||
|
(*view_entity, *visible_entity),
|
||||||
|
(ticks.this_run(), pipeline_id),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn queue_material2d_meshes<M: Material2d>(
|
||||||
|
(render_meshes, render_materials): (
|
||||||
|
Res<RenderAssets<RenderMesh>>,
|
||||||
|
Res<RenderAssets<PreparedMaterial2d<M>>>,
|
||||||
|
),
|
||||||
|
mut render_mesh_instances: ResMut<RenderMesh2dInstances>,
|
||||||
|
render_material_instances: Res<RenderMaterial2dInstances<M>>,
|
||||||
|
mut transparent_render_phases: ResMut<ViewSortedRenderPhases<Transparent2d>>,
|
||||||
|
mut opaque_render_phases: ResMut<ViewBinnedRenderPhases<Opaque2d>>,
|
||||||
|
mut alpha_mask_render_phases: ResMut<ViewBinnedRenderPhases<AlphaMask2d>>,
|
||||||
|
views: Query<(&MainEntity, &ExtractedView, &RenderVisibleEntities)>,
|
||||||
|
specialized_material_pipeline_cache: ResMut<SpecializedMaterial2dPipelineCache<M>>,
|
||||||
|
) where
|
||||||
|
M::Data: PartialEq + Eq + Hash + Clone,
|
||||||
|
{
|
||||||
|
if render_material_instances.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (view_entity, view, visible_entities) in &views {
|
||||||
|
let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity)
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let Some(opaque_phase) = opaque_render_phases.get_mut(&view.retained_view_entity) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let Some(alpha_mask_phase) = alpha_mask_render_phases.get_mut(&view.retained_view_entity)
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (render_entity, visible_entity) in visible_entities.iter::<Mesh2d>() {
|
||||||
|
let Some(pipeline_id) = specialized_material_pipeline_cache
|
||||||
|
.get(&(*view_entity, *visible_entity))
|
||||||
|
.map(|(_, pipeline_id)| *pipeline_id)
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let Some(material_asset_id) = render_material_instances.get(visible_entity) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let Some(mesh_instance) = render_mesh_instances.get_mut(visible_entity) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let Some(material_2d) = render_materials.get(*material_asset_id) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
mesh_instance.material_bind_group_id = material_2d.get_bind_group_id();
|
mesh_instance.material_bind_group_id = material_2d.get_bind_group_id();
|
||||||
let mesh_z = mesh_instance.transforms.world_from_local.translation.z;
|
let mesh_z = mesh_instance.transforms.world_from_local.translation.z;
|
||||||
|
|
||||||
@ -623,7 +791,7 @@ pub fn queue_material2d_meshes<M: Material2d>(
|
|||||||
AlphaMode2d::Opaque => {
|
AlphaMode2d::Opaque => {
|
||||||
let bin_key = Opaque2dBinKey {
|
let bin_key = Opaque2dBinKey {
|
||||||
pipeline: pipeline_id,
|
pipeline: pipeline_id,
|
||||||
draw_function: draw_opaque_2d,
|
draw_function: material_2d.properties.draw_function_id,
|
||||||
asset_id: mesh_instance.mesh_asset_id.into(),
|
asset_id: mesh_instance.mesh_asset_id.into(),
|
||||||
material_bind_group_id: material_2d.get_bind_group_id().0,
|
material_bind_group_id: material_2d.get_bind_group_id().0,
|
||||||
};
|
};
|
||||||
@ -639,7 +807,7 @@ pub fn queue_material2d_meshes<M: Material2d>(
|
|||||||
AlphaMode2d::Mask(_) => {
|
AlphaMode2d::Mask(_) => {
|
||||||
let bin_key = AlphaMask2dBinKey {
|
let bin_key = AlphaMask2dBinKey {
|
||||||
pipeline: pipeline_id,
|
pipeline: pipeline_id,
|
||||||
draw_function: draw_alpha_mask_2d,
|
draw_function: material_2d.properties.draw_function_id,
|
||||||
asset_id: mesh_instance.mesh_asset_id.into(),
|
asset_id: mesh_instance.mesh_asset_id.into(),
|
||||||
material_bind_group_id: material_2d.get_bind_group_id().0,
|
material_bind_group_id: material_2d.get_bind_group_id().0,
|
||||||
};
|
};
|
||||||
@ -655,7 +823,7 @@ pub fn queue_material2d_meshes<M: Material2d>(
|
|||||||
AlphaMode2d::Blend => {
|
AlphaMode2d::Blend => {
|
||||||
transparent_phase.add(Transparent2d {
|
transparent_phase.add(Transparent2d {
|
||||||
entity: (*render_entity, *visible_entity),
|
entity: (*render_entity, *visible_entity),
|
||||||
draw_function: draw_transparent_2d,
|
draw_function: material_2d.properties.draw_function_id,
|
||||||
pipeline: pipeline_id,
|
pipeline: pipeline_id,
|
||||||
// NOTE: Back-to-front ordering for transparent with ascending sort means far should have the
|
// NOTE: Back-to-front ordering for transparent with ascending sort means far should have the
|
||||||
// lowest sort key and getting closer should increase. As we have
|
// lowest sort key and getting closer should increase. As we have
|
||||||
@ -689,6 +857,7 @@ pub struct Material2dProperties {
|
|||||||
/// These are precalculated so that we can just "or" them together in
|
/// These are precalculated so that we can just "or" them together in
|
||||||
/// [`queue_material2d_meshes`].
|
/// [`queue_material2d_meshes`].
|
||||||
pub mesh_pipeline_key_bits: Mesh2dPipelineKey,
|
pub mesh_pipeline_key_bits: Mesh2dPipelineKey,
|
||||||
|
pub draw_function_id: DrawFunctionId,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Data prepared for a [`Material2d`] instance.
|
/// Data prepared for a [`Material2d`] instance.
|
||||||
@ -708,17 +877,42 @@ impl<T: Material2d> PreparedMaterial2d<T> {
|
|||||||
impl<M: Material2d> RenderAsset for PreparedMaterial2d<M> {
|
impl<M: Material2d> RenderAsset for PreparedMaterial2d<M> {
|
||||||
type SourceAsset = M;
|
type SourceAsset = M;
|
||||||
|
|
||||||
type Param = (SRes<RenderDevice>, SRes<Material2dPipeline<M>>, M::Param);
|
type Param = (
|
||||||
|
SRes<RenderDevice>,
|
||||||
|
SRes<Material2dPipeline<M>>,
|
||||||
|
SRes<DrawFunctions<Opaque2d>>,
|
||||||
|
SRes<DrawFunctions<AlphaMask2d>>,
|
||||||
|
SRes<DrawFunctions<Transparent2d>>,
|
||||||
|
M::Param,
|
||||||
|
);
|
||||||
|
|
||||||
fn prepare_asset(
|
fn prepare_asset(
|
||||||
material: Self::SourceAsset,
|
material: Self::SourceAsset,
|
||||||
_: AssetId<Self::SourceAsset>,
|
_: AssetId<Self::SourceAsset>,
|
||||||
(render_device, pipeline, material_param): &mut SystemParamItem<Self::Param>,
|
(
|
||||||
|
render_device,
|
||||||
|
pipeline,
|
||||||
|
opaque_draw_functions,
|
||||||
|
alpha_mask_draw_functions,
|
||||||
|
transparent_draw_functions,
|
||||||
|
material_param,
|
||||||
|
): &mut SystemParamItem<Self::Param>,
|
||||||
) -> Result<Self, PrepareAssetError<Self::SourceAsset>> {
|
) -> Result<Self, PrepareAssetError<Self::SourceAsset>> {
|
||||||
match material.as_bind_group(&pipeline.material2d_layout, render_device, material_param) {
|
match material.as_bind_group(&pipeline.material2d_layout, render_device, material_param) {
|
||||||
Ok(prepared) => {
|
Ok(prepared) => {
|
||||||
let mut mesh_pipeline_key_bits = Mesh2dPipelineKey::empty();
|
let mut mesh_pipeline_key_bits = Mesh2dPipelineKey::empty();
|
||||||
mesh_pipeline_key_bits.insert(alpha_mode_pipeline_key(material.alpha_mode()));
|
mesh_pipeline_key_bits.insert(alpha_mode_pipeline_key(material.alpha_mode()));
|
||||||
|
|
||||||
|
let draw_function_id = match material.alpha_mode() {
|
||||||
|
AlphaMode2d::Opaque => opaque_draw_functions.read().id::<DrawMaterial2d<M>>(),
|
||||||
|
AlphaMode2d::Mask(_) => {
|
||||||
|
alpha_mask_draw_functions.read().id::<DrawMaterial2d<M>>()
|
||||||
|
}
|
||||||
|
AlphaMode2d::Blend => {
|
||||||
|
transparent_draw_functions.read().id::<DrawMaterial2d<M>>()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Ok(PreparedMaterial2d {
|
Ok(PreparedMaterial2d {
|
||||||
bindings: prepared.bindings,
|
bindings: prepared.bindings,
|
||||||
bind_group: prepared.bind_group,
|
bind_group: prepared.bind_group,
|
||||||
@ -727,6 +921,7 @@ impl<M: Material2d> RenderAsset for PreparedMaterial2d<M> {
|
|||||||
depth_bias: material.depth_bias(),
|
depth_bias: material.depth_bias(),
|
||||||
alpha_mode: material.alpha_mode(),
|
alpha_mode: material.alpha_mode(),
|
||||||
mesh_pipeline_key_bits,
|
mesh_pipeline_key_bits,
|
||||||
|
draw_function_id,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
use bevy_app::Plugin;
|
use bevy_app::Plugin;
|
||||||
use bevy_asset::{load_internal_asset, AssetId, Handle};
|
use bevy_asset::{load_internal_asset, AssetId, Handle};
|
||||||
|
|
||||||
use crate::Material2dBindGroupId;
|
use crate::{tonemapping_pipeline_key, Material2dBindGroupId};
|
||||||
|
use bevy_core_pipeline::tonemapping::DebandDither;
|
||||||
use bevy_core_pipeline::{
|
use bevy_core_pipeline::{
|
||||||
core_2d::{AlphaMask2d, Camera2d, Opaque2d, Transparent2d, CORE_2D_DEPTH_FORMAT},
|
core_2d::{AlphaMask2d, Camera2d, Opaque2d, Transparent2d, CORE_2D_DEPTH_FORMAT},
|
||||||
tonemapping::{
|
tonemapping::{
|
||||||
@ -9,6 +10,8 @@ use bevy_core_pipeline::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
use bevy_derive::{Deref, DerefMut};
|
use bevy_derive::{Deref, DerefMut};
|
||||||
|
use bevy_ecs::component::Tick;
|
||||||
|
use bevy_ecs::system::SystemChangeTick;
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
query::ROQueryItem,
|
query::ROQueryItem,
|
||||||
@ -16,6 +19,8 @@ use bevy_ecs::{
|
|||||||
};
|
};
|
||||||
use bevy_image::{BevyDefault, Image, ImageSampler, TextureFormatPixelInfo};
|
use bevy_image::{BevyDefault, Image, ImageSampler, TextureFormatPixelInfo};
|
||||||
use bevy_math::{Affine3, Vec4};
|
use bevy_math::{Affine3, Vec4};
|
||||||
|
use bevy_render::prelude::Msaa;
|
||||||
|
use bevy_render::RenderSet::PrepareAssets;
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
batching::{
|
batching::{
|
||||||
gpu_preprocessing::IndirectParametersMetadata,
|
gpu_preprocessing::IndirectParametersMetadata,
|
||||||
@ -94,6 +99,7 @@ impl Plugin for Mesh2dRenderPlugin {
|
|||||||
|
|
||||||
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
|
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||||
render_app
|
render_app
|
||||||
|
.init_resource::<ViewKeyCache>()
|
||||||
.init_resource::<RenderMesh2dInstances>()
|
.init_resource::<RenderMesh2dInstances>()
|
||||||
.init_resource::<SpecializedMeshPipelines<Mesh2dPipeline>>()
|
.init_resource::<SpecializedMeshPipelines<Mesh2dPipeline>>()
|
||||||
.add_systems(ExtractSchedule, extract_mesh2d)
|
.add_systems(ExtractSchedule, extract_mesh2d)
|
||||||
@ -137,7 +143,13 @@ impl Plugin for Mesh2dRenderPlugin {
|
|||||||
|
|
||||||
render_app
|
render_app
|
||||||
.insert_resource(batched_instance_buffer)
|
.insert_resource(batched_instance_buffer)
|
||||||
.init_resource::<Mesh2dPipeline>();
|
.init_resource::<Mesh2dPipeline>()
|
||||||
|
.init_resource::<ViewKeyCache>()
|
||||||
|
.init_resource::<ViewSpecializationTicks>()
|
||||||
|
.add_systems(
|
||||||
|
Render,
|
||||||
|
check_views_need_specialization.in_set(PrepareAssets),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the mesh_bindings shader module here as it depends on runtime information about
|
// Load the mesh_bindings shader module here as it depends on runtime information about
|
||||||
@ -152,6 +164,48 @@ impl Plugin for Mesh2dRenderPlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Resource, Deref, DerefMut, Default, Debug, Clone)]
|
||||||
|
pub struct ViewKeyCache(MainEntityHashMap<Mesh2dPipelineKey>);
|
||||||
|
|
||||||
|
#[derive(Resource, Deref, DerefMut, Default, Debug, Clone)]
|
||||||
|
pub struct ViewSpecializationTicks(MainEntityHashMap<Tick>);
|
||||||
|
|
||||||
|
pub fn check_views_need_specialization(
|
||||||
|
mut view_key_cache: ResMut<ViewKeyCache>,
|
||||||
|
mut view_specialization_ticks: ResMut<ViewSpecializationTicks>,
|
||||||
|
views: Query<(
|
||||||
|
&MainEntity,
|
||||||
|
&ExtractedView,
|
||||||
|
&Msaa,
|
||||||
|
Option<&Tonemapping>,
|
||||||
|
Option<&DebandDither>,
|
||||||
|
)>,
|
||||||
|
ticks: SystemChangeTick,
|
||||||
|
) {
|
||||||
|
for (view_entity, view, msaa, tonemapping, dither) in &views {
|
||||||
|
let mut view_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples())
|
||||||
|
| Mesh2dPipelineKey::from_hdr(view.hdr);
|
||||||
|
|
||||||
|
if !view.hdr {
|
||||||
|
if let Some(tonemapping) = tonemapping {
|
||||||
|
view_key |= Mesh2dPipelineKey::TONEMAP_IN_SHADER;
|
||||||
|
view_key |= tonemapping_pipeline_key(*tonemapping);
|
||||||
|
}
|
||||||
|
if let Some(DebandDither::Enabled) = dither {
|
||||||
|
view_key |= Mesh2dPipelineKey::DEBAND_DITHER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !view_key_cache
|
||||||
|
.get_mut(view_entity)
|
||||||
|
.is_some_and(|current_key| *current_key == view_key)
|
||||||
|
{
|
||||||
|
view_key_cache.insert(*view_entity, view_key);
|
||||||
|
view_specialization_ticks.insert(*view_entity, ticks.this_run());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct Mesh2dTransforms {
|
pub struct Mesh2dTransforms {
|
||||||
pub world_from_local: Affine3,
|
pub world_from_local: Affine3,
|
||||||
|
@ -67,9 +67,9 @@ pub mod prelude {
|
|||||||
}
|
}
|
||||||
|
|
||||||
use bevy_app::{prelude::*, Animation};
|
use bevy_app::{prelude::*, Animation};
|
||||||
use bevy_asset::AssetApp;
|
|
||||||
#[cfg(feature = "default_font")]
|
#[cfg(feature = "default_font")]
|
||||||
use bevy_asset::{load_internal_binary_asset, Handle};
|
use bevy_asset::{load_internal_binary_asset, Handle};
|
||||||
|
use bevy_asset::{AssetApp, AssetEvents};
|
||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::prelude::*;
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
camera::CameraUpdateSystem, view::VisibilitySystems, ExtractSchedule, RenderApp,
|
camera::CameraUpdateSystem, view::VisibilitySystems, ExtractSchedule, RenderApp,
|
||||||
@ -124,7 +124,7 @@ impl Plugin for TextPlugin {
|
|||||||
.add_systems(
|
.add_systems(
|
||||||
PostUpdate,
|
PostUpdate,
|
||||||
(
|
(
|
||||||
remove_dropped_font_atlas_sets,
|
remove_dropped_font_atlas_sets.before(AssetEvents),
|
||||||
detect_text_needs_rerender::<Text2d>,
|
detect_text_needs_rerender::<Text2d>,
|
||||||
update_text2d_layout
|
update_text2d_layout
|
||||||
// Potential conflict: `Assets<Image>`
|
// Potential conflict: `Assets<Image>`
|
||||||
|
Loading…
Reference in New Issue
Block a user