# 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>
238 lines
7.4 KiB
Rust
238 lines
7.4 KiB
Rust
use crate::{
|
|
render_resource::{encase::internal::WriteInto, DynamicUniformBuffer, ShaderType},
|
|
renderer::{RenderDevice, RenderQueue},
|
|
view::ViewVisibility,
|
|
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
|
|
};
|
|
use bevy_app::{App, Plugin};
|
|
use bevy_asset::{Asset, Handle};
|
|
use bevy_ecs::{
|
|
component::Component,
|
|
prelude::*,
|
|
query::{QueryItem, ReadOnlyWorldQuery, WorldQuery},
|
|
system::lifetimeless::Read,
|
|
};
|
|
use std::{marker::PhantomData, ops::Deref};
|
|
|
|
pub use bevy_render_macros::ExtractComponent;
|
|
|
|
/// Stores the index of a uniform inside of [`ComponentUniforms`].
|
|
#[derive(Component)]
|
|
pub struct DynamicUniformIndex<C: Component> {
|
|
index: u32,
|
|
marker: PhantomData<C>,
|
|
}
|
|
|
|
impl<C: Component> DynamicUniformIndex<C> {
|
|
#[inline]
|
|
pub fn index(&self) -> u32 {
|
|
self.index
|
|
}
|
|
}
|
|
|
|
/// Describes how a component gets extracted for rendering.
|
|
///
|
|
/// Therefore the component is transferred from the "app world" into the "render world"
|
|
/// in the [`ExtractSchedule`](crate::ExtractSchedule) step.
|
|
pub trait ExtractComponent: Component {
|
|
/// ECS [`WorldQuery`] to fetch the components to extract.
|
|
type Query: WorldQuery + ReadOnlyWorldQuery;
|
|
/// Filters the entities with additional constraints.
|
|
type Filter: WorldQuery + ReadOnlyWorldQuery;
|
|
|
|
/// The output from extraction.
|
|
///
|
|
/// Returning `None` based on the queried item can allow early optimization,
|
|
/// for example if there is an `enabled: bool` field on `Self`, or by only accepting
|
|
/// values within certain thresholds.
|
|
///
|
|
/// The output may be different from the queried component.
|
|
/// This can be useful for example if only a subset of the fields are useful
|
|
/// in the render world.
|
|
///
|
|
/// `Out` has a [`Bundle`] trait bound instead of a [`Component`] trait bound in order to allow use cases
|
|
/// such as tuples of components as output.
|
|
type Out: Bundle;
|
|
|
|
// TODO: https://github.com/rust-lang/rust/issues/29661
|
|
// type Out: Component = Self;
|
|
|
|
/// Defines how the component is transferred into the "render world".
|
|
fn extract_component(item: QueryItem<'_, Self::Query>) -> Option<Self::Out>;
|
|
}
|
|
|
|
/// This plugin prepares the components of the corresponding type for the GPU
|
|
/// by transforming them into uniforms.
|
|
///
|
|
/// They can then be accessed from the [`ComponentUniforms`] resource.
|
|
/// For referencing the newly created uniforms a [`DynamicUniformIndex`] is inserted
|
|
/// for every processed entity.
|
|
///
|
|
/// Therefore it sets up the [`RenderSet::Prepare`](crate::RenderSet::Prepare) step
|
|
/// for the specified [`ExtractComponent`].
|
|
pub struct UniformComponentPlugin<C>(PhantomData<fn() -> C>);
|
|
|
|
impl<C> Default for UniformComponentPlugin<C> {
|
|
fn default() -> Self {
|
|
Self(PhantomData)
|
|
}
|
|
}
|
|
|
|
impl<C: Component + ShaderType + WriteInto + Clone> Plugin for UniformComponentPlugin<C> {
|
|
fn build(&self, app: &mut App) {
|
|
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
|
render_app
|
|
.insert_resource(ComponentUniforms::<C>::default())
|
|
.add_systems(
|
|
Render,
|
|
prepare_uniform_components::<C>.in_set(RenderSet::PrepareResources),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Stores all uniforms of the component type.
|
|
#[derive(Resource)]
|
|
pub struct ComponentUniforms<C: Component + ShaderType> {
|
|
uniforms: DynamicUniformBuffer<C>,
|
|
}
|
|
|
|
impl<C: Component + ShaderType> Deref for ComponentUniforms<C> {
|
|
type Target = DynamicUniformBuffer<C>;
|
|
|
|
#[inline]
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.uniforms
|
|
}
|
|
}
|
|
|
|
impl<C: Component + ShaderType> ComponentUniforms<C> {
|
|
#[inline]
|
|
pub fn uniforms(&self) -> &DynamicUniformBuffer<C> {
|
|
&self.uniforms
|
|
}
|
|
}
|
|
|
|
impl<C: Component + ShaderType> Default for ComponentUniforms<C> {
|
|
fn default() -> Self {
|
|
Self {
|
|
uniforms: Default::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// This system prepares all components of the corresponding component type.
|
|
/// They are transformed into uniforms and stored in the [`ComponentUniforms`] resource.
|
|
fn prepare_uniform_components<C: Component>(
|
|
mut commands: Commands,
|
|
render_device: Res<RenderDevice>,
|
|
render_queue: Res<RenderQueue>,
|
|
mut component_uniforms: ResMut<ComponentUniforms<C>>,
|
|
components: Query<(Entity, &C)>,
|
|
) where
|
|
C: ShaderType + WriteInto + Clone,
|
|
{
|
|
component_uniforms.uniforms.clear();
|
|
let entities = components
|
|
.iter()
|
|
.map(|(entity, component)| {
|
|
(
|
|
entity,
|
|
DynamicUniformIndex::<C> {
|
|
index: component_uniforms.uniforms.push(component.clone()),
|
|
marker: PhantomData,
|
|
},
|
|
)
|
|
})
|
|
.collect::<Vec<_>>();
|
|
commands.insert_or_spawn_batch(entities);
|
|
|
|
component_uniforms
|
|
.uniforms
|
|
.write_buffer(&render_device, &render_queue);
|
|
}
|
|
|
|
/// This plugin extracts the components into the "render world".
|
|
///
|
|
/// Therefore it sets up the [`ExtractSchedule`](crate::ExtractSchedule) step
|
|
/// for the specified [`ExtractComponent`].
|
|
pub struct ExtractComponentPlugin<C, F = ()> {
|
|
only_extract_visible: bool,
|
|
marker: PhantomData<fn() -> (C, F)>,
|
|
}
|
|
|
|
impl<C, F> Default for ExtractComponentPlugin<C, F> {
|
|
fn default() -> Self {
|
|
Self {
|
|
only_extract_visible: false,
|
|
marker: PhantomData,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<C, F> ExtractComponentPlugin<C, F> {
|
|
pub fn extract_visible() -> Self {
|
|
Self {
|
|
only_extract_visible: true,
|
|
marker: PhantomData,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<C: ExtractComponent> Plugin for ExtractComponentPlugin<C> {
|
|
fn build(&self, app: &mut App) {
|
|
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
|
if self.only_extract_visible {
|
|
render_app.add_systems(ExtractSchedule, extract_visible_components::<C>);
|
|
} else {
|
|
render_app.add_systems(ExtractSchedule, extract_components::<C>);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T: Asset> ExtractComponent for Handle<T> {
|
|
type Query = Read<Handle<T>>;
|
|
type Filter = ();
|
|
type Out = Handle<T>;
|
|
|
|
#[inline]
|
|
fn extract_component(handle: QueryItem<'_, Self::Query>) -> Option<Self::Out> {
|
|
Some(handle.clone_weak())
|
|
}
|
|
}
|
|
|
|
/// This system extracts all components of the corresponding [`ExtractComponent`] type.
|
|
fn extract_components<C: ExtractComponent>(
|
|
mut commands: Commands,
|
|
mut previous_len: Local<usize>,
|
|
query: Extract<Query<(Entity, C::Query), C::Filter>>,
|
|
) {
|
|
let mut values = Vec::with_capacity(*previous_len);
|
|
for (entity, query_item) in &query {
|
|
if let Some(component) = C::extract_component(query_item) {
|
|
values.push((entity, component));
|
|
}
|
|
}
|
|
*previous_len = values.len();
|
|
commands.insert_or_spawn_batch(values);
|
|
}
|
|
|
|
/// This system extracts all visible components of the corresponding [`ExtractComponent`] type.
|
|
fn extract_visible_components<C: ExtractComponent>(
|
|
mut commands: Commands,
|
|
mut previous_len: Local<usize>,
|
|
query: Extract<Query<(Entity, &ViewVisibility, C::Query), C::Filter>>,
|
|
) {
|
|
let mut values = Vec::with_capacity(*previous_len);
|
|
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));
|
|
}
|
|
}
|
|
}
|
|
*previous_len = values.len();
|
|
commands.insert_or_spawn_batch(values);
|
|
}
|