Split ComputedVisibility into two components to allow for accurate change detection and speed up visibility propagation (#9497)

# Objective

Fix #8267.
Fixes half of #7840.

The `ComputedVisibility` component contains two flags: hierarchy
visibility, and view visibility (whether its visible to any cameras).
Due to the modular and open-ended way that view visibility is computed,
it triggers change detection every single frame, even when the value
does not change. Since hierarchy visibility is stored in the same
component as view visibility, this means that change detection for
inherited visibility is completely broken.

At the company I work for, this has become a real issue. We are using
change detection to only re-render scenes when necessary. The broken
state of change detection for computed visibility means that we have to
to rely on the non-inherited `Visibility` component for now. This is
workable in the early stages of our project, but since we will
inevitably want to use the hierarchy, we will have to either:

1. Roll our own solution for computed visibility.
2. Fix the issue for everyone.

## Solution

Split the `ComputedVisibility` component into two: `InheritedVisibilty`
and `ViewVisibility`.
This allows change detection to behave properly for
`InheritedVisibility`.
View visiblity is still erratic, although it is less useful to be able
to detect changes
for this flavor of visibility.

Overall, this actually simplifies the API. Since the visibility system
consists of
self-explaining components, it is much easier to document the behavior
and usage.
This approach is more modular and "ECS-like" -- one could
strip out the `ViewVisibility` component entirely if it's not needed,
and rely only on inherited visibility.

---

## Changelog

- `ComputedVisibility` has been removed in favor of:
`InheritedVisibility` and `ViewVisiblity`.

## Migration Guide

The `ComputedVisibilty` component has been split into
`InheritedVisiblity` and
`ViewVisibility`. Replace any usages of
`ComputedVisibility::is_visible_in_hierarchy`
with `InheritedVisibility::get`, and replace
`ComputedVisibility::is_visible_in_view`
 with `ViewVisibility::get`.
 
 ```rust
 // Before:
 commands.spawn(VisibilityBundle {
     visibility: Visibility::Inherited,
     computed_visibility: ComputedVisibility::default(),
 });
 
 // After:
 commands.spawn(VisibilityBundle {
     visibility: Visibility::Inherited,
     inherited_visibility: InheritedVisibility::default(),
     view_visibility: ViewVisibility::default(),
 });
 ```
 
 ```rust
 // Before:
 fn my_system(q: Query<&ComputedVisibilty>) {
     for vis in &q {
         if vis.is_visible_in_hierarchy() {
     
 // After:
 fn my_system(q: Query<&InheritedVisibility>) {
     for inherited_visibility in &q {
         if inherited_visibility.get() {
 ```
 
 ```rust
 // Before:
 fn my_system(q: Query<&ComputedVisibilty>) {
     for vis in &q {
         if vis.is_visible_in_view() {
     
 // After:
 fn my_system(q: Query<&ViewVisibility>) {
     for view_visibility in &q {
         if view_visibility.get() {
 ```
 
 ```rust
 // Before:
 fn my_system(mut q: Query<&mut ComputedVisibilty>) {
     for vis in &mut q {
         vis.set_visible_in_view();
     
 // After:
 fn my_system(mut q: Query<&mut ViewVisibility>) {
     for view_visibility in &mut q {
         view_visibility.set();
 ```

---------

Co-authored-by: Robert Swain <robert.swain@gmail.com>
This commit is contained in:
Joseph 2023-09-01 06:00:18 -07:00 committed by GitHub
parent 02025eff0b
commit 02b520b4e8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 473 additions and 318 deletions

View file

@ -49,7 +49,7 @@ impl<T> Default for ReportHierarchyIssue<T> {
/// which parent hasn't a `T` component.
///
/// Hierarchy propagations are top-down, and limited only to entities
/// with a specific component (such as `ComputedVisibility` and `GlobalTransform`).
/// with a specific component (such as `InheritedVisibility` and `GlobalTransform`).
/// This means that entities with one of those component
/// and a parent without the same component is probably a programming error.
/// (See B0004 explanation linked in warning message)

View file

@ -8,7 +8,7 @@ use bevy_reflect::Reflect;
use bevy_render::{
mesh::Mesh,
primitives::{CascadesFrusta, CubemapFrusta, Frustum},
view::{ComputedVisibility, Visibility, VisibleEntities},
view::{InheritedVisibility, ViewVisibility, Visibility, VisibleEntities},
};
use bevy_transform::components::{GlobalTransform, Transform};
use bevy_utils::HashMap;
@ -25,8 +25,10 @@ pub struct MaterialMeshBundle<M: Material> {
pub global_transform: GlobalTransform,
/// User indication of whether an entity is visible
pub visibility: Visibility,
/// Inherited visibility of an entity.
pub inherited_visibility: InheritedVisibility,
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
pub computed_visibility: ComputedVisibility,
pub view_visibility: ViewVisibility,
}
impl<M: Material> Default for MaterialMeshBundle<M> {
@ -37,7 +39,8 @@ impl<M: Material> Default for MaterialMeshBundle<M> {
transform: Default::default(),
global_transform: Default::default(),
visibility: Default::default(),
computed_visibility: Default::default(),
inherited_visibility: Default::default(),
view_visibility: Default::default(),
}
}
}
@ -85,8 +88,10 @@ pub struct PointLightBundle {
pub global_transform: GlobalTransform,
/// Enables or disables the light
pub visibility: Visibility,
/// Inherited visibility of an entity.
pub inherited_visibility: InheritedVisibility,
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
pub computed_visibility: ComputedVisibility,
pub view_visibility: ViewVisibility,
}
/// A component bundle for spot light entities
@ -99,8 +104,10 @@ pub struct SpotLightBundle {
pub global_transform: GlobalTransform,
/// Enables or disables the light
pub visibility: Visibility,
/// Inherited visibility of an entity.
pub inherited_visibility: InheritedVisibility,
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
pub computed_visibility: ComputedVisibility,
pub view_visibility: ViewVisibility,
}
/// A component bundle for [`DirectionalLight`] entities.
@ -115,6 +122,8 @@ pub struct DirectionalLightBundle {
pub global_transform: GlobalTransform,
/// Enables or disables the light
pub visibility: Visibility,
/// Inherited visibility of an entity.
pub visible_in_hieararchy: InheritedVisibility,
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
pub computed_visibility: ComputedVisibility,
pub view_visibility: ViewVisibility,
}

View file

@ -219,7 +219,7 @@ impl Plugin for PbrPlugin {
.after(CameraUpdateSystem),
update_directional_light_frusta
.in_set(SimulationLightSystems::UpdateLightFrusta)
// This must run after CheckVisibility because it relies on ComputedVisibility::is_visible()
// This must run after CheckVisibility because it relies on `ViewVisibility`
.after(VisibilitySystems::CheckVisibility)
.after(TransformSystem::TransformPropagate)
.after(SimulationLightSystems::UpdateDirectionalLightCascades)
@ -241,7 +241,7 @@ impl Plugin for PbrPlugin {
.after(TransformSystem::TransformPropagate)
.after(SimulationLightSystems::UpdateLightFrusta)
// NOTE: This MUST be scheduled AFTER the core renderer visibility check
// because that resets entity ComputedVisibility for the first view
// because that resets entity `ViewVisibility` for the first view
// which would override any results from this otherwise
.after(VisibilitySystems::CheckVisibility),
),

View file

@ -11,7 +11,7 @@ use bevy_render::{
primitives::{Aabb, CascadesFrusta, CubemapFrusta, Frustum, HalfSpace, Sphere},
render_resource::BufferBindingType,
renderer::RenderDevice,
view::{ComputedVisibility, RenderLayers, VisibleEntities},
view::{InheritedVisibility, RenderLayers, ViewVisibility, VisibleEntities},
};
use bevy_transform::{components::GlobalTransform, prelude::Transform};
use bevy_utils::{tracing::warn, HashMap};
@ -1178,8 +1178,8 @@ pub(crate) fn assign_lights_to_clusters(
&mut Clusters,
Option<&mut VisiblePointLights>,
)>,
point_lights_query: Query<(Entity, &GlobalTransform, &PointLight, &ComputedVisibility)>,
spot_lights_query: Query<(Entity, &GlobalTransform, &SpotLight, &ComputedVisibility)>,
point_lights_query: Query<(Entity, &GlobalTransform, &PointLight, &ViewVisibility)>,
spot_lights_query: Query<(Entity, &GlobalTransform, &SpotLight, &ViewVisibility)>,
mut lights: Local<Vec<PointLightAssignmentData>>,
mut cluster_aabb_spheres: Local<Vec<Option<Sphere>>>,
mut max_point_lights_warning_emitted: Local<bool>,
@ -1196,7 +1196,7 @@ pub(crate) fn assign_lights_to_clusters(
lights.extend(
point_lights_query
.iter()
.filter(|(.., visibility)| visibility.is_visible())
.filter(|(.., visibility)| visibility.get())
.map(
|(entity, transform, point_light, _visibility)| PointLightAssignmentData {
entity,
@ -1210,7 +1210,7 @@ pub(crate) fn assign_lights_to_clusters(
lights.extend(
spot_lights_query
.iter()
.filter(|(.., visibility)| visibility.is_visible())
.filter(|(.., visibility)| visibility.get())
.map(
|(entity, transform, spot_light, _visibility)| PointLightAssignmentData {
entity,
@ -1797,7 +1797,7 @@ pub fn update_directional_light_frusta(
(
&Cascades,
&DirectionalLight,
&ComputedVisibility,
&ViewVisibility,
&mut CascadesFrusta,
),
(
@ -1810,7 +1810,7 @@ pub fn update_directional_light_frusta(
// The frustum is used for culling meshes to the light for shadow mapping
// so if shadow mapping is disabled for this light, then the frustum is
// not needed.
if !directional_light.shadows_enabled || !visibility.is_visible() {
if !directional_light.shadows_enabled || !visibility.get() {
continue;
}
@ -1931,14 +1931,15 @@ pub fn check_light_mesh_visibility(
&CascadesFrusta,
&mut CascadesVisibleEntities,
Option<&RenderLayers>,
&mut ComputedVisibility,
&mut ViewVisibility,
),
Without<SpotLight>,
>,
mut visible_entity_query: Query<
(
Entity,
&mut ComputedVisibility,
&InheritedVisibility,
&mut ViewVisibility,
Option<&RenderLayers>,
Option<&Aabb>,
Option<&GlobalTransform>,
@ -1963,13 +1964,8 @@ pub fn check_light_mesh_visibility(
}
// Directional lights
for (
directional_light,
frusta,
mut visible_entities,
maybe_view_mask,
light_computed_visibility,
) in &mut directional_lights
for (directional_light, frusta, mut visible_entities, maybe_view_mask, light_view_visibility) in
&mut directional_lights
{
// Re-use already allocated entries where possible.
let mut views_to_remove = Vec::new();
@ -1995,16 +1991,22 @@ pub fn check_light_mesh_visibility(
}
// NOTE: If shadow mapping is disabled for the light then it must have no visible entities
if !directional_light.shadows_enabled || !light_computed_visibility.is_visible() {
if !directional_light.shadows_enabled || !light_view_visibility.get() {
continue;
}
let view_mask = maybe_view_mask.copied().unwrap_or_default();
for (entity, mut computed_visibility, maybe_entity_mask, maybe_aabb, maybe_transform) in
&mut visible_entity_query
for (
entity,
inherited_visibility,
mut view_visibility,
maybe_entity_mask,
maybe_aabb,
maybe_transform,
) in &mut visible_entity_query
{
if !computed_visibility.is_visible_in_hierarchy() {
if !inherited_visibility.get() {
continue;
}
@ -2029,12 +2031,12 @@ pub fn check_light_mesh_visibility(
continue;
}
computed_visibility.set_visible_in_view();
view_visibility.set();
frustum_visible_entities.entities.push(entity);
}
}
} else {
computed_visibility.set_visible_in_view();
view_visibility.set();
for view in frusta.frusta.keys() {
let view_visible_entities = visible_entities
.entities
@ -2081,13 +2083,14 @@ pub fn check_light_mesh_visibility(
for (
entity,
mut computed_visibility,
inherited_visibility,
mut view_visibility,
maybe_entity_mask,
maybe_aabb,
maybe_transform,
) in &mut visible_entity_query
{
if !computed_visibility.is_visible_in_hierarchy() {
if !inherited_visibility.get() {
continue;
}
@ -2109,12 +2112,12 @@ pub fn check_light_mesh_visibility(
.zip(cubemap_visible_entities.iter_mut())
{
if frustum.intersects_obb(aabb, &model_to_world, true, true) {
computed_visibility.set_visible_in_view();
view_visibility.set();
visible_entities.entities.push(entity);
}
}
} else {
computed_visibility.set_visible_in_view();
view_visibility.set();
for visible_entities in cubemap_visible_entities.iter_mut() {
visible_entities.entities.push(entity);
}
@ -2145,13 +2148,14 @@ pub fn check_light_mesh_visibility(
for (
entity,
mut computed_visibility,
inherited_visibility,
mut view_visibility,
maybe_entity_mask,
maybe_aabb,
maybe_transform,
) in visible_entity_query.iter_mut()
{
if !computed_visibility.is_visible_in_hierarchy() {
if !inherited_visibility.get() {
continue;
}
@ -2169,11 +2173,11 @@ pub fn check_light_mesh_visibility(
}
if frustum.intersects_obb(aabb, &model_to_world, true, true) {
computed_visibility.set_visible_in_view();
view_visibility.set();
visible_entities.entities.push(entity);
}
} else {
computed_visibility.set_visible_in_view();
view_visibility.set();
visible_entities.entities.push(entity);
}
}

View file

@ -22,7 +22,7 @@ use bevy_render::{
render_resource::*,
renderer::{RenderContext, RenderDevice, RenderQueue},
texture::*,
view::{ComputedVisibility, ExtractedView, VisibleEntities},
view::{ExtractedView, ViewVisibility, VisibleEntities},
Extract,
};
use bevy_transform::{components::GlobalTransform, prelude::Transform};
@ -299,7 +299,7 @@ pub fn extract_lights(
&PointLight,
&CubemapVisibleEntities,
&GlobalTransform,
&ComputedVisibility,
&ViewVisibility,
)>,
>,
spot_lights: Extract<
@ -307,7 +307,7 @@ pub fn extract_lights(
&SpotLight,
&VisibleEntities,
&GlobalTransform,
&ComputedVisibility,
&ViewVisibility,
)>,
>,
directional_lights: Extract<
@ -319,7 +319,7 @@ pub fn extract_lights(
&Cascades,
&CascadeShadowConfig,
&GlobalTransform,
&ComputedVisibility,
&ViewVisibility,
),
Without<SpotLight>,
>,
@ -346,10 +346,10 @@ pub fn extract_lights(
let mut point_lights_values = Vec::with_capacity(*previous_point_lights_len);
for entity in global_point_lights.iter().copied() {
if let Ok((point_light, cubemap_visible_entities, transform, visibility)) =
if let Ok((point_light, cubemap_visible_entities, transform, view_visibility)) =
point_lights.get(entity)
{
if !visibility.is_visible() {
if !view_visibility.get() {
continue;
}
// TODO: This is very much not ideal. We should be able to re-use the vector memory.
@ -385,8 +385,10 @@ pub fn extract_lights(
let mut spot_lights_values = Vec::with_capacity(*previous_spot_lights_len);
for entity in global_point_lights.iter().copied() {
if let Ok((spot_light, visible_entities, transform, visibility)) = spot_lights.get(entity) {
if !visibility.is_visible() {
if let Ok((spot_light, visible_entities, transform, view_visibility)) =
spot_lights.get(entity)
{
if !view_visibility.get() {
continue;
}
// TODO: This is very much not ideal. We should be able to re-use the vector memory.
@ -433,10 +435,10 @@ pub fn extract_lights(
cascades,
cascade_config,
transform,
visibility,
) in directional_lights.iter()
view_visibility,
) in &directional_lights
{
if !visibility.is_visible() {
if !view_visibility.get() {
continue;
}

View file

@ -37,7 +37,7 @@ use bevy_render::{
BevyDefault, DefaultImageSampler, FallbackImageCubemap, FallbackImagesDepth,
FallbackImagesMsaa, GpuImage, Image, ImageSampler, TextureFormatPixelInfo,
},
view::{ComputedVisibility, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms},
view::{ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms, ViewVisibility},
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
};
use bevy_transform::components::GlobalTransform;
@ -261,7 +261,7 @@ pub fn extract_meshes(
meshes_query: Extract<
Query<(
Entity,
&ComputedVisibility,
&ViewVisibility,
&GlobalTransform,
Option<&PreviousGlobalTransform>,
&Handle<Mesh>,
@ -272,7 +272,7 @@ pub fn extract_meshes(
) {
let mut caster_commands = Vec::with_capacity(*prev_caster_commands_len);
let mut not_caster_commands = Vec::with_capacity(*prev_not_caster_commands_len);
let visible_meshes = meshes_query.iter().filter(|(_, vis, ..)| vis.is_visible());
let visible_meshes = meshes_query.iter().filter(|(_, vis, ..)| vis.get());
for (entity, _, transform, previous_transform, handle, not_receiver, not_caster) in
visible_meshes
@ -354,7 +354,7 @@ pub fn extract_skinned_meshes(
mut commands: Commands,
mut previous_len: Local<usize>,
mut uniform: ResMut<SkinnedMeshUniform>,
query: Extract<Query<(Entity, &ComputedVisibility, &SkinnedMesh)>>,
query: Extract<Query<(Entity, &ViewVisibility, &SkinnedMesh)>>,
inverse_bindposes: Extract<Res<Assets<SkinnedMeshInverseBindposes>>>,
joint_query: Extract<Query<&GlobalTransform>>,
) {
@ -362,8 +362,8 @@ pub fn extract_skinned_meshes(
let mut values = Vec::with_capacity(*previous_len);
let mut last_start = 0;
for (entity, computed_visibility, skin) in &query {
if !computed_visibility.is_visible() {
for (entity, view_visibility, skin) in &query {
if !view_visibility.get() {
continue;
}
// PERF: This can be expensive, can we move this to prepare?

View file

@ -5,7 +5,7 @@ use bevy_render::{
mesh::morph::{MeshMorphWeights, MAX_MORPH_WEIGHTS},
render_resource::{BufferUsages, BufferVec},
renderer::{RenderDevice, RenderQueue},
view::ComputedVisibility,
view::ViewVisibility,
Extract,
};
use bytemuck::Pod;
@ -72,14 +72,14 @@ pub fn extract_morphs(
mut commands: Commands,
mut previous_len: Local<usize>,
mut uniform: ResMut<MorphUniform>,
query: Extract<Query<(Entity, &ComputedVisibility, &MeshMorphWeights)>>,
query: Extract<Query<(Entity, &ViewVisibility, &MeshMorphWeights)>>,
) {
uniform.buffer.clear();
let mut values = Vec::with_capacity(*previous_len);
for (entity, computed_visibility, morph_weights) in &query {
if !computed_visibility.is_visible() {
for (entity, view_visibility, morph_weights) in &query {
if !view_visibility.get() {
continue;
}
let start = uniform.buffer.len();

View file

@ -1,7 +1,7 @@
use crate::{
render_resource::{encase::internal::WriteInto, DynamicUniformBuffer, ShaderType},
renderer::{RenderDevice, RenderQueue},
view::ComputedVisibility,
view::ViewVisibility,
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
};
use bevy_app::{App, Plugin};
@ -222,11 +222,11 @@ fn extract_components<C: ExtractComponent>(
fn extract_visible_components<C: ExtractComponent>(
mut commands: Commands,
mut previous_len: Local<usize>,
query: Extract<Query<(Entity, &ComputedVisibility, C::Query), C::Filter>>,
query: Extract<Query<(Entity, &ViewVisibility, C::Query), C::Filter>>,
) {
let mut values = Vec::with_capacity(*previous_len);
for (entity, computed_visibility, query_item) in &query {
if computed_visibility.is_visible() {
for (entity, view_visibility, query_item) in &query {
if view_visibility.get() {
if let Some(component) = C::extract_component(query_item) {
values.push((entity, component));
}

View file

@ -38,7 +38,7 @@ pub mod prelude {
render_resource::Shader,
spatial_bundle::SpatialBundle,
texture::{Image, ImagePlugin},
view::{ComputedVisibility, Msaa, Visibility, VisibilityBundle},
view::{InheritedVisibility, Msaa, ViewVisibility, Visibility, VisibilityBundle},
ExtractSchedule,
};
}
@ -357,7 +357,7 @@ impl Plugin for RenderPlugin {
}
app.add_plugins((
ValidParentCheckPlugin::<view::ComputedVisibility>::default(),
ValidParentCheckPlugin::<view::InheritedVisibility>::default(),
WindowRenderPlugin,
CameraPlugin,
ViewPlugin,

View file

@ -1,14 +1,14 @@
use bevy_ecs::prelude::Bundle;
use bevy_transform::prelude::{GlobalTransform, Transform};
use crate::view::{ComputedVisibility, Visibility};
use crate::view::{InheritedVisibility, ViewVisibility, Visibility};
/// A [`Bundle`] with the following [`Component`](bevy_ecs::component::Component)s:
/// * [`Visibility`] and [`ComputedVisibility`], which describe the visibility of an entity
/// * [`Visibility`], and [`InheritedVisibility`], which describe the visibility of an entity
/// * [`Transform`] and [`GlobalTransform`], which describe the position of an entity
///
/// * To show or hide an entity, you should set its [`Visibility`].
/// * To get the computed visibility of an entity, you should get its [`ComputedVisibility`].
/// * To get the computed visibility of an entity, you should get its [`InheritedVisibility`] or [`ViewVisibility`] components.
/// * To place or move an entity, you should set its [`Transform`].
/// * To get the global transform of an entity, you should get its [`GlobalTransform`].
/// * For hierarchies to work correctly, you must have all four components.
@ -17,8 +17,10 @@ use crate::view::{ComputedVisibility, Visibility};
pub struct SpatialBundle {
/// The visibility of the entity.
pub visibility: Visibility,
/// The computed visibility of the entity.
pub computed: ComputedVisibility,
/// The inherited visibility of the entity.
pub inherited_visibility: InheritedVisibility,
/// The view visibility of the entity.
pub view_visibility: ViewVisibility,
/// The transform of the entity.
pub transform: Transform,
/// The global transform of the entity.
@ -40,7 +42,8 @@ impl SpatialBundle {
/// A visible [`SpatialBundle`], with no translation, rotation, and a scale of 1 on all axes.
pub const INHERITED_IDENTITY: Self = SpatialBundle {
visibility: Visibility::Inherited,
computed: ComputedVisibility::HIDDEN,
inherited_visibility: InheritedVisibility::HIDDEN,
view_visibility: ViewVisibility::HIDDEN,
transform: Transform::IDENTITY,
global_transform: GlobalTransform::IDENTITY,
};

View file

@ -40,8 +40,8 @@ impl Plugin for ViewPlugin {
fn build(&self, app: &mut App) {
load_internal_asset!(app, VIEW_TYPE_HANDLE, "view.wgsl", Shader::from_wgsl);
app.register_type::<ComputedVisibility>()
.register_type::<ComputedVisibilityFlags>()
app.register_type::<InheritedVisibility>()
.register_type::<ViewVisibility>()
.register_type::<Msaa>()
.register_type::<NoFrustumCulling>()
.register_type::<RenderLayers>()

View file

@ -1,5 +1,6 @@
mod render_layers;
use bevy_derive::Deref;
pub use render_layers::*;
use bevy_app::{Plugin, PostUpdate};
@ -26,7 +27,7 @@ use crate::{
/// are set to [`Inherited`](Self::Inherited) will also be hidden.
///
/// This is done by the `visibility_propagate_system` which uses the entity hierarchy and
/// `Visibility` to set the values of each entity's [`ComputedVisibility`] component.
/// `Visibility` to set the values of each entity's [`InheritedVisibility`] component.
#[derive(Component, Clone, Copy, Reflect, Debug, PartialEq, Eq, Default)]
#[reflect(Component, Default)]
pub enum Visibility {
@ -60,97 +61,88 @@ impl std::cmp::PartialEq<&Visibility> for Visibility {
}
}
bitflags::bitflags! {
#[derive(Clone, Debug, Eq, PartialEq)]
pub(super) struct ComputedVisibilityFlags: u8 {
const VISIBLE_IN_VIEW = 1 << 0;
const VISIBLE_IN_HIERARCHY = 1 << 1;
}
}
bevy_reflect::impl_reflect_value!((in bevy_render::view) ComputedVisibilityFlags);
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
#[derive(Component, Clone, Reflect, Debug, Eq, PartialEq)]
/// Whether or not an entity is visible in the hierarchy.
/// This will not be accurate until [`VisibilityPropagate`] runs in the [`PostUpdate`] schedule.
///
/// If this is false, then [`ViewVisibility`] should also be false.
///
/// [`VisibilityPropagate`]: VisibilitySystems::VisibilityPropagate
#[derive(Component, Deref, Debug, Default, Clone, Copy, Reflect, PartialEq, Eq)]
#[reflect(Component, Default)]
pub struct ComputedVisibility {
flags: ComputedVisibilityFlags,
}
pub struct InheritedVisibility(bool);
impl Default for ComputedVisibility {
fn default() -> Self {
Self::HIDDEN
impl InheritedVisibility {
/// An entity that is invisible in the hierarchy.
pub const HIDDEN: Self = Self(false);
/// An entity that is visible in the hierarchy.
pub const VISIBLE: Self = Self(true);
/// Returns `true` if the entity is visible in the hierarchy.
/// Otherwise, returns `false`.
#[inline]
pub fn get(self) -> bool {
self.0
}
}
impl ComputedVisibility {
/// A [`ComputedVisibility`], set as invisible.
pub const HIDDEN: Self = ComputedVisibility {
flags: ComputedVisibilityFlags::empty(),
};
/// Algorithmically-computed indication or whether an entity is visible and should be extracted for rendering.
///
/// Each frame, this will be reset to `false` during [`VisibilityPropagate`] systems in [`PostUpdate`].
/// Later in the frame, systems in [`CheckVisibility`] will mark any visible entities using [`ViewVisibility::set`].
/// Because of this, values of this type will be marked as changed every frame, even when they do not change.
///
/// If you wish to add custom visibility system that sets this value, make sure you add it to the [`CheckVisibility`] set.
///
/// [`VisibilityPropagate`]: VisibilitySystems::VisibilityPropagate
/// [`CheckVisibility`]: VisibilitySystems::CheckVisibility
#[derive(Component, Deref, Debug, Default, Clone, Copy, Reflect, PartialEq, Eq)]
#[reflect(Component, Default)]
pub struct ViewVisibility(bool);
/// Whether this entity is visible to something this frame. This is true if and only if [`Self::is_visible_in_hierarchy`] and [`Self::is_visible_in_view`]
/// are true. This is the canonical method to call to determine if an entity should be drawn.
/// This value is updated in [`PostUpdate`] by the [`VisibilitySystems::CheckVisibility`] system set.
/// Reading it during [`Update`](bevy_app::Update) will yield the value from the previous frame.
impl ViewVisibility {
/// An entity that cannot be seen from any views.
pub const HIDDEN: Self = Self(false);
/// Returns `true` if the entity is visible in any view.
/// Otherwise, returns `false`.
#[inline]
pub fn is_visible(&self) -> bool {
self.flags.bits() == ComputedVisibilityFlags::all().bits()
pub fn get(self) -> bool {
self.0
}
/// Whether this entity is visible in the entity hierarchy, which is determined by the [`Visibility`] component.
/// This takes into account "visibility inheritance". If any of this entity's ancestors (see [`Parent`]) are hidden, this entity
/// will be hidden as well. This value is updated in the [`VisibilitySystems::VisibilityPropagate`], which lives in the [`PostUpdate`] schedule.
#[inline]
pub fn is_visible_in_hierarchy(&self) -> bool {
self.flags
.contains(ComputedVisibilityFlags::VISIBLE_IN_HIERARCHY)
}
/// Whether this entity is visible in _any_ view (Cameras, Lights, etc). Each entity type (and view type) should choose how to set this
/// value. For cameras and drawn entities, this will take into account [`RenderLayers`].
/// Sets the visibility to `true`. This should not be considered reversible for a given frame,
/// as this component tracks whether or not the entity visible in _any_ view.
///
/// This value is reset to `false` every frame in [`VisibilitySystems::VisibilityPropagate`] during [`PostUpdate`].
/// Each entity type then chooses how to set this field in the [`VisibilitySystems::CheckVisibility`] system set, in [`PostUpdate`].
/// Meshes might use frustum culling to decide if they are visible in a view.
/// Other entities might just set this to `true` every frame.
/// This will be automatically reset to `false` every frame in [`VisibilityPropagate`] and then set
/// to the proper value in [`CheckVisibility`].
///
/// You should only manaully set this if you are defining a custom visibility system,
/// in which case the system should be placed in the [`CheckVisibility`] set.
/// For normal user-defined entity visibility, see [`Visibility`].
///
/// [`VisibilityPropagate`]: VisibilitySystems::VisibilityPropagate
/// [`CheckVisibility`]: VisibilitySystems::CheckVisibility
#[inline]
pub fn is_visible_in_view(&self) -> bool {
self.flags
.contains(ComputedVisibilityFlags::VISIBLE_IN_VIEW)
}
/// Sets `is_visible_in_view` to `true`. This is not reversible for a given frame, as it encodes whether or not this is visible in
/// _any_ view. This will be automatically reset to `false` every frame in [`VisibilitySystems::VisibilityPropagate`] and then set
/// to the proper value in [`VisibilitySystems::CheckVisibility`]. This should _only_ be set in systems with the [`VisibilitySystems::CheckVisibility`]
/// label. Don't call this unless you are defining a custom visibility system. For normal user-defined entity visibility, see [`Visibility`].
#[inline]
pub fn set_visible_in_view(&mut self) {
self.flags.insert(ComputedVisibilityFlags::VISIBLE_IN_VIEW);
}
#[inline]
fn reset(&mut self, visible_in_hierarchy: bool) {
self.flags = if visible_in_hierarchy {
ComputedVisibilityFlags::VISIBLE_IN_HIERARCHY
} else {
ComputedVisibilityFlags::empty()
};
pub fn set(&mut self) {
self.0 = true;
}
}
/// A [`Bundle`] of the [`Visibility`] and [`ComputedVisibility`]
/// A [`Bundle`] of the [`Visibility`], [`InheritedVisibility`], and [`ViewVisibility`]
/// [`Component`](bevy_ecs::component::Component)s, which describe the visibility of an entity.
///
/// * To show or hide an entity, you should set its [`Visibility`].
/// * To get the computed visibility of an entity, you should get its [`ComputedVisibility`].
/// * For visibility hierarchies to work correctly, you must have both a [`Visibility`] and a [`ComputedVisibility`].
/// * To get the inherited visibility of an entity, you should get its [`InheritedVisibility`].
/// * For visibility hierarchies to work correctly, you must have both all of [`Visibility`], [`InheritedVisibility`], and [`ViewVisibility`].
/// * You may use the [`VisibilityBundle`] to guarantee this.
#[derive(Bundle, Debug, Default)]
pub struct VisibilityBundle {
/// The visibility of the entity.
pub visibility: Visibility,
/// The computed visibility of the entity.
pub computed: ComputedVisibility,
// The inherited visibility of the entity.
pub inherited_visibility: InheritedVisibility,
// The computed visibility of the entity.
pub view_visibility: ViewVisibility,
}
/// Use this component to opt-out of built-in frustum culling for entities, see
@ -207,10 +199,10 @@ pub enum VisibilitySystems {
UpdatePerspectiveFrusta,
/// Label for the [`update_frusta<Projection>`] system.
UpdateProjectionFrusta,
/// Label for the system propagating the [`ComputedVisibility`] in a
/// Label for the system propagating the [`InheritedVisibility`] in a
/// [`hierarchy`](bevy_hierarchy).
VisibilityPropagate,
/// Label for the [`check_visibility`] system updating [`ComputedVisibility`]
/// Label for the [`check_visibility`] system updating [`ViewVisibility`]
/// of each entity and the [`VisibleEntities`] of each view.
CheckVisibility,
}
@ -250,7 +242,8 @@ impl Plugin for VisibilityPlugin {
.in_set(UpdateProjectionFrusta)
.after(camera_system::<Projection>)
.after(TransformSystem::TransformPropagate),
visibility_propagate_system.in_set(VisibilityPropagate),
(visibility_propagate_system, reset_view_visibility)
.in_set(VisibilityPropagate),
check_visibility
.in_set(CheckVisibility)
.after(CalculateBoundsFlush)
@ -306,83 +299,108 @@ pub fn update_frusta<T: Component + CameraProjection + Send + Sync + 'static>(
}
fn visibility_propagate_system(
mut root_query: Query<
(
Option<&Children>,
&Visibility,
&mut ComputedVisibility,
Entity,
),
Without<Parent>,
changed: Query<
(Entity, &Visibility, Option<&Parent>, Option<&Children>),
(With<InheritedVisibility>, Changed<Visibility>),
>,
mut visibility_query: Query<(&Visibility, &mut ComputedVisibility, &Parent)>,
children_query: Query<&Children, (With<Parent>, With<Visibility>, With<ComputedVisibility>)>,
mut visibility_query: Query<(&Visibility, &mut InheritedVisibility)>,
children_query: Query<&Children, (With<Visibility>, With<InheritedVisibility>)>,
) {
for (children, visibility, mut computed_visibility, entity) in root_query.iter_mut() {
// reset "view" visibility here ... if this entity should be drawn a future system should set this to true
computed_visibility
.reset(visibility == Visibility::Inherited || visibility == Visibility::Visible);
if let Some(children) = children {
for child in children.iter() {
let _ = propagate_recursive(
computed_visibility.is_visible_in_hierarchy(),
&mut visibility_query,
&children_query,
*child,
entity,
);
for (entity, visibility, parent, children) in &changed {
let is_visible = match visibility {
Visibility::Visible => true,
Visibility::Hidden => false,
Visibility::Inherited => match parent {
None => true,
Some(parent) => visibility_query.get(parent.get()).unwrap().1.get(),
},
};
let (_, mut inherited_visiblity) = visibility_query
.get_mut(entity)
.expect("With<InheritedVisibility> ensures this query will return a value");
// Only update the visibility if it has changed.
// This will also prevent the visibility from propagating multiple times in the same frame
// if this entity's visiblity has been updated recursively by its parent.
if inherited_visiblity.get() != is_visible {
inherited_visiblity.0 = is_visible;
// Recursively update the visibility of each child.
for &child in children.into_iter().flatten() {
let _ =
propagate_recursive(is_visible, child, &mut visibility_query, &children_query);
}
}
}
}
fn propagate_recursive(
parent_visible: bool,
visibility_query: &mut Query<(&Visibility, &mut ComputedVisibility, &Parent)>,
children_query: &Query<&Children, (With<Parent>, With<Visibility>, With<ComputedVisibility>)>,
parent_is_visible: bool,
entity: Entity,
expected_parent: Entity,
visibility_query: &mut Query<(&Visibility, &mut InheritedVisibility)>,
children_query: &Query<&Children, (With<Visibility>, With<InheritedVisibility>)>,
// BLOCKED: https://github.com/rust-lang/rust/issues/31436
// We use a result here to use the `?` operator. Ideally we'd use a try block instead
) -> Result<(), ()> {
let is_visible = {
let (visibility, mut computed_visibility, child_parent) =
visibility_query.get_mut(entity).map_err(drop)?;
assert_eq!(
child_parent.get(), expected_parent,
"Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
);
let visible_in_hierarchy = (parent_visible && visibility == Visibility::Inherited)
|| visibility == Visibility::Visible;
// reset "view" visibility here ... if this entity should be drawn a future system should set this to true
computed_visibility.reset(visible_in_hierarchy);
visible_in_hierarchy
// Get the visibility components for the current entity.
// If the entity does not have the requuired components, just return early.
let (visibility, mut inherited_visibility) = visibility_query.get_mut(entity).map_err(drop)?;
let is_visible = match visibility {
Visibility::Visible => true,
Visibility::Hidden => false,
Visibility::Inherited => parent_is_visible,
};
for child in children_query.get(entity).map_err(drop)?.iter() {
let _ = propagate_recursive(is_visible, visibility_query, children_query, *child, entity);
// Only update the visibility if it has changed.
if inherited_visibility.get() != is_visible {
inherited_visibility.0 = is_visible;
// Recursively update the visibility of each child.
for &child in children_query.get(entity).ok().into_iter().flatten() {
let _ = propagate_recursive(is_visible, child, visibility_query, children_query);
}
}
Ok(())
}
/// Updates the visibility of entities each frame.
/// Resets the view visibility of every entity.
/// Entities that are visible will be marked as such later this frame
/// by a [`VisibilitySystems::CheckVisibility`] system.
fn reset_view_visibility(mut query: Query<&mut ViewVisibility>) {
for mut view_visibility in &mut query {
// NOTE: We do not use `set_if_neq` here, as we don't care about
// change detection for view visibility, and adding a branch to every
// loop iteration would pessimize performance.
*view_visibility = ViewVisibility::HIDDEN;
}
}
/// System updating the visibility of entities each frame.
///
/// This system is part of the [`VisibilitySystems::CheckVisibility`] set. Each frame, it updates the
/// [`ComputedVisibility`] of all entities, and for each view also compute the [`VisibleEntities`]
/// The system is part of the [`VisibilitySystems::CheckVisibility`] set. Each frame, it updates the
/// [`ViewVisibility`] of all entities, and for each view also compute the [`VisibleEntities`]
/// for that view.
pub fn check_visibility(
mut thread_queues: Local<ThreadLocal<Cell<Vec<Entity>>>>,
mut view_query: Query<(&mut VisibleEntities, &Frustum, Option<&RenderLayers>), With<Camera>>,
mut visible_aabb_query: Query<(
Entity,
&mut ComputedVisibility,
&InheritedVisibility,
&mut ViewVisibility,
Option<&RenderLayers>,
&Aabb,
&GlobalTransform,
Option<&NoFrustumCulling>,
)>,
mut visible_no_aabb_query: Query<
(Entity, &mut ComputedVisibility, Option<&RenderLayers>),
(
Entity,
&InheritedVisibility,
&mut ViewVisibility,
Option<&RenderLayers>,
),
Without<Aabb>,
>,
) {
@ -393,15 +411,16 @@ pub fn check_visibility(
visible_aabb_query.par_iter_mut().for_each(
|(
entity,
mut computed_visibility,
inherited_visibility,
mut view_visibility,
maybe_entity_mask,
model_aabb,
transform,
maybe_no_frustum_culling,
)| {
// skip computing visibility for entities that are configured to be hidden. is_visible_in_view has already been set to false
// in visibility_propagate_system
if !computed_visibility.is_visible_in_hierarchy() {
// Skip computing visibility for entities that are configured to be hidden.
// ViewVisibility has already been reset in `reset_view_visibility`.
if !inherited_visibility.get() {
return;
}
@ -427,7 +446,7 @@ pub fn check_visibility(
}
}
computed_visibility.set_visible_in_view();
view_visibility.set();
let cell = thread_queues.get_or_default();
let mut queue = cell.take();
queue.push(entity);
@ -436,10 +455,10 @@ pub fn check_visibility(
);
visible_no_aabb_query.par_iter_mut().for_each(
|(entity, mut computed_visibility, maybe_entity_mask)| {
// skip computing visibility for entities that are configured to be hidden. is_visible_in_view has already been set to false
// in visibility_propagate_system
if !computed_visibility.is_visible_in_hierarchy() {
|(entity, inherited_visibility, mut view_visibility, maybe_entity_mask)| {
// Skip computing visibility for entities that are configured to be hidden.
// ViewVisiblity has already been reset in `reset_view_visibility`.
if !inherited_visibility.get() {
return;
}
@ -448,7 +467,7 @@ pub fn check_visibility(
return;
}
computed_visibility.set_visible_in_view();
view_visibility.set();
let cell = thread_queues.get_or_default();
let mut queue = cell.take();
queue.push(entity);
@ -478,24 +497,21 @@ mod test {
let root1 = app
.world
.spawn((Visibility::Hidden, ComputedVisibility::default()))
.id();
let root1_child1 = app
.world
.spawn((Visibility::default(), ComputedVisibility::default()))
.spawn(VisibilityBundle {
visibility: Visibility::Hidden,
..Default::default()
})
.id();
let root1_child1 = app.world.spawn(VisibilityBundle::default()).id();
let root1_child2 = app
.world
.spawn((Visibility::Hidden, ComputedVisibility::default()))
.id();
let root1_child1_grandchild1 = app
.world
.spawn((Visibility::default(), ComputedVisibility::default()))
.id();
let root1_child2_grandchild1 = app
.world
.spawn((Visibility::default(), ComputedVisibility::default()))
.spawn(VisibilityBundle {
visibility: Visibility::Hidden,
..Default::default()
})
.id();
let root1_child1_grandchild1 = app.world.spawn(VisibilityBundle::default()).id();
let root1_child2_grandchild1 = app.world.spawn(VisibilityBundle::default()).id();
app.world
.entity_mut(root1)
@ -507,26 +523,17 @@ mod test {
.entity_mut(root1_child2)
.push_children(&[root1_child2_grandchild1]);
let root2 = app
.world
.spawn((Visibility::default(), ComputedVisibility::default()))
.id();
let root2_child1 = app
.world
.spawn((Visibility::default(), ComputedVisibility::default()))
.id();
let root2 = app.world.spawn(VisibilityBundle::default()).id();
let root2_child1 = app.world.spawn(VisibilityBundle::default()).id();
let root2_child2 = app
.world
.spawn((Visibility::Hidden, ComputedVisibility::default()))
.id();
let root2_child1_grandchild1 = app
.world
.spawn((Visibility::default(), ComputedVisibility::default()))
.id();
let root2_child2_grandchild1 = app
.world
.spawn((Visibility::default(), ComputedVisibility::default()))
.spawn(VisibilityBundle {
visibility: Visibility::Hidden,
..Default::default()
})
.id();
let root2_child1_grandchild1 = app.world.spawn(VisibilityBundle::default()).id();
let root2_child2_grandchild1 = app.world.spawn(VisibilityBundle::default()).id();
app.world
.entity_mut(root2)
@ -543,9 +550,9 @@ mod test {
let is_visible = |e: Entity| {
app.world
.entity(e)
.get::<ComputedVisibility>()
.get::<InheritedVisibility>()
.unwrap()
.is_visible_in_hierarchy()
.get()
};
assert!(
!is_visible(root1),
@ -597,32 +604,53 @@ mod test {
let root1 = app
.world
.spawn((Visibility::Visible, ComputedVisibility::default()))
.spawn(VisibilityBundle {
visibility: Visibility::Visible,
..Default::default()
})
.id();
let root1_child1 = app
.world
.spawn((Visibility::Inherited, ComputedVisibility::default()))
.spawn(VisibilityBundle {
visibility: Visibility::Inherited,
..Default::default()
})
.id();
let root1_child2 = app
.world
.spawn((Visibility::Hidden, ComputedVisibility::default()))
.spawn(VisibilityBundle {
visibility: Visibility::Hidden,
..Default::default()
})
.id();
let root1_child1_grandchild1 = app
.world
.spawn((Visibility::Visible, ComputedVisibility::default()))
.spawn(VisibilityBundle {
visibility: Visibility::Visible,
..Default::default()
})
.id();
let root1_child2_grandchild1 = app
.world
.spawn((Visibility::Visible, ComputedVisibility::default()))
.spawn(VisibilityBundle {
visibility: Visibility::Visible,
..Default::default()
})
.id();
let root2 = app
.world
.spawn((Visibility::Inherited, ComputedVisibility::default()))
.spawn(VisibilityBundle {
visibility: Visibility::Inherited,
..Default::default()
})
.id();
let root3 = app
.world
.spawn((Visibility::Hidden, ComputedVisibility::default()))
.spawn(VisibilityBundle {
visibility: Visibility::Hidden,
..Default::default()
})
.id();
app.world
@ -640,9 +668,9 @@ mod test {
let is_visible = |e: Entity| {
app.world
.entity(e)
.get::<ComputedVisibility>()
.get::<InheritedVisibility>()
.unwrap()
.is_visible_in_hierarchy()
.get()
};
assert!(
is_visible(root1),
@ -668,6 +696,87 @@ mod test {
assert!(!is_visible(root3), "a hidden root is hidden");
}
#[test]
fn visibility_progation_change_detection() {
let mut world = World::new();
let mut schedule = Schedule::default();
schedule.add_systems(visibility_propagate_system);
// Set up an entity hierarchy.
let id1 = world.spawn(VisibilityBundle::default()).id();
let id2 = world.spawn(VisibilityBundle::default()).id();
world.entity_mut(id1).push_children(&[id2]);
let id3 = world
.spawn(VisibilityBundle {
visibility: Visibility::Hidden,
..Default::default()
})
.id();
world.entity_mut(id2).push_children(&[id3]);
let id4 = world.spawn(VisibilityBundle::default()).id();
world.entity_mut(id3).push_children(&[id4]);
// Test the hierarchy.
// Make sure the hierarchy is up-to-date.
schedule.run(&mut world);
world.clear_trackers();
let mut q = world.query::<Ref<InheritedVisibility>>();
assert!(!q.get(&world, id1).unwrap().is_changed());
assert!(!q.get(&world, id2).unwrap().is_changed());
assert!(!q.get(&world, id3).unwrap().is_changed());
assert!(!q.get(&world, id4).unwrap().is_changed());
world.clear_trackers();
world.entity_mut(id1).insert(Visibility::Hidden);
schedule.run(&mut world);
assert!(q.get(&world, id1).unwrap().is_changed());
assert!(q.get(&world, id2).unwrap().is_changed());
assert!(!q.get(&world, id3).unwrap().is_changed());
assert!(!q.get(&world, id4).unwrap().is_changed());
world.clear_trackers();
schedule.run(&mut world);
assert!(!q.get(&world, id1).unwrap().is_changed());
assert!(!q.get(&world, id2).unwrap().is_changed());
assert!(!q.get(&world, id3).unwrap().is_changed());
assert!(!q.get(&world, id4).unwrap().is_changed());
world.clear_trackers();
world.entity_mut(id3).insert(Visibility::Inherited);
schedule.run(&mut world);
assert!(!q.get(&world, id1).unwrap().is_changed());
assert!(!q.get(&world, id2).unwrap().is_changed());
assert!(!q.get(&world, id3).unwrap().is_changed());
assert!(!q.get(&world, id4).unwrap().is_changed());
world.clear_trackers();
world.entity_mut(id2).insert(Visibility::Visible);
schedule.run(&mut world);
assert!(!q.get(&world, id1).unwrap().is_changed());
assert!(q.get(&world, id2).unwrap().is_changed());
assert!(q.get(&world, id3).unwrap().is_changed());
assert!(q.get(&world, id4).unwrap().is_changed());
world.clear_trackers();
schedule.run(&mut world);
assert!(!q.get(&world, id1).unwrap().is_changed());
assert!(!q.get(&world, id2).unwrap().is_changed());
assert!(!q.get(&world, id3).unwrap().is_changed());
assert!(!q.get(&world, id4).unwrap().is_changed());
}
#[test]
fn ensure_visibility_enum_size() {
use std::mem;

View file

@ -8,7 +8,7 @@ use bevy_ecs::{
system::{Commands, Query},
};
#[cfg(feature = "bevy_render")]
use bevy_render::prelude::{ComputedVisibility, Visibility};
use bevy_render::prelude::{InheritedVisibility, ViewVisibility, Visibility};
use bevy_transform::components::{GlobalTransform, Transform};
use crate::{DynamicScene, InstanceId, Scene, SceneSpawner};
@ -32,7 +32,9 @@ pub struct SceneBundle {
#[cfg(feature = "bevy_render")]
pub visibility: Visibility,
#[cfg(feature = "bevy_render")]
pub computed_visibility: ComputedVisibility,
pub inherited_visibility: InheritedVisibility,
#[cfg(feature = "bevy_render")]
pub view_visibility: ViewVisibility,
}
/// A component bundle for a [`DynamicScene`] root.
@ -49,7 +51,9 @@ pub struct DynamicSceneBundle {
#[cfg(feature = "bevy_render")]
pub visibility: Visibility,
#[cfg(feature = "bevy_render")]
pub computed_visibility: ComputedVisibility,
pub inherited_visibility: InheritedVisibility,
#[cfg(feature = "bevy_render")]
pub view_visibility: ViewVisibility,
}
/// System that will spawn scenes from [`SceneBundle`].

View file

@ -6,7 +6,7 @@ use bevy_asset::Handle;
use bevy_ecs::bundle::Bundle;
use bevy_render::{
texture::{Image, DEFAULT_IMAGE_HANDLE},
view::{ComputedVisibility, Visibility},
view::{InheritedVisibility, ViewVisibility, Visibility},
};
use bevy_transform::components::{GlobalTransform, Transform};
@ -18,8 +18,10 @@ pub struct SpriteBundle {
pub texture: Handle<Image>,
/// User indication of whether an entity is visible
pub visibility: Visibility,
/// Inherited visibility of an entity.
pub inherited_visibility: InheritedVisibility,
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
pub computed_visibility: ComputedVisibility,
pub view_visibility: ViewVisibility,
}
impl Default for SpriteBundle {
@ -30,7 +32,8 @@ impl Default for SpriteBundle {
global_transform: Default::default(),
texture: DEFAULT_IMAGE_HANDLE.typed(),
visibility: Default::default(),
computed_visibility: Default::default(),
inherited_visibility: Default::default(),
view_visibility: Default::default(),
}
}
}
@ -47,6 +50,7 @@ pub struct SpriteSheetBundle {
pub global_transform: GlobalTransform,
/// User indication of whether an entity is visible
pub visibility: Visibility,
pub inherited_visibility: InheritedVisibility,
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
pub computed_visibility: ComputedVisibility,
pub view_visibility: ViewVisibility,
}

View file

@ -31,7 +31,7 @@ use bevy_render::{
},
renderer::RenderDevice,
texture::FallbackImage,
view::{ComputedVisibility, ExtractedView, Msaa, Visibility, VisibleEntities},
view::{ExtractedView, InheritedVisibility, Msaa, ViewVisibility, Visibility, VisibleEntities},
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
};
use bevy_transform::components::{GlobalTransform, Transform};
@ -585,8 +585,10 @@ pub struct MaterialMesh2dBundle<M: Material2d> {
pub global_transform: GlobalTransform,
/// User indication of whether an entity is visible
pub visibility: Visibility,
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
pub computed_visibility: ComputedVisibility,
// Inherited visibility of an entity.
pub inherited_visibility: InheritedVisibility,
// Indication of whether an entity is visible in any view.
pub view_visibility: ViewVisibility,
}
impl<M: Material2d> Default for MaterialMesh2dBundle<M> {
@ -597,7 +599,8 @@ impl<M: Material2d> Default for MaterialMesh2dBundle<M> {
transform: Default::default(),
global_transform: Default::default(),
visibility: Default::default(),
computed_visibility: Default::default(),
inherited_visibility: Default::default(),
view_visibility: Default::default(),
}
}
}

View file

@ -20,7 +20,7 @@ use bevy_render::{
BevyDefault, DefaultImageSampler, GpuImage, Image, ImageSampler, TextureFormatPixelInfo,
},
view::{
ComputedVisibility, ExtractedView, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms,
ExtractedView, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms, ViewVisibility,
},
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
};
@ -139,11 +139,11 @@ bitflags::bitflags! {
pub fn extract_mesh2d(
mut commands: Commands,
mut previous_len: Local<usize>,
query: Extract<Query<(Entity, &ComputedVisibility, &GlobalTransform, &Mesh2dHandle)>>,
query: Extract<Query<(Entity, &ViewVisibility, &GlobalTransform, &Mesh2dHandle)>>,
) {
let mut values = Vec::with_capacity(*previous_len);
for (entity, computed_visibility, transform, handle) in &query {
if !computed_visibility.is_visible() {
for (entity, view_visibility, transform, handle) in &query {
if !view_visibility.get() {
continue;
}
let transform = transform.compute_matrix();

View file

@ -28,8 +28,8 @@ use bevy_render::{
BevyDefault, DefaultImageSampler, GpuImage, Image, ImageSampler, TextureFormatPixelInfo,
},
view::{
ComputedVisibility, ExtractedView, Msaa, ViewTarget, ViewUniform, ViewUniformOffset,
ViewUniforms, VisibleEntities,
ExtractedView, Msaa, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms,
ViewVisibility, VisibleEntities,
},
Extract,
};
@ -349,7 +349,7 @@ pub fn extract_sprites(
sprite_query: Extract<
Query<(
Entity,
&ComputedVisibility,
&ViewVisibility,
&Sprite,
&GlobalTransform,
&Handle<Image>,
@ -358,15 +358,15 @@ pub fn extract_sprites(
atlas_query: Extract<
Query<(
Entity,
&ComputedVisibility,
&ViewVisibility,
&TextureAtlasSprite,
&GlobalTransform,
&Handle<TextureAtlas>,
)>,
>,
) {
for (entity, visibility, sprite, transform, handle) in sprite_query.iter() {
if !visibility.is_visible() {
for (entity, view_visibility, sprite, transform, handle) in sprite_query.iter() {
if !view_visibility.get() {
continue;
}
// PERF: we don't check in this function that the `Image` asset is ready, since it should be in most cases and hashing the handle is expensive
@ -385,8 +385,10 @@ pub fn extract_sprites(
},
);
}
for (entity, visibility, atlas_sprite, transform, texture_atlas_handle) in atlas_query.iter() {
if !visibility.is_visible() {
for (entity, view_visibility, atlas_sprite, transform, texture_atlas_handle) in
atlas_query.iter()
{
if !view_visibility.get() {
continue;
}
if let Some(texture_atlas) = texture_atlases.get(texture_atlas_handle) {

View file

@ -14,7 +14,7 @@ use bevy_reflect::Reflect;
use bevy_render::{
prelude::Color,
texture::Image,
view::{ComputedVisibility, Visibility},
view::{InheritedVisibility, ViewVisibility, Visibility},
Extract,
};
use bevy_sprite::{Anchor, ExtractedSprite, ExtractedSprites, TextureAtlas};
@ -70,8 +70,10 @@ pub struct Text2dBundle {
pub global_transform: GlobalTransform,
/// The visibility properties of the text.
pub visibility: Visibility,
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering.
pub computed_visibility: ComputedVisibility,
/// Inherited visibility of an entity.
pub inherited_visibility: InheritedVisibility,
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
pub view_visibility: ViewVisibility,
/// Contains the size of the text and its glyph's position and scale data. Generated via [`TextPipeline::queue_text`]
pub text_layout_info: TextLayoutInfo,
}
@ -83,7 +85,7 @@ pub fn extract_text2d_sprite(
windows: Extract<Query<&Window, With<PrimaryWindow>>>,
text2d_query: Extract<
Query<(
&ComputedVisibility,
&ViewVisibility,
&Text,
&TextLayoutInfo,
&Anchor,
@ -98,10 +100,8 @@ pub fn extract_text2d_sprite(
.unwrap_or(1.0);
let scaling = GlobalTransform::from_scale(Vec3::splat(scale_factor.recip()));
for (computed_visibility, text, text_layout_info, anchor, global_transform) in
text2d_query.iter()
{
if !computed_visibility.is_visible() {
for (view_visibility, text, text_layout_info, anchor, global_transform) in text2d_query.iter() {
if !view_visibility.get() {
continue;
}

View file

@ -11,7 +11,7 @@ use bevy_ecs::{
use bevy_input::{mouse::MouseButton, touch::Touches, Input};
use bevy_math::Vec2;
use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize};
use bevy_render::{camera::NormalizedRenderTarget, prelude::Camera, view::ComputedVisibility};
use bevy_render::{camera::NormalizedRenderTarget, prelude::Camera, view::ViewVisibility};
use bevy_transform::components::GlobalTransform;
use bevy_window::{PrimaryWindow, Window};
@ -24,9 +24,9 @@ use smallvec::SmallVec;
///
/// Updated in [`ui_focus_system`].
///
/// If a UI node has both [`Interaction`] and [`ComputedVisibility`] components,
/// If a UI node has both [`Interaction`] and [`ViewVisibility`] components,
/// [`Interaction`] will always be [`Interaction::None`]
/// when [`ComputedVisibility::is_visible()`] is false.
/// when [`ViewVisibility::get()`] is false.
/// This ensures that hidden UI nodes are not interactable,
/// and do not end up stuck in an active state if hidden at the wrong time.
///
@ -125,12 +125,12 @@ pub struct NodeQuery {
relative_cursor_position: Option<&'static mut RelativeCursorPosition>,
focus_policy: Option<&'static FocusPolicy>,
calculated_clip: Option<&'static CalculatedClip>,
computed_visibility: Option<&'static ComputedVisibility>,
view_visibility: Option<&'static ViewVisibility>,
}
/// The system that sets Interaction for all UI elements based on the mouse cursor activity
///
/// Entities with a hidden [`ComputedVisibility`] are always treated as released.
/// Entities with a hidden [`ViewVisibility`] are always treated as released.
#[allow(clippy::too_many_arguments)]
pub fn ui_focus_system(
mut state: Local<State>,
@ -204,8 +204,8 @@ pub fn ui_focus_system(
.filter_map(|entity| {
if let Ok(node) = node_query.get_mut(*entity) {
// Nodes that are not rendered should not be interactable
if let Some(computed_visibility) = node.computed_visibility {
if !computed_visibility.is_visible() {
if let Some(view_visibility) = node.view_visibility {
if !view_visibility.get() {
// Reset their interaction to None to avoid strange stuck state
if let Some(mut interaction) = node.interaction {
// We cannot simply set the interaction to None, as that will trigger change detection repeatedly

View file

@ -10,8 +10,8 @@ use crate::{
use bevy_asset::Handle;
use bevy_ecs::bundle::Bundle;
use bevy_render::{
prelude::{Color, ComputedVisibility},
view::Visibility,
prelude::Color,
view::{InheritedVisibility, ViewVisibility, Visibility},
};
use bevy_sprite::TextureAtlas;
#[cfg(feature = "bevy_text")]
@ -46,8 +46,10 @@ pub struct NodeBundle {
pub global_transform: GlobalTransform,
/// Describes the visibility properties of the node
pub visibility: Visibility,
/// Inherited visibility of an entity.
pub inherited_visibility: InheritedVisibility,
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
pub computed_visibility: ComputedVisibility,
pub view_visibility: ViewVisibility,
/// Indicates the depth at which the node should appear in the UI
pub z_index: ZIndex,
}
@ -64,7 +66,8 @@ impl Default for NodeBundle {
transform: Default::default(),
global_transform: Default::default(),
visibility: Default::default(),
computed_visibility: Default::default(),
inherited_visibility: Default::default(),
view_visibility: Default::default(),
z_index: Default::default(),
}
}
@ -103,8 +106,10 @@ pub struct ImageBundle {
pub global_transform: GlobalTransform,
/// Describes the visibility properties of the node
pub visibility: Visibility,
/// Inherited visibility of an entity.
pub inherited_visibility: InheritedVisibility,
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
pub computed_visibility: ComputedVisibility,
pub view_visibility: ViewVisibility,
/// Indicates the depth at which the node should appear in the UI
pub z_index: ZIndex,
}
@ -144,8 +149,10 @@ pub struct AtlasImageBundle {
pub global_transform: GlobalTransform,
/// Describes the visibility properties of the node
pub visibility: Visibility,
/// Inherited visibility of an entity.
pub inherited_visibility: InheritedVisibility,
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
pub computed_visibility: ComputedVisibility,
pub view_visibility: ViewVisibility,
/// Indicates the depth at which the node should appear in the UI
pub z_index: ZIndex,
}
@ -180,8 +187,10 @@ pub struct TextBundle {
pub global_transform: GlobalTransform,
/// Describes the visibility properties of the node
pub visibility: Visibility,
/// Inherited visibility of an entity.
pub inherited_visibility: InheritedVisibility,
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
pub computed_visibility: ComputedVisibility,
pub view_visibility: ViewVisibility,
/// Indicates the depth at which the node should appear in the UI
pub z_index: ZIndex,
/// The background color that will fill the containing node
@ -196,16 +205,17 @@ impl Default for TextBundle {
text_layout_info: Default::default(),
text_flags: Default::default(),
calculated_size: Default::default(),
// Transparent background
background_color: BackgroundColor(Color::NONE),
node: Default::default(),
style: Default::default(),
focus_policy: Default::default(),
transform: Default::default(),
global_transform: Default::default(),
visibility: Default::default(),
computed_visibility: Default::default(),
inherited_visibility: Default::default(),
view_visibility: Default::default(),
z_index: Default::default(),
// Transparent background
background_color: BackgroundColor(Color::NONE),
}
}
}
@ -291,8 +301,10 @@ pub struct ButtonBundle {
pub global_transform: GlobalTransform,
/// Describes the visibility properties of the node
pub visibility: Visibility,
/// Inherited visibility of an entity.
pub inherited_visibility: InheritedVisibility,
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
pub computed_visibility: ComputedVisibility,
pub view_visibility: ViewVisibility,
/// Indicates the depth at which the node should appear in the UI
pub z_index: ZIndex,
}
@ -311,7 +323,8 @@ impl Default for ButtonBundle {
transform: Default::default(),
global_transform: Default::default(),
visibility: Default::default(),
computed_visibility: Default::default(),
inherited_visibility: Default::default(),
view_visibility: Default::default(),
z_index: Default::default(),
}
}

View file

@ -4,6 +4,7 @@ mod render_pass;
use bevy_core_pipeline::{core_2d::Camera2d, core_3d::Camera3d};
use bevy_ecs::storage::SparseSet;
use bevy_hierarchy::Parent;
use bevy_render::view::ViewVisibility;
use bevy_render::{ExtractSchedule, Render};
use bevy_window::{PrimaryWindow, Window};
pub use pipeline::*;
@ -29,7 +30,7 @@ use bevy_render::{
render_resource::*,
renderer::{RenderDevice, RenderQueue},
texture::Image,
view::{ComputedVisibility, ExtractedView, ViewUniforms},
view::{ExtractedView, ViewUniforms},
Extract, RenderApp, RenderSet,
};
use bevy_sprite::SpriteAssetEvents;
@ -182,7 +183,7 @@ pub fn extract_atlas_uinodes(
&Node,
&GlobalTransform,
&BackgroundColor,
&ComputedVisibility,
&ViewVisibility,
Option<&CalculatedClip>,
&Handle<TextureAtlas>,
&UiTextureAtlasImage,
@ -197,14 +198,14 @@ pub fn extract_atlas_uinodes(
uinode,
transform,
color,
visibility,
view_visibility,
clip,
texture_atlas_handle,
atlas_image,
)) = uinode_query.get(*entity)
{
// Skip invisible and completely transparent nodes
if !visibility.is_visible() || color.0.a() == 0.0 {
if !view_visibility.get() || color.0.a() == 0.0 {
continue;
}
@ -284,7 +285,7 @@ pub fn extract_uinode_borders(
&Style,
&BorderColor,
Option<&Parent>,
&ComputedVisibility,
&ViewVisibility,
Option<&CalculatedClip>,
),
Without<ContentSize>,
@ -303,11 +304,11 @@ pub fn extract_uinode_borders(
/ ui_scale.0 as f32;
for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() {
if let Ok((node, global_transform, style, border_color, parent, visibility, clip)) =
if let Ok((node, global_transform, style, border_color, parent, view_visibility, clip)) =
uinode_query.get(*entity)
{
// Skip invisible borders
if !visibility.is_visible()
if !view_visibility.get()
|| border_color.0.a() == 0.0
|| node.size().x <= 0.
|| node.size().y <= 0.
@ -405,7 +406,7 @@ pub fn extract_uinodes(
&GlobalTransform,
&BackgroundColor,
Option<&UiImage>,
&ComputedVisibility,
&ViewVisibility,
Option<&CalculatedClip>,
),
Without<UiTextureAtlasImage>,
@ -413,11 +414,11 @@ pub fn extract_uinodes(
>,
) {
for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() {
if let Ok((entity, uinode, transform, color, maybe_image, visibility, clip)) =
if let Ok((entity, uinode, transform, color, maybe_image, view_visibility, clip)) =
uinode_query.get(*entity)
{
// Skip invisible and completely transparent nodes
if !visibility.is_visible() || color.0.a() == 0.0 {
if !view_visibility.get() || color.0.a() == 0.0 {
continue;
}
@ -539,7 +540,7 @@ pub fn extract_text_uinodes(
&GlobalTransform,
&Text,
&TextLayoutInfo,
&ComputedVisibility,
&ViewVisibility,
Option<&CalculatedClip>,
)>,
>,
@ -554,11 +555,11 @@ pub fn extract_text_uinodes(
let inverse_scale_factor = (scale_factor as f32).recip();
for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() {
if let Ok((uinode, global_transform, text, text_layout_info, visibility, clip)) =
if let Ok((uinode, global_transform, text, text_layout_info, view_visibility, clip)) =
uinode_query.get(*entity)
{
// Skip if not visible or if size is set to zero (e.g. when a parent is set to `Display::None`)
if !visibility.is_visible() || uinode.size().x == 0. || uinode.size().y == 0. {
if !view_visibility.get() || uinode.size().x == 0. || uinode.size().y == 0. {
continue;
}
let transform = global_transform.compute_matrix()

View file

@ -7,7 +7,7 @@ without the hierarchy-inherited component in question.
The hierarchy-inherited components defined in bevy include:
- [`ComputedVisibility`]
- [`InheritedVisibility`]
- [`GlobalTransform`]
Third party plugins may also define their own hierarchy components, so
@ -57,9 +57,9 @@ fn main() {
This code **will not** show a cube on screen.
This is because the entity spawned with `commands.spawn(…)`
doesn't have a [`ComputedVisibility`] component.
doesn't have a [`ViewVisibility`] or [`InheritedVisibility`] component.
Since the cube is spawned as a child of an entity without the
[`ComputedVisibility`] component, it will not be visible at all.
visibility components, it will not be visible at all.
To fix this, you must use [`SpatialBundle`] over [`TransformBundle`],
as follows:
@ -74,7 +74,7 @@ fn setup_cube(
) {
commands
// We use SpatialBundle instead of TransformBundle, it contains the
// ComputedVisibility component needed to display the cube,
// visibility components needed to display the cube,
// In addition to the Transform and GlobalTransform components.
.spawn(SpatialBundle::default())
.with_children(|parent| {
@ -110,7 +110,8 @@ including when updating the [`Transform`] component of the child.
You will most likely encounter this warning when loading a scene
as a child of a pre-existing [`Entity`] that does not have the proper components.
[`ComputedVisibility`]: https://docs.rs/bevy/*/bevy/render/view/struct.ComputedVisibility.html
[`InheritedVisibility`]: https://docs.rs/bevy/*/bevy/render/view/struct.InheritedVisibility.html
[`ViewVisibility`]: https://docs.rs/bevy/*/bevy/render/view/struct.ViewVisibility.html
[`GlobalTransform`]: https://docs.rs/bevy/*/bevy/transform/components/struct.GlobalTransform.html
[`Transform`]: https://docs.rs/bevy/*/bevy/transform/components/struct.Transform.html
[`Parent`]: https://docs.rs/bevy/*/bevy/hierarchy/struct.Parent.html

View file

@ -296,11 +296,11 @@ pub fn extract_colored_mesh2d(
mut previous_len: Local<usize>,
// When extracting, you must use `Extract` to mark the `SystemParam`s
// which should be taken from the main world.
query: Extract<Query<(Entity, &ComputedVisibility), With<ColoredMesh2d>>>,
query: Extract<Query<(Entity, &ViewVisibility), With<ColoredMesh2d>>>,
) {
let mut values = Vec::with_capacity(*previous_len);
for (entity, computed_visibility) in &query {
if !computed_visibility.is_visible() {
for (entity, view_visibility) in &query {
if !view_visibility.get() {
continue;
}
values.push((entity, ColoredMesh2d));

View file

@ -170,7 +170,7 @@ fn move_camera(time: Res<Time>, mut camera_query: Query<&mut Transform, With<Cam
fn print_mesh_count(
time: Res<Time>,
mut timer: Local<PrintingTimer>,
sprites: Query<(&Handle<Mesh>, &ComputedVisibility)>,
sprites: Query<(&Handle<Mesh>, &ViewVisibility)>,
) {
timer.tick(time.delta());
@ -178,7 +178,7 @@ fn print_mesh_count(
info!(
"Meshes: {} - Visible Meshes {}",
sprites.iter().len(),
sprites.iter().filter(|(_, cv)| cv.is_visible()).count(),
sprites.iter().filter(|(_, vis)| vis.get()).count(),
);
}
}