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:
charlotte 2025-02-05 10:31:20 -08:00 committed by GitHub
parent be9b38e372
commit 2ea5e9b846
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 1377 additions and 497 deletions

View File

@ -32,7 +32,7 @@ use crate::{
};
use bevy_app::{Animation, App, Plugin, PostUpdate};
use bevy_asset::{Asset, AssetApp, Assets};
use bevy_asset::{Asset, AssetApp, AssetEvents, Assets};
use bevy_ecs::{
entity::{VisitEntities, VisitEntitiesMut},
prelude::*,
@ -1244,7 +1244,7 @@ impl Plugin for AnimationPlugin {
.add_systems(
PostUpdate,
(
graph::thread_animation_graphs,
graph::thread_animation_graphs.before(AssetEvents),
advance_transitions,
advance_animations,
// TODO: `animate_targets` can animate anything, so

View File

@ -293,7 +293,7 @@ mod tests {
use std::println;
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::{
component::Component,
@ -410,7 +410,7 @@ mod tests {
.init_asset::<MyAsset>()
.insert_resource(Counter(vec![0, 0, 0, 0]))
.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.
app.update(); // run_count == 0
@ -445,7 +445,7 @@ mod tests {
},
)
.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.
app.update(); // run_count == 0

View File

@ -212,7 +212,7 @@ use alloc::{
sync::Arc,
vec::Vec,
};
use bevy_app::{App, Last, Plugin, PreUpdate};
use bevy_app::{App, Plugin, PostUpdate, PreUpdate};
use bevy_ecs::prelude::Component;
use bevy_ecs::{
reflect::AppTypeRegistry,
@ -580,7 +580,7 @@ impl AssetApp for App {
.add_event::<AssetLoadFailedEvent<A>>()
.register_type::<Handle<A>>()
.add_systems(
Last,
PostUpdate,
Assets::<A>::asset_events
.run_if(Assets::<A>::asset_events_condition)
.in_set(AssetEvents),

View File

@ -1,4 +1,3 @@
use self::{irradiance_volume::IrradianceVolume, prelude::EnvironmentMapLight};
use crate::material_bind_groups::{MaterialBindGroupAllocator, MaterialBindingId};
#[cfg(feature = "meshlet")]
use crate::meshlet::{
@ -6,20 +5,22 @@ use crate::meshlet::{
InstanceManager,
};
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::{
core_3d::{
AlphaMask3d, Camera3d, Opaque3d, Opaque3dBatchSetKey, Opaque3dBinKey,
ScreenSpaceTransmissionQuality, Transmissive3d, Transparent3d,
AlphaMask3d, Opaque3d, Opaque3dBatchSetKey, Opaque3dBinKey, ScreenSpaceTransmissionQuality,
Transmissive3d, Transparent3d,
},
oit::OrderIndependentTransparencySettings,
prepass::{
DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass,
OpaqueNoLightmap3dBatchSetKey, OpaqueNoLightmap3dBinKey,
},
tonemapping::{DebandDither, Tonemapping},
prepass::{OpaqueNoLightmap3dBatchSetKey, OpaqueNoLightmap3dBinKey},
tonemapping::Tonemapping,
};
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::component::Tick;
use bevy_ecs::entity::EntityHash;
use bevy_ecs::system::SystemChangeTick;
use bevy_ecs::{
prelude::*,
system::{
@ -30,11 +31,11 @@ use bevy_ecs::{
use bevy_platform_support::collections::HashMap;
use bevy_reflect::std_traits::ReflectDefault;
use bevy_reflect::Reflect;
use bevy_render::mesh::mark_3d_meshes_as_changed_if_their_assets_changed;
use bevy_render::{
batching::gpu_preprocessing::GpuPreprocessingSupport,
camera::TemporalJitter,
extract_resource::ExtractResource,
mesh::{self, Mesh3d, MeshVertexBufferLayoutRef, RenderMesh},
mesh::{Mesh3d, MeshVertexBufferLayoutRef, RenderMesh},
render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets},
render_phase::*,
render_resource::*,
@ -271,16 +272,29 @@ where
fn build(&self, app: &mut App) {
app.init_asset::<M>()
.register_type::<MeshMaterial3d<M>>()
.add_plugins(RenderAssetPlugin::<PreparedMaterial<M>>::default())
.init_resource::<EntitiesNeedingSpecialization<M>>()
.add_plugins((RenderAssetPlugin::<PreparedMaterial<M>>::default(),))
.add_systems(
PostUpdate,
mark_meshes_as_changed_if_their_materials_changed::<M>
.ambiguous_with_all()
.after(mesh::mark_3d_meshes_as_changed_if_their_assets_changed),
(
mark_meshes_as_changed_if_their_materials_changed::<M>.ambiguous_with_all(),
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) {
render_app
.init_resource::<EntitySpecializationTicks<M>>()
.init_resource::<SpecializedMaterialPipelineCache<M>>()
.init_resource::<DrawFunctions<Shadow>>()
.init_resource::<RenderMaterialInstances<M>>()
.add_render_command::<Shadow, DrawPrepass<M>>()
@ -291,13 +305,22 @@ where
.init_resource::<SpecializedMeshPipelines<MaterialPipeline<M>>>()
.add_systems(
ExtractSchedule,
(
extract_mesh_materials::<M>.before(ExtractMeshesSet),
extract_entities_needs_specialization::<M>,
),
)
.add_systems(
Render,
(
specialize_material_meshes::<M>
.in_set(RenderSet::PrepareAssets)
.after(prepare_assets::<PreparedMaterial<M>>)
.after(prepare_assets::<RenderMesh>),
queue_material_meshes::<M>
.in_set(RenderSet::QueueMeshes)
.after(prepare_assets::<PreparedMaterial<M>>),
),
)
.add_systems(
Render,
@ -307,11 +330,21 @@ where
);
if self.shadows_enabled {
render_app.add_systems(
render_app
.init_resource::<LightKeyCache>()
.init_resource::<LightSpecializationTicks>()
.init_resource::<SpecializedShadowMaterialPipelineCache<M>>()
.add_systems(
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>
.in_set(RenderSet::QueueMeshes)
.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
/// them to [`BinnedRenderPhase`]s or [`SortedRenderPhase`]s as appropriate.
pub fn queue_material_meshes<M: Material>(
(
opaque_draw_functions,
alpha_mask_draw_functions,
transmissive_draw_functions,
transparent_draw_functions,
): (
Res<DrawFunctions<Opaque3d>>,
Res<DrawFunctions<AlphaMask3d>>,
Res<DrawFunctions<Transmissive3d>>,
Res<DrawFunctions<Transparent3d>>,
),
material_pipeline: Res<MaterialPipeline<M>>,
mut pipelines: ResMut<SpecializedMeshPipelines<MaterialPipeline<M>>>,
pipeline_cache: Res<PipelineCache>,
pub fn extract_entities_needs_specialization<M>(
entities_needing_specialization: Extract<Res<EntitiesNeedingSpecialization<M>>>,
mut entity_specialization_ticks: ResMut<EntitySpecializationTicks<M>>,
ticks: SystemChangeTick,
) where
M: Material,
{
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(Resource, Deref, DerefMut, Clone, Debug)]
pub struct EntitiesNeedingSpecialization<M> {
#[deref]
pub entities: Vec<Entity>,
_marker: PhantomData<M>,
}
impl<M> Default for EntitiesNeedingSpecialization<M> {
fn default() -> Self {
Self {
entities: Default::default(),
_marker: Default::default(),
}
}
}
#[derive(Resource, Deref, DerefMut, 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_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>,
(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<GpuPreprocessingSupport>,
Res<ViewBinnedRenderPhases<Opaque3d>>,
Res<ViewBinnedRenderPhases<AlphaMask3d>>,
Res<ViewSortedRenderPhases<Transmissive3d>>,
Res<ViewSortedRenderPhases<Transparent3d>>,
),
mut opaque_render_phases: ResMut<ViewBinnedRenderPhases<Opaque3d>>,
mut alpha_mask_render_phases: ResMut<ViewBinnedRenderPhases<AlphaMask3d>>,
mut transmissive_render_phases: ResMut<ViewSortedRenderPhases<Transmissive3d>>,
mut transparent_render_phases: ResMut<ViewSortedRenderPhases<Transparent3d>>,
views: Query<(
&ExtractedView,
&RenderVisibleEntities,
&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>,
)>,
views: Query<(&MainEntity, &ExtractedView, &RenderVisibleEntities)>,
view_key_cache: Res<ViewKeyCache>,
entity_specialization_ticks: Res<EntitySpecializationTicks<M>>,
view_specialization_ticks: Res<ViewSpecializationTicks>,
mut specialized_material_pipeline_cache: ResMut<SpecializedMaterialPipelineCache<M>>,
mut pipelines: ResMut<SpecializedMeshPipelines<MaterialPipeline<M>>>,
pipeline: Res<MaterialPipeline<M>>,
pipeline_cache: Res<PipelineCache>,
ticks: SystemChangeTick,
) where
M::Data: PartialEq + Eq + Hash + Clone,
{
for (
view,
visible_entities,
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
for (view_entity, view, visible_entities) in &views {
if !transparent_render_phases.contains_key(&view.retained_view_entity)
&& !opaque_render_phases.contains_key(&view.retained_view_entity)
&& !alpha_mask_render_phases.contains_key(&view.retained_view_entity)
&& !transmissive_render_phases.contains_key(&view.retained_view_entity)
{
let (
Some(opaque_phase),
Some(alpha_mask_phase),
Some(transmissive_phase),
Some(transparent_phase),
) = (
opaque_render_phases.get_mut(&view.retained_view_entity),
alpha_mask_render_phases.get_mut(&view.retained_view_entity),
transmissive_render_phases.get_mut(&view.retained_view_entity),
transparent_render_phases.get_mut(&view.retained_view_entity),
)
else {
continue;
}
let Some(view_key) = view_key_cache.get(view_entity) else {
continue;
};
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 mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples())
| MeshPipelineKey::from_hdr(view.hdr);
if normal_prepass {
view_key |= MeshPipelineKey::NORMAL_PREPASS;
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;
}
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 {
continue;
};
@ -849,15 +848,13 @@ pub fn queue_material_meshes<M: Material>(
let mut mesh_pipeline_key_bits = material.properties.mesh_pipeline_key_bits;
mesh_pipeline_key_bits.insert(alpha_mode_pipeline_key(
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())
| mesh_pipeline_key_bits;
let mut lightmap_slab = None;
if let Some(lightmap) = render_lightmaps.render_lightmaps.get(visible_entity) {
lightmap_slab = Some(*lightmap.slab_index);
mesh_key |= MeshPipelineKey::LIGHTMAPPED;
if lightmap.bicubic_sampling {
@ -869,7 +866,7 @@ pub fn queue_material_meshes<M: Material>(
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 mesh_instance
.flags
@ -885,17 +882,13 @@ pub fn queue_material_meshes<M: Material>(
}
}
let pipeline_id = pipelines.specialize(
&pipeline_cache,
&material_pipeline,
MaterialPipelineKey {
let key = MaterialPipelineKey {
mesh_key,
bind_group_data: material_bind_group
.get_extra_data(material.binding.slot)
.clone(),
},
&mesh.layout,
);
};
let pipeline_id = pipelines.specialize(&pipeline_cache, &pipeline, key, &mesh.layout);
let pipeline_id = match pipeline_id {
Ok(id) => id,
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.
let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
match mesh_key
.intersection(MeshPipelineKey::BLEND_RESERVED_BITS | MeshPipelineKey::MAY_DISCARD)
{
MeshPipelineKey::BLEND_OPAQUE | MeshPipelineKey::BLEND_ALPHA_TO_COVERAGE => {
if material.properties.reads_view_transmission_texture {
match material.properties.render_phase_type {
RenderPhaseType::Transmissive => {
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,
draw_function: material.properties.draw_function_id,
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 {
}
RenderPhaseType::Opaque => {
if material.properties.render_method == OpaqueRendererMethod::Deferred {
continue;
}
let batch_set_key = Opaque3dBatchSetKey {
pipeline: pipeline_id,
draw_function: draw_opaque_pbr,
draw_function: material.properties.draw_function_id,
material_bind_group_index: Some(material.binding.group.0),
vertex_slab: vertex_slab.unwrap_or_default(),
index_slab,
lightmap_slab,
lightmap_slab: mesh_instance.shared.lightmap_slab_index.map(|index| *index),
};
let bin_key = Opaque3dBinKey {
asset_id: mesh_instance.mesh_asset_id.into(),
@ -945,24 +999,10 @@ pub fn queue_material_meshes<M: Material>(
),
);
}
}
// Alpha mask
MeshPipelineKey::MAY_DISCARD => {
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 {
RenderPhaseType::AlphaMask => {
let batch_set_key = OpaqueNoLightmap3dBatchSetKey {
draw_function: draw_alpha_mask_pbr,
draw_function: material.properties.draw_function_id,
pipeline: pipeline_id,
material_bind_group_index: Some(material.binding.group.0),
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)
+ material.properties.depth_bias;
transparent_phase.add(Transparent3d {
entity: (*render_entity, *visible_entity),
draw_function: draw_transparent_pbr,
draw_function: material.properties.draw_function_id,
pipeline: pipeline_id,
distance,
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
/// rendering to take place in a separate [`Transmissive3d`] pass.
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.
@ -1096,6 +1147,14 @@ impl<M: Material> RenderAsset for PreparedMaterial<M> {
SRes<DefaultOpaqueRendererMethod>,
SResMut<MaterialBindGroupAllocator<M>>,
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,
);
@ -1108,6 +1167,14 @@ impl<M: Material> RenderAsset for PreparedMaterial<M> {
default_opaque_render_method,
ref mut bind_group_allocator,
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,
): &mut SystemParamItem<Self::Param>,
) -> Result<Self, PrepareAssetError<Self::SourceAsset>> {
@ -1116,17 +1183,64 @@ impl<M: Material> RenderAsset for PreparedMaterial<M> {
.entry(material_id.into())
.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::Deferred => OpaqueRendererMethod::Deferred,
OpaqueRendererMethod::Auto => default_opaque_render_method.0,
};
let mut mesh_pipeline_key_bits = MeshPipelineKey::empty();
mesh_pipeline_key_bits.set(
MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE,
material.reads_view_transmission_texture(),
);
let reads_view_transmission_texture =
mesh_pipeline_key_bits.contains(MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE);
let 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(
&pipeline.material_layout,
render_device,
@ -1141,10 +1255,13 @@ impl<M: Material> RenderAsset for PreparedMaterial<M> {
properties: MaterialProperties {
alpha_mode: material.alpha_mode(),
depth_bias: material.depth_bias(),
reads_view_transmission_texture: mesh_pipeline_key_bits
.contains(MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE),
render_method: method,
reads_view_transmission_texture,
render_phase_type,
draw_function_id,
prepass_draw_function_id,
render_method,
mesh_pipeline_key_bits,
deferred_draw_function_id,
},
phantom: PhantomData,
})
@ -1177,10 +1294,13 @@ impl<M: Material> RenderAsset for PreparedMaterial<M> {
properties: MaterialProperties {
alpha_mode: material.alpha_mode(),
depth_bias: material.depth_bias(),
reads_view_transmission_texture: mesh_pipeline_key_bits
.contains(MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE),
render_method: method,
reads_view_transmission_texture,
render_phase_type,
draw_function_id,
prepass_draw_function_id,
render_method,
mesh_pipeline_key_bits,
deferred_draw_function_id,
},
phantom: PhantomData,
})
@ -1206,7 +1326,7 @@ impl<M: Material> RenderAsset for PreparedMaterial<M> {
_,
ref mut bind_group_allocator,
ref mut render_material_bindings,
_,
..,
): &mut SystemParamItem<Self::Param>,
) {
let Some(material_binding_id) = render_material_bindings.remove(&source_asset.untyped())

View File

@ -1,5 +1,5 @@
use crate::Material;
use bevy_asset::{AssetId, Handle};
use bevy_asset::{AsAssetId, AssetId, Handle};
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{component::Component, reflect::ReflectComponent};
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
@ -57,3 +57,11 @@ impl<M: Material> From<&MeshMaterial3d<M>> for AssetId<M> {
material.id()
}
}
impl<M: Material> AsAssetId for MeshMaterial3d<M> {
type Asset = M;
fn as_asset_id(&self) -> AssetId<Self::Asset> {
self.id()
}
}

View File

@ -3,10 +3,11 @@ mod prepass_bindings;
use crate::{
alpha_mode_pipeline_key, binding_arrays_are_usable, buffer_layout,
material_bind_groups::MaterialBindGroupAllocator, queue_material_meshes,
setup_morph_and_skinning_defs, skin, DrawMesh, Material, MaterialPipeline, MaterialPipelineKey,
MeshLayouts, MeshPipeline, MeshPipelineKey, OpaqueRendererMethod, PreparedMaterial,
RenderLightmaps, RenderMaterialInstances, RenderMeshInstanceFlags, RenderMeshInstances,
SetMaterialBindGroup, SetMeshBindGroup, ShadowView, StandardMaterial,
setup_morph_and_skinning_defs, skin, DrawMesh, EntitySpecializationTicks, Material,
MaterialPipeline, MaterialPipelineKey, MeshLayouts, MeshPipeline, MeshPipelineKey,
OpaqueRendererMethod, PreparedMaterial, RenderLightmaps, RenderMaterialInstances,
RenderMeshInstanceFlags, RenderMeshInstances, RenderPhaseType, SetMaterialBindGroup,
SetMeshBindGroup, ShadowView, StandardMaterial,
};
use bevy_app::{App, Plugin, PreUpdate};
use bevy_render::{
@ -53,7 +54,14 @@ use crate::meshlet::{
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::RenderSet::PrepareAssets;
use core::{hash::Hash, marker::PhantomData};
pub const PREPASS_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(921124473254008983);
@ -184,17 +192,27 @@ where
}
render_app
.init_resource::<ViewPrepassSpecializationTicks>()
.init_resource::<ViewKeyPrepassCache>()
.init_resource::<SpecializedPrepassMaterialPipelineCache<M>>()
.add_render_command::<Opaque3dPrepass, DrawPrepass<M>>()
.add_render_command::<AlphaMask3dPrepass, DrawPrepass<M>>()
.add_render_command::<Opaque3dDeferred, DrawPrepass<M>>()
.add_render_command::<AlphaMask3dDeferred, DrawPrepass<M>>()
.add_systems(
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>
.in_set(RenderSet::QueueMeshes)
.after(prepare_assets::<PreparedMaterial<M>>)
// queue_material_meshes only writes to `material_bind_group_id`, which `queue_prepass_material_meshes` doesn't read
.ambiguous_with(queue_material_meshes::<StandardMaterial>),
),
);
#[cfg(feature = "meshlet")]
@ -775,97 +793,44 @@ pub fn prepare_prepass_view_bind_group<M: Material>(
}
}
pub fn queue_prepass_material_meshes<M: Material>(
(
opaque_draw_functions,
alpha_mask_draw_functions,
opaque_deferred_draw_functions,
alpha_mask_deferred_draw_functions,
): (
Res<DrawFunctions<Opaque3dPrepass>>,
Res<DrawFunctions<AlphaMask3dPrepass>>,
Res<DrawFunctions<Opaque3dDeferred>>,
Res<DrawFunctions<AlphaMask3dDeferred>>,
),
prepass_pipeline: Res<PrepassPipeline<M>>,
mut pipelines: ResMut<SpecializedMeshPipelines<PrepassPipeline<M>>>,
pipeline_cache: Res<PipelineCache>,
(render_meshes, render_mesh_instances): (
Res<RenderAssets<RenderMesh>>,
Res<RenderMeshInstances>,
),
render_materials: Res<RenderAssets<PreparedMaterial<M>>>,
render_material_instances: Res<RenderMaterialInstances<M>>,
render_lightmaps: Res<RenderLightmaps>,
render_visibility_ranges: Res<RenderVisibilityRanges>,
(mesh_allocator, material_bind_group_allocator): (
Res<MeshAllocator>,
Res<MaterialBindGroupAllocator<M>>,
),
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<(
&ExtractedView,
&RenderVisibleEntities,
#[derive(Resource, Deref, DerefMut)]
pub struct SpecializedPrepassMaterialPipelineCache<M> {
// (view_entity, material_entity) -> (tick, pipeline_id)
#[deref]
map: HashMap<(MainEntity, MainEntity), (Tick, CachedRenderPipelineId), EntityHash>,
marker: PhantomData<M>,
}
impl<M> Default for SpecializedPrepassMaterialPipelineCache<M> {
fn default() -> Self {
Self {
map: HashMap::default(),
marker: PhantomData,
}
}
}
#[derive(Resource, Deref, DerefMut, Default, Clone)]
pub struct ViewKeyPrepassCache(MainEntityHashMap<MeshPipelineKey>);
#[derive(Resource, Deref, DerefMut, Default, Clone)]
pub struct ViewPrepassSpecializationTicks(MainEntityHashMap<Tick>);
pub fn check_prepass_views_need_specialization(
mut view_key_cache: ResMut<ViewKeyPrepassCache>,
mut view_specialization_ticks: ResMut<ViewPrepassSpecializationTicks>,
mut views: Query<(
&MainEntity,
&Msaa,
Option<&DepthPrepass>,
Option<&NormalPrepass>,
Option<&MotionVectorPrepass>,
Option<&DeferredPrepass>,
)>,
) where
M::Data: PartialEq + Eq + Hash + Clone,
{
let opaque_draw_prepass = opaque_draw_functions
.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
ticks: SystemChangeTick,
) {
for (view_entity, msaa, depth_prepass, normal_prepass, motion_vector_prepass) in
views.iter_mut()
{
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());
if depth_prepass.is_some() {
view_key |= MeshPipelineKey::DEPTH_PREPASS;
@ -877,7 +842,102 @@ pub fn queue_prepass_material_meshes<M: Material>(
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 {
continue;
};
@ -897,7 +957,7 @@ pub fn queue_prepass_material_meshes<M: Material>(
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;
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);
match mesh_key
.intersection(MeshPipelineKey::BLEND_RESERVED_BITS | MeshPipelineKey::MAY_DISCARD)
{
MeshPipelineKey::BLEND_OPAQUE | MeshPipelineKey::BLEND_ALPHA_TO_COVERAGE => {
let deferred = match material.properties.render_method {
OpaqueRendererMethod::Forward => false,
OpaqueRendererMethod::Deferred => true,
OpaqueRendererMethod::Auto => unreachable!(),
};
match material.properties.render_phase_type {
RenderPhaseType::Opaque => {
if deferred {
opaque_deferred_phase.as_mut().unwrap().add(
OpaqueNoLightmap3dBatchSetKey {
draw_function: opaque_draw_deferred,
pipeline: pipeline_id,
draw_function: material
.properties
.deferred_draw_function_id
.unwrap(),
pipeline: *pipeline_id,
material_bind_group_index: Some(material.binding.group.0),
vertex_slab: vertex_slab.unwrap_or_default(),
index_slab,
@ -1009,8 +1137,11 @@ pub fn queue_prepass_material_meshes<M: Material>(
mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
opaque_phase.add(
OpaqueNoLightmap3dBatchSetKey {
draw_function: opaque_draw_prepass,
pipeline: pipeline_id,
draw_function: material
.properties
.prepass_draw_function_id
.unwrap(),
pipeline: *pipeline_id,
material_bind_group_index: Some(material.binding.group.0),
vertex_slab: vertex_slab.unwrap_or_default(),
index_slab,
@ -1026,14 +1157,13 @@ pub fn queue_prepass_material_meshes<M: Material>(
);
}
}
// Alpha mask
MeshPipelineKey::MAY_DISCARD => {
RenderPhaseType::AlphaMask => {
if deferred {
let (vertex_slab, index_slab) =
mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
let batch_set_key = OpaqueNoLightmap3dBatchSetKey {
draw_function: alpha_mask_draw_deferred,
pipeline: pipeline_id,
draw_function: material.properties.deferred_draw_function_id.unwrap(),
pipeline: *pipeline_id,
material_bind_group_index: Some(material.binding.group.0),
vertex_slab: vertex_slab.unwrap_or_default(),
index_slab,
@ -1054,8 +1184,8 @@ pub fn queue_prepass_material_meshes<M: Material>(
let (vertex_slab, index_slab) =
mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
let batch_set_key = OpaqueNoLightmap3dBatchSetKey {
draw_function: alpha_mask_draw_prepass,
pipeline: pipeline_id,
draw_function: material.properties.prepass_draw_function_id.unwrap(),
pipeline: *pipeline_id,
material_bind_group_index: Some(material.binding.group.0),
vertex_slab: vertex_slab.unwrap_or_default(),
index_slab,

View File

@ -5,6 +5,9 @@ use bevy_asset::UntypedAssetId;
use bevy_color::ColorToComponents;
use bevy_core_pipeline::core_3d::{Camera3d, CORE_3D_DEPTH_FORMAT};
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::component::Tick;
use bevy_ecs::entity::EntityHash;
use bevy_ecs::system::SystemChangeTick;
use bevy_ecs::{
entity::{hash_map::EntityHashMap, hash_set::EntityHashSet},
prelude::*,
@ -37,7 +40,7 @@ use bevy_render::{
};
use bevy_transform::{components::GlobalTransform, prelude::Transform};
use bevy_utils::default;
use core::{hash::Hash, ops::Range};
use core::{hash::Hash, marker::PhantomData, ops::Range};
#[cfg(feature = "trace")]
use tracing::info_span;
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
/// adds them to [`BinnedRenderPhase`]s or [`SortedRenderPhase`]s as
/// appropriate.
pub fn queue_shadows<M: Material>(
shadow_draw_functions: Res<DrawFunctions<Shadow>>,
// These will be extracted in the material extraction, which will also clear the needs_specialization
// collection.
pub fn check_light_entities_needing_specialization<M: Material>(
needs_specialization: Query<Entity, (With<MeshMaterial3d<M>>, Changed<NotShadowCaster>)>,
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>>,
(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<RenderMeshInstances>,
Res<RenderAssets<PreparedMaterial<M>>>,
Res<RenderMaterialInstances<M>>,
Res<MaterialBindGroupAllocator<M>>,
),
material_bind_group_allocator: Res<MaterialBindGroupAllocator<M>>,
mut shadow_render_phases: ResMut<ViewBinnedRenderPhases<Shadow>>,
shadow_render_phases: Res<ViewBinnedRenderPhases<Shadow>>,
mut pipelines: ResMut<SpecializedMeshPipelines<PrepassPipeline<M>>>,
pipeline_cache: Res<PipelineCache>,
render_lightmaps: Res<RenderLightmaps>,
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>>,
@ -1607,24 +1684,28 @@ pub fn queue_shadows<M: Material>(
With<ExtractedDirectionalLight>,
>,
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
M::Data: PartialEq + Eq + Hash + Clone,
{
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() {
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 {
if !shadow_render_phases.contains_key(&extracted_view_light.retained_view_entity) {
continue;
}
let Some(light_key) = light_key_cache.get(&view_light_entity) else {
continue;
};
let is_directional_light = matches!(light_entity, LightEntity::Directional { .. });
let visible_entities = match light_entity {
LightEntity::Directional {
light_entity,
@ -1648,14 +1729,25 @@ pub fn queue_shadows<M: Material>(
.get(*light_entity)
.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
// so no meshes will be queued
for (entity, main_entity) in visible_entities.iter().copied() {
let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(main_entity)
for (_, visible_entity) in visible_entities.iter().copied() {
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 {
continue;
};
@ -1665,7 +1757,7 @@ pub fn queue_shadows<M: Material>(
{
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;
};
let Some(material) = render_materials.get(*material_asset_id) else {
@ -1681,14 +1773,17 @@ pub fn queue_shadows<M: Material>(
};
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
// `SetMeshBindGroup` render command will bind the data for it. So
// we need to include the appropriate flag in the mesh pipeline key
// to ensure that the necessary bind group layout entries are
// present.
if render_lightmaps.render_lightmaps.contains_key(&main_entity) {
if render_lightmaps
.render_lightmaps
.contains_key(&visible_entity)
{
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) =
mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
let batch_set_key = ShadowBatchSetKey {
pipeline: pipeline_id,
pipeline: *pipeline_id,
draw_function: draw_shadow_mesh,
vertex_slab: vertex_slab.unwrap_or_default(),
index_slab,

View File

@ -1,5 +1,3 @@
use core::mem::size_of;
use crate::material_bind_groups::{MaterialBindGroupIndex, MaterialBindGroupSlot};
use allocator::MeshAllocator;
use bevy_asset::{load_internal_asset, AssetId, UntypedAssetId};
@ -46,10 +44,14 @@ use bevy_render::{
};
use bevy_transform::components::GlobalTransform;
use bevy_utils::{default, Parallel};
use core::mem::size_of;
use material_bind_groups::MaterialBindingId;
use render::skin::{self, SkinIndex};
use tracing::{error, warn};
use self::irradiance_volume::IRRADIANCE_VOLUMES_ARE_USABLE;
use crate::environment_map::EnvironmentMapLight;
use crate::irradiance_volume::IrradianceVolume;
use crate::{
render::{
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::view::ExtractedView;
use bevy_render::RenderSet::PrepareAssets;
use bytemuck::{Pod, Zeroable};
use nonmax::{NonMaxU16, NonMaxU32};
use smallvec::{smallvec, SmallVec};
use static_assertions::const_assert_eq;
use self::irradiance_volume::IRRADIANCE_VOLUMES_ARE_USABLE;
/// Provides support for rendering 3D meshes.
#[derive(Default)]
pub struct MeshRenderPlugin {
@ -204,8 +214,14 @@ impl Plugin for MeshRenderPlugin {
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
.init_resource::<ViewKeyCache>()
.init_resource::<ViewSpecializationTicks>()
.init_resource::<GpuPreprocessingSupport>()
.init_resource::<SkinUniforms>();
.init_resource::<SkinUniforms>()
.add_systems(
Render,
check_views_need_specialization.in_set(PrepareAssets),
);
let gpu_preprocessing_support =
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)]
pub struct MeshTransforms {
pub world_from_local: Affine3,
@ -568,6 +721,9 @@ pub struct RenderMeshInstanceShared {
pub material_bindings_index: MaterialBindingId,
/// Various flags.
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
@ -666,6 +822,7 @@ impl RenderMeshInstanceShared {
flags: mesh_instance_flags,
// This gets filled in later, during `RenderMeshGpuBuilder::update`.
material_bindings_index: default(),
lightmap_slab_index: None,
}
}
@ -974,6 +1131,11 @@ impl RenderMeshInstanceGpuBuilder {
Some(render_lightmap) => u16::from(*render_lightmap.slot_index),
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.
let mut mesh_input_uniform = MeshInputUniform {

View File

@ -1134,6 +1134,7 @@ pub fn extract_cameras(
})
.collect(),
};
let mut commands = commands.entity(render_entity);
commands.insert((
ExtractedCamera {

View File

@ -2,6 +2,7 @@ use core::fmt::Debug;
use crate::{primitives::Frustum, view::VisibilitySystems};
use bevy_app::{App, Plugin, PostStartup, PostUpdate};
use bevy_asset::AssetEvents;
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::prelude::*;
use bevy_math::{ops, AspectRatio, Mat4, Rect, Vec2, Vec3A, Vec4};
@ -29,7 +30,9 @@ impl Plugin for CameraProjectionPlugin {
.add_systems(
PostUpdate,
(
crate::camera::camera_system.in_set(CameraUpdateSystem),
crate::camera::camera_system
.in_set(CameraUpdateSystem)
.before(AssetEvents),
crate::view::update_frusta
.in_set(VisibilitySystems::UpdateFrusta)
.after(crate::camera::camera_system)

View File

@ -2,7 +2,7 @@ use crate::{
mesh::Mesh,
view::{self, Visibility, VisibilityClass},
};
use bevy_asset::{AssetEvent, AssetId, Handle};
use bevy_asset::{AsAssetId, AssetEvent, AssetId, Handle};
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
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`].
///
/// [`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
/// has changed.
///

View File

@ -13,7 +13,7 @@ use crate::{
};
use allocator::MeshAllocatorPlugin;
use bevy_app::{App, Plugin, PostUpdate};
use bevy_asset::{AssetApp, AssetId, RenderAssetUsages};
use bevy_asset::{AssetApp, AssetEvents, AssetId, RenderAssetUsages};
use bevy_ecs::{
prelude::*,
system::{
@ -72,7 +72,8 @@ impl Plugin for MeshPlugin {
.add_systems(
PostUpdate,
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 {

View File

@ -186,6 +186,16 @@ impl Msaa {
pub fn samples(&self) -> 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.

View File

@ -37,7 +37,7 @@ pub use sprite::*;
pub use texture_slice::*;
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_ecs::prelude::*;
use bevy_image::{prelude::*, TextureAtlasPlugin};
@ -115,7 +115,7 @@ impl Plugin for SpritePlugin {
(
calculate_bounds_2d.in_set(VisibilitySystems::CalculateBounds),
(
compute_slices_on_asset_event,
compute_slices_on_asset_event.before(AssetEvents),
compute_slices_on_sprite_change,
)
.in_set(SpriteSystem::ComputeSlices),

View File

@ -1,22 +1,29 @@
use crate::{
DrawMesh2d, Mesh2d, Mesh2dPipeline, Mesh2dPipelineKey, RenderMesh2dInstances,
SetMesh2dBindGroup, SetMesh2dViewBindGroup,
SetMesh2dBindGroup, SetMesh2dViewBindGroup, ViewKeyCache, ViewSpecializationTicks,
};
use bevy_app::{App, Plugin};
use bevy_asset::{Asset, AssetApp, AssetId, AssetServer, Handle};
use bevy_app::{App, Plugin, PostUpdate};
use bevy_asset::prelude::AssetChanged;
use bevy_asset::{AsAssetId, Asset, AssetApp, AssetEvents, AssetId, AssetServer, Handle};
use bevy_core_pipeline::{
core_2d::{
AlphaMask2d, AlphaMask2dBinKey, BatchSetKey2d, Opaque2d, Opaque2dBinKey, Transparent2d,
},
tonemapping::{DebandDither, Tonemapping},
tonemapping::Tonemapping,
};
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::component::Tick;
use bevy_ecs::entity::EntityHash;
use bevy_ecs::system::SystemChangeTick;
use bevy_ecs::{
prelude::*,
system::{lifetimeless::SRes, SystemParamItem},
};
use bevy_math::FloatOrd;
use bevy_platform_support::collections::HashMap;
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::{
mesh::{MeshVertexBufferLayoutRef, RenderMesh},
@ -35,7 +42,7 @@ use bevy_render::{
},
renderer::RenderDevice,
sync_world::{MainEntity, MainEntityHashMap},
view::{ExtractedView, Msaa, ViewVisibility},
view::{ExtractedView, ViewVisibility},
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
};
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.
/// Currently, this only works with [`Mesh2d`]. Sprites are always transparent.
///
@ -244,22 +259,41 @@ where
{
fn build(&self, app: &mut App) {
app.init_asset::<M>()
.init_resource::<EntitiesNeedingSpecialization<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) {
render_app
.init_resource::<EntitySpecializationTicks<M>>()
.init_resource::<SpecializedMaterial2dPipelineCache<M>>()
.add_render_command::<Opaque2d, DrawMaterial2d<M>>()
.add_render_command::<AlphaMask2d, DrawMaterial2d<M>>()
.add_render_command::<Transparent2d, DrawMaterial2d<M>>()
.init_resource::<RenderMaterial2dInstances<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(
Render,
(
specialize_material2d_meshes::<M>
.in_set(RenderSet::PrepareAssets)
.after(prepare_assets::<PreparedMaterial2d<M>>)
.after(prepare_assets::<RenderMesh>),
queue_material2d_meshes::<M>
.in_set(RenderSet::QueueMeshes)
.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>(
opaque_draw_functions: Res<DrawFunctions<Opaque2d>>,
alpha_mask_draw_functions: Res<DrawFunctions<AlphaMask2d>>,
transparent_draw_functions: Res<DrawFunctions<Transparent2d>>,
pub fn extract_entities_needs_specialization<M>(
entities_needing_specialization: Extract<Res<EntitiesNeedingSpecialization<M>>>,
mut entity_specialization_ticks: ResMut<EntitySpecializationTicks<M>>,
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>>,
mut pipelines: ResMut<SpecializedMeshPipelines<Material2dPipeline<M>>>,
pipeline_cache: Res<PipelineCache>,
@ -524,16 +637,15 @@ pub fn queue_material2d_meshes<M: Material2d>(
),
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<(
&ExtractedView,
&RenderVisibleEntities,
&Msaa,
Option<&Tonemapping>,
Option<&DebandDither>,
)>,
transparent_render_phases: Res<ViewSortedRenderPhases<Transparent2d>>,
opaque_render_phases: Res<ViewBinnedRenderPhases<Opaque2d>>,
alpha_mask_render_phases: Res<ViewBinnedRenderPhases<AlphaMask2d>>,
views: Query<(&MainEntity, &ExtractedView, &RenderVisibleEntities)>,
view_key_cache: Res<ViewKeyCache>,
entity_specialization_ticks: Res<EntitySpecializationTicks<M>>,
view_specialization_ticks: Res<ViewSpecializationTicks>,
ticks: SystemChangeTick,
mut specialized_material_pipeline_cache: ResMut<SpecializedMaterial2dPipelineCache<M>>,
) where
M::Data: PartialEq + Eq + Hash + Clone,
{
@ -541,36 +653,32 @@ pub fn queue_material2d_meshes<M: Material2d>(
return;
}
for (view, visible_entities, msaa, tonemapping, dither) in &views {
let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity)
else {
for (view_entity, view, visible_entities) in &views {
if !transparent_render_phases.contains_key(&view.retained_view_entity)
&& !opaque_render_phases.contains_key(&view.retained_view_entity)
&& !alpha_mask_render_phases.contains_key(&view.retained_view_entity)
{
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 {
}
let Some(view_key) = view_key_cache.get(view_entity) else {
continue;
};
let draw_transparent_2d = transparent_draw_functions.read().id::<DrawMaterial2d<M>>();
let draw_opaque_2d = opaque_draw_functions.read().id::<DrawMaterial2d<M>>();
let draw_alpha_mask_2d = alpha_mask_draw_functions.read().id::<DrawMaterial2d<M>>();
for (_, visible_entity) in visible_entities.iter::<Mesh2d>() {
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 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 {
continue;
};
@ -583,7 +691,7 @@ pub fn queue_material2d_meshes<M: Material2d>(
let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else {
continue;
};
let mesh_key = view_key
let mesh_key = *view_key
| Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology())
| 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();
let mesh_z = mesh_instance.transforms.world_from_local.translation.z;
@ -623,7 +791,7 @@ pub fn queue_material2d_meshes<M: Material2d>(
AlphaMode2d::Opaque => {
let bin_key = Opaque2dBinKey {
pipeline: pipeline_id,
draw_function: draw_opaque_2d,
draw_function: material_2d.properties.draw_function_id,
asset_id: mesh_instance.mesh_asset_id.into(),
material_bind_group_id: material_2d.get_bind_group_id().0,
};
@ -639,7 +807,7 @@ pub fn queue_material2d_meshes<M: Material2d>(
AlphaMode2d::Mask(_) => {
let bin_key = AlphaMask2dBinKey {
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(),
material_bind_group_id: material_2d.get_bind_group_id().0,
};
@ -655,7 +823,7 @@ pub fn queue_material2d_meshes<M: Material2d>(
AlphaMode2d::Blend => {
transparent_phase.add(Transparent2d {
entity: (*render_entity, *visible_entity),
draw_function: draw_transparent_2d,
draw_function: material_2d.properties.draw_function_id,
pipeline: pipeline_id,
// 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
@ -689,6 +857,7 @@ pub struct Material2dProperties {
/// These are precalculated so that we can just "or" them together in
/// [`queue_material2d_meshes`].
pub mesh_pipeline_key_bits: Mesh2dPipelineKey,
pub draw_function_id: DrawFunctionId,
}
/// Data prepared for a [`Material2d`] instance.
@ -708,17 +877,42 @@ impl<T: Material2d> PreparedMaterial2d<T> {
impl<M: Material2d> RenderAsset for PreparedMaterial2d<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(
material: 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>> {
match material.as_bind_group(&pipeline.material2d_layout, render_device, material_param) {
Ok(prepared) => {
let mut mesh_pipeline_key_bits = Mesh2dPipelineKey::empty();
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 {
bindings: prepared.bindings,
bind_group: prepared.bind_group,
@ -727,6 +921,7 @@ impl<M: Material2d> RenderAsset for PreparedMaterial2d<M> {
depth_bias: material.depth_bias(),
alpha_mode: material.alpha_mode(),
mesh_pipeline_key_bits,
draw_function_id,
},
})
}

View File

@ -1,7 +1,8 @@
use bevy_app::Plugin;
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::{
core_2d::{AlphaMask2d, Camera2d, Opaque2d, Transparent2d, CORE_2D_DEPTH_FORMAT},
tonemapping::{
@ -9,6 +10,8 @@ use bevy_core_pipeline::{
},
};
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::component::Tick;
use bevy_ecs::system::SystemChangeTick;
use bevy_ecs::{
prelude::*,
query::ROQueryItem,
@ -16,6 +19,8 @@ use bevy_ecs::{
};
use bevy_image::{BevyDefault, Image, ImageSampler, TextureFormatPixelInfo};
use bevy_math::{Affine3, Vec4};
use bevy_render::prelude::Msaa;
use bevy_render::RenderSet::PrepareAssets;
use bevy_render::{
batching::{
gpu_preprocessing::IndirectParametersMetadata,
@ -94,6 +99,7 @@ impl Plugin for Mesh2dRenderPlugin {
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
.init_resource::<ViewKeyCache>()
.init_resource::<RenderMesh2dInstances>()
.init_resource::<SpecializedMeshPipelines<Mesh2dPipeline>>()
.add_systems(ExtractSchedule, extract_mesh2d)
@ -137,7 +143,13 @@ impl Plugin for Mesh2dRenderPlugin {
render_app
.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
@ -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)]
pub struct Mesh2dTransforms {
pub world_from_local: Affine3,

View File

@ -67,9 +67,9 @@ pub mod prelude {
}
use bevy_app::{prelude::*, Animation};
use bevy_asset::AssetApp;
#[cfg(feature = "default_font")]
use bevy_asset::{load_internal_binary_asset, Handle};
use bevy_asset::{AssetApp, AssetEvents};
use bevy_ecs::prelude::*;
use bevy_render::{
camera::CameraUpdateSystem, view::VisibilitySystems, ExtractSchedule, RenderApp,
@ -124,7 +124,7 @@ impl Plugin for TextPlugin {
.add_systems(
PostUpdate,
(
remove_dropped_font_atlas_sets,
remove_dropped_font_atlas_sets.before(AssetEvents),
detect_text_needs_rerender::<Text2d>,
update_text2d_layout
// Potential conflict: `Assets<Image>`