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:  `many_cubes` trace after:  ## 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.
This commit is contained in:
parent
ac1faf073f
commit
40df1ea4b6
@ -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::<WithLight>.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),
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@ -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::<LightVisibilityClass>)]
|
||||
pub struct DirectionalLight {
|
||||
/// The color of the light.
|
||||
///
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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::<LightVisibilityClass>)]
|
||||
pub struct PointLight {
|
||||
/// The color of this light source.
|
||||
pub color: Color,
|
||||
|
||||
@ -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::<LightVisibilityClass>)]
|
||||
pub struct SpotLight {
|
||||
/// The color of the light.
|
||||
///
|
||||
|
||||
@ -780,7 +780,7 @@ pub fn queue_material_meshes<M: Material>(
|
||||
}
|
||||
|
||||
let rangefinder = view.rangefinder3d();
|
||||
for (render_entity, visible_entity) in visible_entities.iter::<With<Mesh3d>>() {
|
||||
for (render_entity, visible_entity) in visible_entities.iter::<Mesh3d>() {
|
||||
let Some(material_asset_id) = render_material_instances.get(visible_entity) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
@ -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::<MeshletMesh>()
|
||||
.register_asset_loader(MeshletMeshLoader)
|
||||
.add_systems(
|
||||
PostUpdate,
|
||||
check_visibility::<With<MeshletMesh3d>>.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::<MeshletMesh3d>)]
|
||||
pub struct MeshletMesh3d(pub Handle<MeshletMesh>);
|
||||
|
||||
impl From<MeshletMesh3d> for AssetId<MeshletMesh> {
|
||||
|
||||
@ -842,7 +842,7 @@ pub fn queue_prepass_material_meshes<M: Material>(
|
||||
view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS;
|
||||
}
|
||||
|
||||
for (render_entity, visible_entity) in visible_entities.iter::<With<Mesh3d>>() {
|
||||
for (render_entity, visible_entity) in visible_entities.iter::<Mesh3d>() {
|
||||
let Some(material_asset_id) = render_material_instances.get(visible_entity) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
@ -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::<Mesh2d>)]
|
||||
pub struct Mesh2d(pub Handle<Mesh>);
|
||||
|
||||
impl From<Mesh2d> for AssetId<Mesh> {
|
||||
@ -82,7 +86,8 @@ impl From<&Mesh2d> for AssetId<Mesh> {
|
||||
/// ```
|
||||
#[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::<Mesh3d>)]
|
||||
pub struct Mesh3d(pub Handle<Mesh>);
|
||||
|
||||
impl From<Mesh3d> for AssetId<Mesh> {
|
||||
|
||||
@ -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<QF>(&self) -> &[Entity]
|
||||
where
|
||||
QF: 'static,
|
||||
{
|
||||
match self.entities.get(&TypeId::of::<QF>()) {
|
||||
pub fn get(&self, type_id: TypeId) -> &[Entity] {
|
||||
match self.entities.get(&type_id) {
|
||||
Some(entities) => &entities[..],
|
||||
None => &[],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_mut<QF>(&mut self) -> &mut Vec<Entity>
|
||||
where
|
||||
QF: 'static,
|
||||
{
|
||||
self.entities.entry(TypeId::of::<QF>()).or_default()
|
||||
pub fn get_mut(&mut self, type_id: TypeId) -> &mut Vec<Entity> {
|
||||
self.entities.entry(type_id).or_default()
|
||||
}
|
||||
|
||||
pub fn iter<QF>(&self) -> impl DoubleEndedIterator<Item = &Entity>
|
||||
where
|
||||
QF: 'static,
|
||||
{
|
||||
self.get::<QF>().iter()
|
||||
pub fn iter(&self, type_id: TypeId) -> impl DoubleEndedIterator<Item = &Entity> {
|
||||
self.get(type_id).iter()
|
||||
}
|
||||
|
||||
pub fn len<QF>(&self) -> usize
|
||||
where
|
||||
QF: 'static,
|
||||
{
|
||||
self.get::<QF>().len()
|
||||
pub fn len(&self, type_id: TypeId) -> usize {
|
||||
self.get(type_id).len()
|
||||
}
|
||||
|
||||
pub fn is_empty<QF>(&self) -> bool
|
||||
where
|
||||
QF: 'static,
|
||||
{
|
||||
self.get::<QF>().is_empty()
|
||||
pub fn is_empty(&self, type_id: TypeId) -> bool {
|
||||
self.get(type_id).is_empty()
|
||||
}
|
||||
|
||||
pub fn clear<QF>(&mut self)
|
||||
where
|
||||
QF: 'static,
|
||||
{
|
||||
self.get_mut::<QF>().clear();
|
||||
pub fn clear(&mut self, type_id: TypeId) {
|
||||
self.get_mut(type_id).clear();
|
||||
}
|
||||
|
||||
pub fn push<QF>(&mut self, entity: Entity)
|
||||
where
|
||||
QF: 'static,
|
||||
{
|
||||
self.get_mut::<QF>().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::<PreviousVisibleEntities>()
|
||||
.add_systems(
|
||||
PostUpdate,
|
||||
(
|
||||
calculate_bounds.in_set(CalculateBounds),
|
||||
(visibility_propagate_system, reset_view_visibility).in_set(VisibilityPropagate),
|
||||
check_visibility::<With<Mesh3d>>.in_set(CheckVisibility),
|
||||
mark_view_visibility_as_changed_if_necessary.in_set(VisibilityChangeDetect),
|
||||
),
|
||||
);
|
||||
app.register_type::<VisibilityClass>()
|
||||
.configure_sets(
|
||||
PostUpdate,
|
||||
(CalculateBounds, UpdateFrusta, VisibilityPropagate)
|
||||
.before(CheckVisibility)
|
||||
.after(TransformSystem::TransformPropagate),
|
||||
)
|
||||
.init_resource::<PreviousVisibleEntities>()
|
||||
.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<PreviousVisibleEntities>,
|
||||
) {
|
||||
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<QF>(
|
||||
mut thread_queues: Local<Parallel<Vec<Entity>>>,
|
||||
/// 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<Parallel<TypeIdMap<Vec<Entity>>>>,
|
||||
mut view_query: Query<(
|
||||
Entity,
|
||||
&mut VisibleEntities,
|
||||
@ -510,23 +515,20 @@ pub fn check_visibility<QF>(
|
||||
&Camera,
|
||||
Has<NoCpuCulling>,
|
||||
)>,
|
||||
mut visible_aabb_query: Query<
|
||||
(
|
||||
Entity,
|
||||
&InheritedVisibility,
|
||||
&mut ViewVisibility,
|
||||
Option<&RenderLayers>,
|
||||
Option<&Aabb>,
|
||||
&GlobalTransform,
|
||||
Has<NoFrustumCulling>,
|
||||
Has<VisibilityRange>,
|
||||
),
|
||||
QF,
|
||||
>,
|
||||
mut visible_aabb_query: Query<(
|
||||
Entity,
|
||||
&InheritedVisibility,
|
||||
&mut ViewVisibility,
|
||||
&VisibilityClass,
|
||||
Option<&RenderLayers>,
|
||||
Option<&Aabb>,
|
||||
&GlobalTransform,
|
||||
Has<NoFrustumCulling>,
|
||||
Has<VisibilityRange>,
|
||||
)>,
|
||||
visible_entity_ranges: Option<Res<VisibleEntityRanges>>,
|
||||
) where
|
||||
QF: QueryFilter + 'static,
|
||||
{
|
||||
mut previous_visible_entities: ResMut<PreviousVisibleEntities>,
|
||||
) {
|
||||
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<QF>(
|
||||
entity,
|
||||
inherited_visibility,
|
||||
mut view_visibility,
|
||||
visibility_class,
|
||||
maybe_entity_mask,
|
||||
maybe_model_aabb,
|
||||
transform,
|
||||
@ -592,34 +595,70 @@ pub fn check_visibility<QF>(
|
||||
}
|
||||
|
||||
// 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::<QF>();
|
||||
thread_queues.drain_into(visible_entities.get_mut::<QF>());
|
||||
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<PreviousVisibleEntities>,
|
||||
) {
|
||||
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::<MyComponent>)]
|
||||
/// struct MyComponent {
|
||||
/// ...
|
||||
/// }
|
||||
/// ```
|
||||
pub fn add_visibility_class<C>(mut world: DeferredWorld<'_>, entity: Entity, _: ComponentId)
|
||||
where
|
||||
C: 'static,
|
||||
{
|
||||
if let Some(mut visibility_class) = world.get_mut::<VisibilityClass>(entity) {
|
||||
visibility_class.push(TypeId::of::<C>());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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::<With<Mesh3d>>),
|
||||
.before(check_visibility),
|
||||
);
|
||||
|
||||
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
|
||||
|
||||
@ -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::<Sprite>)]
|
||||
pub struct SpriteSource;
|
||||
|
||||
/// A convenient alias for `Or<With<Sprite>, With<SpriteSource>>`, for use with
|
||||
/// [`bevy_render::view::VisibleEntities`].
|
||||
pub type WithSprite = Or<(With<Sprite>, With<SpriteSource>)>;
|
||||
|
||||
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::<With<Mesh2d>>,
|
||||
check_visibility::<WithSprite>,
|
||||
)
|
||||
.in_set(VisibilitySystems::CheckVisibility),
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@ -525,7 +525,7 @@ pub fn queue_material2d_meshes<M: Material2d>(
|
||||
view_key |= Mesh2dPipelineKey::DEBAND_DITHER;
|
||||
}
|
||||
}
|
||||
for (render_entity, visible_entity) in visible_entities.iter::<With<Mesh2d>>() {
|
||||
for (render_entity, visible_entity) in visible_entities.iter::<Mesh2d>() {
|
||||
let Some(material_asset_id) = render_material_instances.get(visible_entity) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
@ -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::<WithSprite>()
|
||||
.iter::<Sprite>()
|
||||
.map(|(_, e)| e.index() as usize),
|
||||
);
|
||||
|
||||
|
||||
@ -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::<Sprite>)]
|
||||
pub struct Sprite {
|
||||
/// The image used to render the sprite
|
||||
pub image: Handle<Image>,
|
||||
|
||||
@ -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::<With<Node>>.in_set(VisibilitySystems::CheckVisibility),
|
||||
update_target_camera_system.in_set(UiSystem::Prepare),
|
||||
ui_layout_system_config,
|
||||
ui_stack_system
|
||||
|
||||
@ -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<Vec2> for ScrollPosition {
|
||||
ScrollPosition,
|
||||
Transform,
|
||||
Visibility,
|
||||
VisibilityClass,
|
||||
ZIndex
|
||||
)]
|
||||
#[reflect(Component, Default, PartialEq, Debug)]
|
||||
#[component(on_add = view::add_visibility_class::<Node>)]
|
||||
#[cfg_attr(
|
||||
feature = "serialize",
|
||||
derive(serde::Serialize, serde::Deserialize),
|
||||
|
||||
@ -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::<With<Mesh2d>>() {
|
||||
for (render_entity, visible_entity) in visible_entities.iter::<Mesh2d>() {
|
||||
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;
|
||||
|
||||
@ -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::<CustomRenderedEntity>)]
|
||||
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<CustomRenderedEntity>;
|
||||
|
||||
/// 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::<CustomRenderedEntity>::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::<WithCustomRenderedEntity>
|
||||
.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::<WithCustomRenderedEntity>()
|
||||
.iter()
|
||||
{
|
||||
for &entity in view_visible_entities.get::<CustomRenderedEntity>().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,
|
||||
|
||||
@ -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<Assets<Mesh>>) {
|
||||
struct CustomRenderedMeshPipelinePlugin;
|
||||
impl Plugin for CustomRenderedMeshPipelinePlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_plugins(ExtractComponentPlugin::<CustomRenderedEntity>::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::<WithCustomRenderedEntity>
|
||||
.in_set(VisibilitySystems::CheckVisibility),
|
||||
);
|
||||
app.add_plugins(ExtractComponentPlugin::<CustomRenderedEntity>::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::<CustomRenderedEntity>)]
|
||||
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<CustomRenderedEntity>;
|
||||
|
||||
// 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::<WithCustomRenderedEntity>()
|
||||
.iter()
|
||||
for &(render_entity, visible_entity) in
|
||||
view_visible_entities.get::<CustomRenderedEntity>().iter()
|
||||
{
|
||||
// Get the mesh instance
|
||||
let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(visible_entity)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user