Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 19 commits into from Sep 1, 2023

Conversation

JoJoJet
Copy link
Member

@JoJoJet JoJoJet commented Aug 19, 2023

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.

// 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(),
});
// 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() {
// 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() {
// 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();

@JoJoJet JoJoJet added C-Bug An unexpected or incorrect behavior A-Rendering Drawing game state to the screen C-Breaking-Change A breaking change to Bevy's public API that needs to be noted in a migration guide labels Aug 19, 2023
@alice-i-cecile alice-i-cecile added the C-Performance A change motivated by improving speed, memory usage or compile times label Aug 19, 2023
@alice-i-cecile
Copy link
Member

Could you run some benchmarks? I expect this to be quite performance sensitive, but I'd put even odds on improvements vs regressions.

///
/// [`VisibilitySystems::CheckVisibility`]
#[derive(Component, Deref, Debug, Default, Clone, Copy, Reflect, PartialEq, Eq)]
pub struct ViewVisibility(bool);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that we have a trivial getter, and a public way to set values as both true and false, I have a hard time seeing why we shouldn't just make this field pub.

The docs about how to use this are excellent, but they could just be moved to the struct level.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In previous discussion about components like Parent, I think it was decided that we should avoid the .0 syntax in public APIs, which is why I opted to derive deref and add a getter.

I decided not to provide a .set(false) option, as marking an entity as visible in view is meant to be irreversible during a given frame (this is a property I carried over from the old API, which had .set_visible_in_view()). Of course you could still reset the view visibility by just overwriting the entire component, but at least this API nudges users in the right direction.

Agreed about the documentation.

/// 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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking: this may benefit from parallel query iteration: it's a trivial loop with a ton of entities in it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ran cargo run --release --example many_foxes

  1. Without par_iter: avg fps 288.0 over 5285 frames.
  2. With par_iter: avg fps 281.8 over 5073.

Parallel iteration doesn't seem to have much of an effect, though it's possible that it would scale better for scenes with a huge number of entities.

@alice-i-cecile
Copy link
Member

From an architectural and usability perspective I really like these proposed changes. I am nervous about performance costs here: I often see these systems as among the most expensive in real applications.

@alice-i-cecile alice-i-cecile added this to the 0.12 milestone Aug 19, 2023
@JoJoJet
Copy link
Member Author

JoJoJet commented Aug 19, 2023

I ran the stress test cargo run --release --example many_foxes:

  1. Main - avg fps of 285.2 over 5239 frames.
  2. This PR - avg FPS of 287.9 over 5475 frames.

@JoJoJet
Copy link
Member Author

JoJoJet commented Aug 19, 2023

With cargo run --release --example bevymark with ~8000 birds, the FPS stays in the high 600s with and without this PR. I don't think this PR adds or takes away performance.

@mockersf
Copy link
Member

Would it be possible to keep it as one component and check if the value changes before updating it? Or triggering change manually when needed?

@JoJoJet
Copy link
Member Author

JoJoJet commented Aug 20, 2023

Would it be possible to keep it as one component and check if the value changes before updating it?

That's the "naive" solution I mentioned in #8267. In order for that approach to work, we'd have to overhaul how view visibility is calculated.

Each frame, the view visibility of every entity is reset. Then any number of systems belonging to the CheckVisibility set will mark entities that are visible in any view. Systems belonging to this set can live in any crate, not just bevy_render. View calculations are done in bevy_pbr, and are allowed to be performed in 3rd party. Because of this, it's not as simple as just using set_if_neq.

Furthermore, I think it is good to split it into separate components, even without the change detection issue. Inherited visibility and view visibility are separate concepts, and it's odd to bundle them the way we do now.

@DGriffin91
Copy link
Contributor

@JoJoJet How do I run it single threaded?

@JoJoJet
Copy link
Member Author

JoJoJet commented Aug 26, 2023

You should be able to just disable the multi-threaded cargo feature for bevy.

@DGriffin91
Copy link
Contributor

Without the multi-threaded feature I get 101ms on bevy main and 86ms on this PR. Nice work!

@JoJoJet
Copy link
Member Author

JoJoJet commented Aug 26, 2023

Nice, thanks for testing it!

@JoJoJet JoJoJet changed the title Split ComputedVisibility into two components to allow for accurate change detection Split ComputedVisibility into two components to allow for accurate change detection and speed up visibility propagation Aug 31, 2023
Copy link
Contributor

@superdump superdump left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few minor comments. Looks generally good.

crates/bevy_render/src/view/visibility/mod.rs Show resolved Hide resolved
crates/bevy_render/src/view/visibility/mod.rs Show resolved Hide resolved
crates/bevy_render/src/view/visibility/mod.rs Show resolved Hide resolved
crates/bevy_render/src/view/visibility/mod.rs Outdated Show resolved Hide resolved
crates/bevy_render/src/view/visibility/mod.rs Outdated Show resolved Hide resolved
crates/bevy_render/src/view/visibility/mod.rs Outdated Show resolved Hide resolved
JoJoJet and others added 2 commits August 31, 2023 17:33
Co-authored-by: Robert Swain <robert.swain@gmail.com>
@JoJoJet
Copy link
Member Author

JoJoJet commented Aug 31, 2023

Thanks for the review! I believe that's everything.

@superdump superdump added this pull request to the merge queue Sep 1, 2023
Merged via the queue into bevyengine:main with commit 02b520b Sep 1, 2023
21 checks passed
@JoJoJet JoJoJet deleted the visibility-split branch September 1, 2023 13:18
github-merge-queue bot pushed a commit that referenced this pull request Sep 3, 2023
# Objective

This PR aims to fix a handful of problems with the `SpatialBundle` docs:

The docs describe the role of the single components of the bundle,
overshadowing the purpose of `SpatialBundle` itself. Also, those items
may be added, removed or changed over time, as it happened with #9497,
requiring a higher maintenance effort, which will often result in
errors, as it happened.

## Solution

Just describe the role of `SpatialBundle` and of the transform and
visibility concepts, without mentioning the specific component types.
Since the bundle has public fields, the reader can easily click them and
read the documentation if they need to know more. I removed the mention
of numbers of components since they were four, now they are five, and
who knows how many they will be in the future. In this process, I
removed the bullet points, which are no longer needed, and were
contextually wrong in the first place, since they were meant to list the
components, but ended up describing use-cases and requirements for
hierarchies.

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
github-merge-queue bot pushed a commit that referenced this pull request Sep 20, 2023
# Objective

Fix a typo introduced by #9497. While drafting the PR, the type was
originally called `VisibleInHierarchy` before I renamed it to
`InheritedVisibility`, but this field got left behind due to a typo.
@cart cart mentioned this pull request Oct 13, 2023
43 tasks
TristanCacqueray added a commit to TristanCacqueray/bevy_mod_raycast that referenced this pull request Nov 4, 2023
TristanCacqueray added a commit to TristanCacqueray/bevy_mod_raycast that referenced this pull request Nov 4, 2023
aevyrie pushed a commit to aevyrie/bevy_mod_raycast that referenced this pull request Nov 10, 2023
* Bump bevy to 0.12

* Replace ComputedVisibility

… according to bevyengine/bevy#9497
rdrpenguin04 pushed a commit to rdrpenguin04/bevy that referenced this pull request Jan 9, 2024
…change detection and speed up visibility propagation (bevyengine#9497)

# Objective

Fix bevyengine#8267.
Fixes half of bevyengine#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>
rdrpenguin04 pushed a commit to rdrpenguin04/bevy that referenced this pull request Jan 9, 2024
# Objective

This PR aims to fix a handful of problems with the `SpatialBundle` docs:

The docs describe the role of the single components of the bundle,
overshadowing the purpose of `SpatialBundle` itself. Also, those items
may be added, removed or changed over time, as it happened with bevyengine#9497,
requiring a higher maintenance effort, which will often result in
errors, as it happened.

## Solution

Just describe the role of `SpatialBundle` and of the transform and
visibility concepts, without mentioning the specific component types.
Since the bundle has public fields, the reader can easily click them and
read the documentation if they need to know more. I removed the mention
of numbers of components since they were four, now they are five, and
who knows how many they will be in the future. In this process, I
removed the bullet points, which are no longer needed, and were
contextually wrong in the first place, since they were meant to list the
components, but ended up describing use-cases and requirements for
hierarchies.

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
rdrpenguin04 pushed a commit to rdrpenguin04/bevy that referenced this pull request Jan 9, 2024
# Objective

Fix a typo introduced by bevyengine#9497. While drafting the PR, the type was
originally called `VisibleInHierarchy` before I renamed it to
`InheritedVisibility`, but this field got left behind due to a typo.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Rendering Drawing game state to the screen C-Breaking-Change A breaking change to Bevy's public API that needs to be noted in a migration guide C-Bug An unexpected or incorrect behavior C-Performance A change motivated by improving speed, memory usage or compile times
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Changed<ComputedVisibility> always evaluates as true
6 participants