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.
This commit is contained in:
Patrick Walton 2024-12-16 20:43:45 -08:00 committed by GitHub
parent ac1faf073f
commit 40df1ea4b6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 225 additions and 203 deletions

View File

@ -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),
),
);

View File

@ -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.
///

View File

@ -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 {

View File

@ -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,

View File

@ -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.
///

View File

@ -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;
};

View File

@ -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> {

View File

@ -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;
};

View File

@ -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> {

View File

@ -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,22 +357,21 @@ impl Plugin for VisibilityPlugin {
fn build(&self, app: &mut bevy_app::App) {
use VisibilitySystems::*;
app.configure_sets(
app.register_type::<VisibilityClass>()
.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),
(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<
(
mut visible_aabb_query: Query<(
Entity,
&InheritedVisibility,
&mut ViewVisibility,
&VisibilityClass,
Option<&RenderLayers>,
Option<&Aabb>,
&GlobalTransform,
Has<NoFrustumCulling>,
Has<VisibilityRange>,
),
QF,
>,
)>,
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,37 +595,73 @@ 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);
}
}
}
}
/// A system that marks [`ViewVisibility`] components as changed if their
/// visibility changed this frame.
///
/// 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();
// 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 generic component add hook that automatically adds the appropriate
/// [`VisibilityClass`] to an entity.
///
/// 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>());
}
}
#[cfg(test)]
mod test {
use super::*;

View File

@ -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 {

View File

@ -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),
),
);

View File

@ -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;
};

View File

@ -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),
);

View File

@ -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>,

View File

@ -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

View File

@ -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),

View File

@ -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;

View File

@ -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,

View File

@ -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)