From 40df1ea4b6b4cc61f547d79db667f4d730453321 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Mon, 16 Dec 2024 20:43:45 -0800 Subject: [PATCH] Remove the type parameter from `check_visibility`, and only invoke it once. (#16812) Currently, `check_visibility` is parameterized over a query filter that specifies the type of potentially-visible object. This has the unfortunate side effect that we need a separate system, `mark_view_visibility_as_changed_if_necessary`, to trigger view visibility change detection. That system is quite slow because it must iterate sequentially over all entities in the scene. This PR moves the query filter from `check_visibility` to a new component, `VisibilityClass`. `VisibilityClass` stores a list of type IDs, each corresponding to one of the query filters we used to use. Because `check_visibility` is no longer specialized to the query filter at the type level, Bevy now only needs to invoke it once, leading to better performance as `check_visibility` can do change detection on the fly rather than delegating it to a separate system. This commit also has ergonomic improvements, as there's no need for applications that want to add their own custom renderable components to add specializations of the `check_visibility` system to the schedule. Instead, they only need to ensure that the `ViewVisibility` component is properly kept up to date. The recommended way to do this, and the way that's demonstrated in the `custom_phase_item` and `specialized_mesh_pipeline` examples, is to make `ViewVisibility` a required component and to add the type ID to it in a component add hook. This patch does this for `Mesh3d`, `Mesh2d`, `Sprite`, `Light`, and `Node`, which means that most app code doesn't need to change at all. Note that, although this patch has a large impact on the performance of visibility determination, it doesn't actually improve the end-to-end frame time of `many_cubes`. That's because the render world was already effectively hiding the latency from `mark_view_visibility_as_changed_if_necessary`. This patch is, however, necessary for *further* improvements to `many_cubes` performance. `many_cubes` trace before: ![Screenshot 2024-12-13 015318](https://github.com/user-attachments/assets/d0b1881b-fb75-4a39-b05d-1a16eabfa2c5) `many_cubes` trace after: ![Screenshot 2024-12-13 145735](https://github.com/user-attachments/assets/0a364289-e942-41bb-9cc2-b05d07e3722d) ## Migration Guide * `check_visibility` no longer takes a `QueryFilter`, and there's no need to add it manually to your app schedule anymore for custom rendering items. Instead, entities with custom renderable components should add the appropriate type IDs to `VisibilityClass`. See `custom_phase_item` for an example. --- crates/bevy_pbr/src/lib.rs | 12 +- .../bevy_pbr/src/light/directional_light.rs | 6 +- crates/bevy_pbr/src/light/mod.rs | 7 +- crates/bevy_pbr/src/light/point_light.rs | 11 +- crates/bevy_pbr/src/light/spot_light.rs | 5 +- crates/bevy_pbr/src/material.rs | 2 +- crates/bevy_pbr/src/meshlet/mod.rs | 16 +- crates/bevy_pbr/src/prepass/mod.rs | 2 +- crates/bevy_render/src/mesh/components.rs | 11 +- crates/bevy_render/src/view/visibility/mod.rs | 255 ++++++++++-------- .../bevy_render/src/view/visibility/range.rs | 3 +- crates/bevy_sprite/src/lib.rs | 13 +- crates/bevy_sprite/src/mesh2d/material.rs | 2 +- crates/bevy_sprite/src/render/mod.rs | 5 +- crates/bevy_sprite/src/sprite.rs | 8 +- crates/bevy_ui/src/lib.rs | 7 +- crates/bevy_ui/src/ui_node.rs | 4 +- examples/2d/mesh2d_manual.rs | 2 +- examples/shader/custom_phase_item.rs | 28 +- examples/shader/specialized_mesh_pipeline.rs | 29 +- 20 files changed, 225 insertions(+), 203 deletions(-) diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index ea3eab5285c31..f219518dfcce2 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -132,7 +132,7 @@ use bevy_render::{ render_resource::Shader, sync_component::SyncComponentPlugin, texture::GpuImage, - view::{check_visibility, VisibilitySystems}, + view::VisibilitySystems, ExtractSchedule, Render, RenderApp, RenderSet, }; @@ -383,8 +383,7 @@ impl Plugin for PbrPlugin { .in_set(SimulationLightSystems::AssignLightsToClusters) .after(TransformSystem::TransformPropagate) .after(VisibilitySystems::CheckVisibility) - .after(CameraUpdateSystem) - .ambiguous_with(VisibilitySystems::VisibilityChangeDetect), + .after(CameraUpdateSystem), clear_directional_light_cascades .in_set(SimulationLightSystems::UpdateDirectionalLightCascades) .after(TransformSystem::TransformPropagate) @@ -398,8 +397,7 @@ impl Plugin for PbrPlugin { // We assume that no entity will be both a directional light and a spot light, // so these systems will run independently of one another. // FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481. - .ambiguous_with(update_spot_light_frusta) - .ambiguous_with(VisibilitySystems::VisibilityChangeDetect), + .ambiguous_with(update_spot_light_frusta), update_point_light_frusta .in_set(SimulationLightSystems::UpdateLightFrusta) .after(TransformSystem::TransformPropagate) @@ -408,7 +406,6 @@ impl Plugin for PbrPlugin { .in_set(SimulationLightSystems::UpdateLightFrusta) .after(TransformSystem::TransformPropagate) .after(SimulationLightSystems::AssignLightsToClusters), - check_visibility::.in_set(VisibilitySystems::CheckVisibility), ( check_dir_light_mesh_visibility, check_point_light_mesh_visibility, @@ -420,8 +417,7 @@ impl Plugin for PbrPlugin { // NOTE: This MUST be scheduled AFTER the core renderer visibility check // because that resets entity `ViewVisibility` for the first view // which would override any results from this otherwise - .after(VisibilitySystems::CheckVisibility) - .ambiguous_with(VisibilitySystems::VisibilityChangeDetect), + .after(VisibilitySystems::CheckVisibility), ), ); diff --git a/crates/bevy_pbr/src/light/directional_light.rs b/crates/bevy_pbr/src/light/directional_light.rs index f351ad7340cac..1eb17ea9abd25 100644 --- a/crates/bevy_pbr/src/light/directional_light.rs +++ b/crates/bevy_pbr/src/light/directional_light.rs @@ -1,4 +1,4 @@ -use bevy_render::view::Visibility; +use bevy_render::view::{self, Visibility}; use super::*; @@ -57,8 +57,10 @@ use super::*; CascadeShadowConfig, CascadesVisibleEntities, Transform, - Visibility + Visibility, + VisibilityClass )] +#[component(on_add = view::add_visibility_class::)] pub struct DirectionalLight { /// The color of the light. /// diff --git a/crates/bevy_pbr/src/light/mod.rs b/crates/bevy_pbr/src/light/mod.rs index a5d6fcf5d0450..87543e1377b72 100644 --- a/crates/bevy_pbr/src/light/mod.rs +++ b/crates/bevy_pbr/src/light/mod.rs @@ -13,8 +13,8 @@ use bevy_render::{ mesh::Mesh3d, primitives::{Aabb, CascadesFrusta, CubemapFrusta, Frustum, Sphere}, view::{ - InheritedVisibility, NoFrustumCulling, RenderLayers, ViewVisibility, VisibilityRange, - VisibleEntityRanges, + InheritedVisibility, NoFrustumCulling, RenderLayers, ViewVisibility, VisibilityClass, + VisibilityRange, VisibleEntityRanges, }, }; use bevy_transform::components::{GlobalTransform, Transform}; @@ -503,6 +503,9 @@ pub enum ShadowFilteringMethod { Temporal, } +/// The [`VisibilityClass`] used for all lights (point, directional, and spot). +pub struct LightVisibilityClass; + /// System sets used to run light-related systems. #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] pub enum SimulationLightSystems { diff --git a/crates/bevy_pbr/src/light/point_light.rs b/crates/bevy_pbr/src/light/point_light.rs index 50097772bbe29..800c7b9bd029a 100644 --- a/crates/bevy_pbr/src/light/point_light.rs +++ b/crates/bevy_pbr/src/light/point_light.rs @@ -1,4 +1,4 @@ -use bevy_render::view::Visibility; +use bevy_render::view::{self, Visibility}; use super::*; @@ -21,7 +21,14 @@ use super::*; /// Source: [Wikipedia](https://en.wikipedia.org/wiki/Lumen_(unit)#Lighting) #[derive(Component, Debug, Clone, Copy, Reflect)] #[reflect(Component, Default, Debug)] -#[require(CubemapFrusta, CubemapVisibleEntities, Transform, Visibility)] +#[require( + CubemapFrusta, + CubemapVisibleEntities, + Transform, + Visibility, + VisibilityClass +)] +#[component(on_add = view::add_visibility_class::)] pub struct PointLight { /// The color of this light source. pub color: Color, diff --git a/crates/bevy_pbr/src/light/spot_light.rs b/crates/bevy_pbr/src/light/spot_light.rs index 90be62c79e565..08160a8cfa0a5 100644 --- a/crates/bevy_pbr/src/light/spot_light.rs +++ b/crates/bevy_pbr/src/light/spot_light.rs @@ -1,4 +1,4 @@ -use bevy_render::view::Visibility; +use bevy_render::view::{self, Visibility}; use super::*; @@ -9,7 +9,8 @@ use super::*; /// the transform, and can be specified with [`Transform::looking_at`](Transform::looking_at). #[derive(Component, Debug, Clone, Copy, Reflect)] #[reflect(Component, Default, Debug)] -#[require(Frustum, VisibleMeshEntities, Transform, Visibility)] +#[require(Frustum, VisibleMeshEntities, Transform, Visibility, VisibilityClass)] +#[component(on_add = view::add_visibility_class::)] pub struct SpotLight { /// The color of the light. /// diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index dda50c59e0d84..b0339bede838d 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -780,7 +780,7 @@ pub fn queue_material_meshes( } let rangefinder = view.rangefinder3d(); - for (render_entity, visible_entity) in visible_entities.iter::>() { + for (render_entity, visible_entity) in visible_entities.iter::() { let Some(material_asset_id) = render_material_instances.get(visible_entity) else { continue; }; diff --git a/crates/bevy_pbr/src/meshlet/mod.rs b/crates/bevy_pbr/src/meshlet/mod.rs index d5c837f2d3efd..0ad880877d1cb 100644 --- a/crates/bevy_pbr/src/meshlet/mod.rs +++ b/crates/bevy_pbr/src/meshlet/mod.rs @@ -59,7 +59,7 @@ use self::{ visibility_buffer_raster_node::MeshletVisibilityBufferRasterPassNode, }; use crate::{graph::NodePbr, Material, MeshMaterial3d, PreviousGlobalTransform}; -use bevy_app::{App, Plugin, PostUpdate}; +use bevy_app::{App, Plugin}; use bevy_asset::{load_internal_asset, AssetApp, AssetId, Handle}; use bevy_core_pipeline::{ core_3d::graph::{Core3d, Node3d}, @@ -70,7 +70,6 @@ use bevy_ecs::{ bundle::Bundle, component::{require, Component}, entity::Entity, - prelude::With, query::Has, reflect::ReflectComponent, schedule::IntoSystemConfigs, @@ -83,8 +82,8 @@ use bevy_render::{ renderer::RenderDevice, settings::WgpuFeatures, view::{ - check_visibility, prepare_view_targets, InheritedVisibility, Msaa, ViewVisibility, - Visibility, VisibilitySystems, + self, prepare_view_targets, InheritedVisibility, Msaa, ViewVisibility, Visibility, + VisibilityClass, }, ExtractSchedule, Render, RenderApp, RenderSet, }; @@ -219,11 +218,7 @@ impl Plugin for MeshletPlugin { ); app.init_asset::() - .register_asset_loader(MeshletMeshLoader) - .add_systems( - PostUpdate, - check_visibility::>.in_set(VisibilitySystems::CheckVisibility), - ); + .register_asset_loader(MeshletMeshLoader); } fn finish(&self, app: &mut App) { @@ -303,7 +298,8 @@ impl Plugin for MeshletPlugin { /// The meshlet mesh equivalent of [`bevy_render::mesh::Mesh3d`]. #[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq, From)] #[reflect(Component, Default)] -#[require(Transform, PreviousGlobalTransform, Visibility)] +#[require(Transform, PreviousGlobalTransform, Visibility, VisibilityClass)] +#[component(on_add = view::add_visibility_class::)] pub struct MeshletMesh3d(pub Handle); impl From for AssetId { diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index f61d6c9a096ea..ac5cbeca4216b 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -842,7 +842,7 @@ pub fn queue_prepass_material_meshes( view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS; } - for (render_entity, visible_entity) in visible_entities.iter::>() { + for (render_entity, visible_entity) in visible_entities.iter::() { let Some(material_asset_id) = render_material_instances.get(visible_entity) else { continue; }; diff --git a/crates/bevy_render/src/mesh/components.rs b/crates/bevy_render/src/mesh/components.rs index d95953358b191..10229be41210d 100644 --- a/crates/bevy_render/src/mesh/components.rs +++ b/crates/bevy_render/src/mesh/components.rs @@ -1,4 +1,7 @@ -use crate::{mesh::Mesh, view::Visibility}; +use crate::{ + mesh::Mesh, + view::{self, Visibility, VisibilityClass}, +}; use bevy_asset::{AssetId, Handle}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{component::Component, prelude::require, reflect::ReflectComponent}; @@ -35,7 +38,8 @@ use derive_more::derive::From; /// ``` #[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq, From)] #[reflect(Component, Default)] -#[require(Transform, Visibility)] +#[require(Transform, Visibility, VisibilityClass)] +#[component(on_add = view::add_visibility_class::)] pub struct Mesh2d(pub Handle); impl From for AssetId { @@ -82,7 +86,8 @@ impl From<&Mesh2d> for AssetId { /// ``` #[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq, From)] #[reflect(Component, Default)] -#[require(Transform, Visibility)] +#[require(Transform, Visibility, VisibilityClass)] +#[component(on_add = view::add_visibility_class::)] pub struct Mesh3d(pub Handle); impl From for AssetId { diff --git a/crates/bevy_render/src/view/visibility/mod.rs b/crates/bevy_render/src/view/visibility/mod.rs index 88a32cd0fb53a..3e991a6cbdde0 100644 --- a/crates/bevy_render/src/view/visibility/mod.rs +++ b/crates/bevy_render/src/view/visibility/mod.rs @@ -5,18 +5,21 @@ mod render_layers; use core::any::TypeId; +use bevy_ecs::component::ComponentId; use bevy_ecs::entity::EntityHashSet; +use bevy_ecs::world::DeferredWorld; +use derive_more::derive::{Deref, DerefMut}; pub use range::*; pub use render_layers::*; use bevy_app::{Plugin, PostUpdate}; use bevy_asset::Assets; -use bevy_derive::{Deref, DerefMut}; -use bevy_ecs::{change_detection::DetectChangesMut as _, prelude::*, query::QueryFilter}; +use bevy_ecs::prelude::*; use bevy_hierarchy::{Children, Parent}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_transform::{components::GlobalTransform, TransformSystem}; use bevy_utils::{Parallel, TypeIdMap}; +use smallvec::SmallVec; use super::NoCpuCulling; use crate::sync_world::MainEntity; @@ -126,6 +129,34 @@ impl InheritedVisibility { } } +/// A bucket into which we group entities for the purposes of visibility. +/// +/// Bevy's various rendering subsystems (3D, 2D, UI, etc.) want to be able to +/// quickly winnow the set of entities to only those that the subsystem is +/// tasked with rendering, to avoid spending time examining irrelevant entities. +/// At the same time, Bevy wants the [`check_visibility`] system to determine +/// all entities' visibilities at the same time, regardless of what rendering +/// subsystem is responsible for drawing them. Additionally, your application +/// may want to add more types of renderable objects that Bevy determines +/// visibility for just as it does for Bevy's built-in objects. +/// +/// The solution to this problem is *visibility classes*. A visibility class is +/// a type, typically the type of a component, that represents the subsystem +/// that renders it: for example, `Mesh3d`, `Mesh2d`, and `Sprite`. The +/// [`VisibilityClass`] component stores the visibility class or classes that +/// the entity belongs to. (Generally, an object will belong to only one +/// visibility class, but in rare cases it may belong to multiple.) +/// +/// When adding a new renderable component, you'll typically want to write an +/// add-component hook that adds the type ID of that component to the +/// [`VisibilityClass`] array. See `custom_phase_item` for an example. +// +// Note: This can't be a `ComponentId` because the visibility classes are copied +// into the render world, and component IDs are per-world. +#[derive(Clone, Component, Default, Reflect, Deref, DerefMut)] +#[reflect(Component, Default)] +pub struct VisibilityClass(pub SmallVec<[TypeId; 1]>); + /// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering. /// /// Each frame, this will be reset to `false` during [`VisibilityPropagate`] systems in [`PostUpdate`]. @@ -219,56 +250,42 @@ pub struct VisibleEntities { } impl VisibleEntities { - pub fn get(&self) -> &[Entity] - where - QF: 'static, - { - match self.entities.get(&TypeId::of::()) { + pub fn get(&self, type_id: TypeId) -> &[Entity] { + match self.entities.get(&type_id) { Some(entities) => &entities[..], None => &[], } } - pub fn get_mut(&mut self) -> &mut Vec - where - QF: 'static, - { - self.entities.entry(TypeId::of::()).or_default() + pub fn get_mut(&mut self, type_id: TypeId) -> &mut Vec { + self.entities.entry(type_id).or_default() } - pub fn iter(&self) -> impl DoubleEndedIterator - where - QF: 'static, - { - self.get::().iter() + pub fn iter(&self, type_id: TypeId) -> impl DoubleEndedIterator { + self.get(type_id).iter() } - pub fn len(&self) -> usize - where - QF: 'static, - { - self.get::().len() + pub fn len(&self, type_id: TypeId) -> usize { + self.get(type_id).len() } - pub fn is_empty(&self) -> bool - where - QF: 'static, - { - self.get::().is_empty() + pub fn is_empty(&self, type_id: TypeId) -> bool { + self.get(type_id).is_empty() } - pub fn clear(&mut self) - where - QF: 'static, - { - self.get_mut::().clear(); + pub fn clear(&mut self, type_id: TypeId) { + self.get_mut(type_id).clear(); } - pub fn push(&mut self, entity: Entity) - where - QF: 'static, - { - self.get_mut::().push(entity); + pub fn clear_all(&mut self) { + // Don't just nuke the hash table; we want to reuse allocations. + for entities in self.entities.values_mut() { + entities.clear(); + } + } + + pub fn push(&mut self, entity: Entity, type_id: TypeId) { + self.get_mut(type_id).push(entity); } } @@ -332,10 +349,6 @@ pub enum VisibilitySystems { /// the order of systems within this set is irrelevant, as [`check_visibility`] /// assumes that its operations are irreversible during the frame. CheckVisibility, - /// Label for the system that runs after visibility checking and marks - /// entities that have gone from visible to invisible, or vice versa, as - /// changed. - VisibilityChangeDetect, } pub struct VisibilityPlugin; @@ -344,24 +357,23 @@ impl Plugin for VisibilityPlugin { fn build(&self, app: &mut bevy_app::App) { use VisibilitySystems::*; - app.configure_sets( - PostUpdate, - (CalculateBounds, UpdateFrusta, VisibilityPropagate) - .before(CheckVisibility) - .after(TransformSystem::TransformPropagate), - ) - .configure_sets(PostUpdate, CheckVisibility.ambiguous_with(CheckVisibility)) - .configure_sets(PostUpdate, VisibilityChangeDetect.after(CheckVisibility)) - .init_resource::() - .add_systems( - PostUpdate, - ( - calculate_bounds.in_set(CalculateBounds), - (visibility_propagate_system, reset_view_visibility).in_set(VisibilityPropagate), - check_visibility::>.in_set(CheckVisibility), - mark_view_visibility_as_changed_if_necessary.in_set(VisibilityChangeDetect), - ), - ); + app.register_type::() + .configure_sets( + PostUpdate, + (CalculateBounds, UpdateFrusta, VisibilityPropagate) + .before(CheckVisibility) + .after(TransformSystem::TransformPropagate), + ) + .init_resource::() + .add_systems( + PostUpdate, + ( + calculate_bounds.in_set(CalculateBounds), + (visibility_propagate_system, reset_view_visibility) + .in_set(VisibilityPropagate), + check_visibility.in_set(CheckVisibility), + ), + ); } } @@ -465,9 +477,6 @@ fn propagate_recursive( } /// Stores all entities that were visible in the previous frame. -/// -/// At the end of visibility checking, we compare the visible entities against -/// these and set the [`ViewVisibility`] change flags accordingly. #[derive(Resource, Default, Deref, DerefMut)] pub struct PreviousVisibleEntities(EntityHashSet); @@ -475,18 +484,16 @@ pub struct PreviousVisibleEntities(EntityHashSet); /// Entities that are visible will be marked as such later this frame /// by a [`VisibilitySystems::CheckVisibility`] system. fn reset_view_visibility( - mut query: Query<(Entity, &mut ViewVisibility)>, + mut query: Query<(Entity, &ViewVisibility)>, mut previous_visible_entities: ResMut, ) { previous_visible_entities.clear(); - query.iter_mut().for_each(|(entity, mut view_visibility)| { + query.iter_mut().for_each(|(entity, view_visibility)| { // Record the entities that were previously visible. if view_visibility.get() { previous_visible_entities.insert(entity); } - - *view_visibility.bypass_change_detection() = ViewVisibility::HIDDEN; }); } @@ -496,12 +503,10 @@ fn reset_view_visibility( /// frame, it updates the [`ViewVisibility`] of all entities, and for each view /// also compute the [`VisibleEntities`] for that view. /// -/// This system needs to be run for each type of renderable entity. If you add a -/// new type of renderable entity, you'll need to add an instantiation of this -/// system to the [`VisibilitySystems::CheckVisibility`] set so that Bevy will -/// detect visibility properly for those entities. -pub fn check_visibility( - mut thread_queues: Local>>, +/// To ensure that an entity is checked for visibility, make sure that it has a +/// [`VisibilityClass`] component and that that component is nonempty. +pub fn check_visibility( + mut thread_queues: Local>>>, mut view_query: Query<( Entity, &mut VisibleEntities, @@ -510,23 +515,20 @@ pub fn check_visibility( &Camera, Has, )>, - mut visible_aabb_query: Query< - ( - Entity, - &InheritedVisibility, - &mut ViewVisibility, - Option<&RenderLayers>, - Option<&Aabb>, - &GlobalTransform, - Has, - Has, - ), - QF, - >, + mut visible_aabb_query: Query<( + Entity, + &InheritedVisibility, + &mut ViewVisibility, + &VisibilityClass, + Option<&RenderLayers>, + Option<&Aabb>, + &GlobalTransform, + Has, + Has, + )>, visible_entity_ranges: Option>, -) where - QF: QueryFilter + 'static, -{ + mut previous_visible_entities: ResMut, +) { let visible_entity_ranges = visible_entity_ranges.as_deref(); for (view, mut visible_entities, frustum, maybe_view_mask, camera, no_cpu_culling) in @@ -545,6 +547,7 @@ pub fn check_visibility( entity, inherited_visibility, mut view_visibility, + visibility_class, maybe_entity_mask, maybe_model_aabb, transform, @@ -592,34 +595,70 @@ pub fn check_visibility( } // Make sure we don't trigger changed notifications - // unnecessarily. - view_visibility.bypass_change_detection().set(); + // unnecessarily by checking whether the flag is set before + // setting it. + if !**view_visibility { + view_visibility.set(); + } - queue.push(entity); + // Add the entity to the queue for all visibility classes the + // entity is in. + for visibility_class_id in visibility_class.iter() { + queue.entry(*visibility_class_id).or_default().push(entity); + } }, ); - visible_entities.clear::(); - thread_queues.drain_into(visible_entities.get_mut::()); + visible_entities.clear_all(); + + // Drain all the thread queues into the `visible_entities` list. + for class_queues in thread_queues.iter_mut() { + for (class, entities) in class_queues { + let visible_entities_for_class = visible_entities.get_mut(*class); + for entity in entities.drain(..) { + // As we mark entities as visible, we remove them from the + // `previous_visible_entities` list. At the end, all of the + // entities remaining in `previous_visible_entities` will be + // entities that were visible last frame but are no longer + // visible this frame. + previous_visible_entities.remove(&entity); + + visible_entities_for_class.push(entity); + } + } + } + } + + // Now whatever previous visible entities are left are entities that were + // visible last frame but just became invisible. + for entity in previous_visible_entities.drain() { + if let Ok((_, _, mut view_visibility, _, _, _, _, _, _)) = + visible_aabb_query.get_mut(entity) + { + *view_visibility = ViewVisibility::HIDDEN; + } } } -/// A system that marks [`ViewVisibility`] components as changed if their -/// visibility changed this frame. +/// A generic component add hook that automatically adds the appropriate +/// [`VisibilityClass`] to an entity. /// -/// Ordinary change detection doesn't work for this use case because we use -/// multiple [`check_visibility`] systems, and visibility is set if *any one* of -/// those systems judges the entity to be visible. Thus, in order to perform -/// change detection, we need this system, which is a follow-up pass that has a -/// global view of whether the entity became visible or not. -fn mark_view_visibility_as_changed_if_necessary( - mut query: Query<(Entity, &mut ViewVisibility)>, - previous_visible_entities: Res, -) { - for (entity, mut view_visibility) in query.iter_mut() { - if previous_visible_entities.contains(&entity) != view_visibility.get() { - view_visibility.set_changed(); - } +/// This can be handy when creating custom renderable components. To use this +/// hook, add it to your renderable component like this: +/// +/// ```ignore +/// #[derive(Component)] +/// #[component(on_add = add_visibility_class::)] +/// struct MyComponent { +/// ... +/// } +/// ``` +pub fn add_visibility_class(mut world: DeferredWorld<'_>, entity: Entity, _: ComponentId) +where + C: 'static, +{ + if let Some(mut visibility_class) = world.get_mut::(entity) { + visibility_class.push(TypeId::of::()); } } diff --git a/crates/bevy_render/src/view/visibility/range.rs b/crates/bevy_render/src/view/visibility/range.rs index 9ea69db7f9770..d62a9ffb69c5e 100644 --- a/crates/bevy_render/src/view/visibility/range.rs +++ b/crates/bevy_render/src/view/visibility/range.rs @@ -27,7 +27,6 @@ use super::{check_visibility, VisibilitySystems}; use crate::sync_world::{MainEntity, MainEntityHashMap}; use crate::{ camera::Camera, - mesh::Mesh3d, primitives::Aabb, render_resource::BufferVec, renderer::{RenderDevice, RenderQueue}, @@ -59,7 +58,7 @@ impl Plugin for VisibilityRangePlugin { PostUpdate, check_visibility_ranges .in_set(VisibilitySystems::CheckVisibility) - .before(check_visibility::>), + .before(check_visibility), ); let Some(render_app) = app.get_sub_app_mut(RenderApp) else { diff --git a/crates/bevy_sprite/src/lib.rs b/crates/bevy_sprite/src/lib.rs index 40b987f2bf9fe..39204eb4cc9e0 100644 --- a/crates/bevy_sprite/src/lib.rs +++ b/crates/bevy_sprite/src/lib.rs @@ -59,7 +59,7 @@ use bevy_render::{ primitives::Aabb, render_phase::AddRenderCommand, render_resource::{Shader, SpecializedRenderPipelines}, - view::{check_visibility, NoFrustumCulling, VisibilitySystems}, + view::{self, NoFrustumCulling, VisibilityClass, VisibilitySystems}, ExtractSchedule, Render, RenderApp, RenderSet, }; @@ -96,12 +96,10 @@ pub enum SpriteSystem { /// Right now, this is used for `Text`. #[derive(Component, Reflect, Clone, Copy, Debug, Default)] #[reflect(Component, Default, Debug)] +#[require(VisibilityClass)] +#[component(on_add = view::add_visibility_class::)] pub struct SpriteSource; -/// A convenient alias for `Or, With>`, for use with -/// [`bevy_render::view::VisibleEntities`]. -pub type WithSprite = Or<(With, With)>; - impl Plugin for SpritePlugin { fn build(&self, app: &mut App) { load_internal_asset!( @@ -139,11 +137,6 @@ impl Plugin for SpritePlugin { compute_slices_on_sprite_change, ) .in_set(SpriteSystem::ComputeSlices), - ( - check_visibility::>, - check_visibility::, - ) - .in_set(VisibilitySystems::CheckVisibility), ), ); diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index 8b1a31ac9c56d..fcc830301c554 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -525,7 +525,7 @@ pub fn queue_material2d_meshes( view_key |= Mesh2dPipelineKey::DEBAND_DITHER; } } - for (render_entity, visible_entity) in visible_entities.iter::>() { + for (render_entity, visible_entity) in visible_entities.iter::() { let Some(material_asset_id) = render_material_instances.get(visible_entity) else { continue; }; diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index a5569013ec4ed..32ad0d1647ecd 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -1,8 +1,7 @@ use core::ops::Range; use crate::{ - texture_atlas::TextureAtlasLayout, ComputedTextureSlices, Sprite, WithSprite, - SPRITE_SHADER_HANDLE, + texture_atlas::TextureAtlasLayout, ComputedTextureSlices, Sprite, SPRITE_SHADER_HANDLE, }; use bevy_asset::{AssetEvent, AssetId, Assets}; use bevy_color::{ColorToComponents, LinearRgba}; @@ -551,7 +550,7 @@ pub fn queue_sprites( view_entities.clear(); view_entities.extend( visible_entities - .iter::() + .iter::() .map(|(_, e)| e.index() as usize), ); diff --git a/crates/bevy_sprite/src/sprite.rs b/crates/bevy_sprite/src/sprite.rs index 967877d2d79fa..c6550f51d7e62 100644 --- a/crates/bevy_sprite/src/sprite.rs +++ b/crates/bevy_sprite/src/sprite.rs @@ -7,15 +7,19 @@ use bevy_ecs::{ use bevy_image::Image; use bevy_math::{Rect, UVec2, Vec2}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; -use bevy_render::{sync_world::SyncToRenderWorld, view::Visibility}; +use bevy_render::{ + sync_world::SyncToRenderWorld, + view::{self, Visibility, VisibilityClass}, +}; use bevy_transform::components::Transform; use crate::{TextureAtlas, TextureAtlasLayout, TextureSlicer}; /// Describes a sprite to be rendered to a 2D camera #[derive(Component, Debug, Default, Clone, Reflect)] -#[require(Transform, Visibility, SyncToRenderWorld)] +#[require(Transform, Visibility, SyncToRenderWorld, VisibilityClass)] #[reflect(Component, Default, Debug)] +#[component(on_add = view::add_visibility_class::)] pub struct Sprite { /// The image used to render the sprite pub image: Handle, diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index 842306fdf56b3..508f133b81cd9 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -72,11 +72,7 @@ pub mod prelude { use bevy_app::{prelude::*, Animation}; use bevy_ecs::prelude::*; use bevy_input::InputSystem; -use bevy_render::{ - camera::CameraUpdateSystem, - view::{check_visibility, VisibilitySystems}, - RenderApp, -}; +use bevy_render::{camera::CameraUpdateSystem, RenderApp}; use bevy_transform::TransformSystem; use layout::ui_surface::UiSurface; use stack::ui_stack_system; @@ -203,7 +199,6 @@ impl Plugin for UiPlugin { app.add_systems( PostUpdate, ( - check_visibility::>.in_set(VisibilitySystems::CheckVisibility), update_target_camera_system.in_set(UiSystem::Prepare), ui_layout_system_config, ui_stack_system diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index 1f2428d216e81..11080199365d2 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -6,7 +6,7 @@ use bevy_math::{vec4, Rect, Vec2, Vec4Swizzles}; use bevy_reflect::prelude::*; use bevy_render::{ camera::{Camera, RenderTarget}, - view::Visibility, + view::{self, Visibility, VisibilityClass}, }; use bevy_sprite::BorderRect; use bevy_transform::components::Transform; @@ -328,9 +328,11 @@ impl From for ScrollPosition { ScrollPosition, Transform, Visibility, + VisibilityClass, ZIndex )] #[reflect(Component, Default, PartialEq, Debug)] +#[component(on_add = view::add_visibility_class::)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), diff --git a/examples/2d/mesh2d_manual.rs b/examples/2d/mesh2d_manual.rs index bf7ef52d3d5c1..9870d48e6589b 100644 --- a/examples/2d/mesh2d_manual.rs +++ b/examples/2d/mesh2d_manual.rs @@ -386,7 +386,7 @@ pub fn queue_colored_mesh2d( | Mesh2dPipelineKey::from_hdr(view.hdr); // Queue all entities visible to that view - for (render_entity, visible_entity) in visible_entities.iter::>() { + for (render_entity, visible_entity) in visible_entities.iter::() { if let Some(mesh_instance) = render_mesh_instances.get(visible_entity) { let mesh2d_handle = mesh_instance.mesh_asset_id; let mesh2d_transforms = &mesh_instance.transforms; diff --git a/examples/shader/custom_phase_item.rs b/examples/shader/custom_phase_item.rs index 67e3e16d51d8e..3eb8b6a754279 100644 --- a/examples/shader/custom_phase_item.rs +++ b/examples/shader/custom_phase_item.rs @@ -29,7 +29,7 @@ use bevy::{ VertexFormat, VertexState, VertexStepMode, }, renderer::{RenderDevice, RenderQueue}, - view::{self, ExtractedView, RenderVisibleEntities, VisibilitySystems}, + view::{self, ExtractedView, RenderVisibleEntities, VisibilityClass}, Render, RenderApp, RenderSet, }, }; @@ -38,9 +38,13 @@ use bytemuck::{Pod, Zeroable}; /// A marker component that represents an entity that is to be rendered using /// our custom phase item. /// -/// Note the [`ExtractComponent`] trait implementation. This is necessary to -/// tell Bevy that this object should be pulled into the render world. +/// Note the [`ExtractComponent`] trait implementation: this is necessary to +/// tell Bevy that this object should be pulled into the render world. Also note +/// the `on_add` hook, which is needed to tell Bevy's `check_visibility` system +/// that entities with this component need to be examined for visibility. #[derive(Clone, Component, ExtractComponent)] +#[require(VisibilityClass)] +#[component(on_add = view::add_visibility_class::)] struct CustomRenderedEntity; /// Holds a reference to our shader. @@ -152,10 +156,6 @@ impl Vertex { /// the render phase. type DrawCustomPhaseItemCommands = (SetItemPipeline, DrawCustomPhaseItem); -/// A query filter that tells [`view::check_visibility`] about our custom -/// rendered entity. -type WithCustomRenderedEntity = With; - /// A single triangle's worth of vertices, for demonstration purposes. static VERTICES: [Vertex; 3] = [ Vertex::new(vec3(-0.866, -0.5, 0.5), vec3(1.0, 0.0, 0.0)), @@ -168,14 +168,7 @@ fn main() { let mut app = App::new(); app.add_plugins(DefaultPlugins) .add_plugins(ExtractComponentPlugin::::default()) - .add_systems(Startup, setup) - // Make sure to tell Bevy to check our entity for visibility. Bevy won't - // do this by default, for efficiency reasons. - .add_systems( - PostUpdate, - view::check_visibility:: - .in_set(VisibilitySystems::CheckVisibility), - ); + .add_systems(Startup, setup); // We make sure to add these to the render app, not the main app. app.get_sub_app_mut(RenderApp) @@ -246,10 +239,7 @@ fn queue_custom_phase_item( // Find all the custom rendered entities that are visible from this // view. - for &entity in view_visible_entities - .get::() - .iter() - { + for &entity in view_visible_entities.get::().iter() { // Ordinarily, the [`SpecializedRenderPipeline::Key`] would contain // some per-view settings, such as whether the view is HDR, but for // simplicity's sake we simply hard-code the view's characteristics, diff --git a/examples/shader/specialized_mesh_pipeline.rs b/examples/shader/specialized_mesh_pipeline.rs index 8fe2eeb00e357..8e9913eb2b221 100644 --- a/examples/shader/specialized_mesh_pipeline.rs +++ b/examples/shader/specialized_mesh_pipeline.rs @@ -28,7 +28,7 @@ use bevy::{ RenderPipelineDescriptor, SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines, TextureFormat, VertexState, }, - view::{self, ExtractedView, RenderVisibleEntities, ViewTarget, VisibilitySystems}, + view::{self, ExtractedView, RenderVisibleEntities, ViewTarget, VisibilityClass}, Render, RenderApp, RenderSet, }, }; @@ -97,15 +97,7 @@ fn setup(mut commands: Commands, mut meshes: ResMut>) { struct CustomRenderedMeshPipelinePlugin; impl Plugin for CustomRenderedMeshPipelinePlugin { fn build(&self, app: &mut App) { - app.add_plugins(ExtractComponentPlugin::::default()) - .add_systems( - PostUpdate, - // Make sure to tell Bevy to check our entity for visibility. Bevy won't - // do this by default, for efficiency reasons. - // This will do things like frustum culling and hierarchy visibility - view::check_visibility:: - .in_set(VisibilitySystems::CheckVisibility), - ); + app.add_plugins(ExtractComponentPlugin::::default()); // We make sure to add these to the render app, not the main app. let Some(render_app) = app.get_sub_app_mut(RenderApp) else { @@ -132,9 +124,13 @@ impl Plugin for CustomRenderedMeshPipelinePlugin { /// A marker component that represents an entity that is to be rendered using /// our specialized pipeline. /// -/// Note the [`ExtractComponent`] trait implementation. This is necessary to -/// tell Bevy that this object should be pulled into the render world. +/// Note the [`ExtractComponent`] trait implementation: this is necessary to +/// tell Bevy that this object should be pulled into the render world. Also note +/// the `on_add` hook, which is needed to tell Bevy's `check_visibility` system +/// that entities with this component need to be examined for visibility. #[derive(Clone, Component, ExtractComponent)] +#[require(VisibilityClass)] +#[component(on_add = view::add_visibility_class::)] struct CustomRenderedEntity; /// The custom draw commands that Bevy executes for each entity we enqueue into @@ -150,10 +146,6 @@ type DrawSpecializedPipelineCommands = ( DrawMesh, ); -/// A query filter that tells [`view::check_visibility`] about our custom -/// rendered entity. -type WithCustomRenderedEntity = With; - // This contains the state needed to specialize a mesh pipeline #[derive(Resource)] struct CustomMeshPipeline { @@ -299,9 +291,8 @@ fn queue_custom_mesh_pipeline( // Find all the custom rendered entities that are visible from this // view. - for &(render_entity, visible_entity) in view_visible_entities - .get::() - .iter() + for &(render_entity, visible_entity) in + view_visible_entities.get::().iter() { // Get the mesh instance let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(visible_entity)