Remove the type parameter from check_visibility, and only invoke it once. (#16812)
				
					
				
			Currently, `check_visibility` is parameterized over a query filter that specifies the type of potentially-visible object. This has the unfortunate side effect that we need a separate system, `mark_view_visibility_as_changed_if_necessary`, to trigger view visibility change detection. That system is quite slow because it must iterate sequentially over all entities in the scene. This PR moves the query filter from `check_visibility` to a new component, `VisibilityClass`. `VisibilityClass` stores a list of type IDs, each corresponding to one of the query filters we used to use. Because `check_visibility` is no longer specialized to the query filter at the type level, Bevy now only needs to invoke it once, leading to better performance as `check_visibility` can do change detection on the fly rather than delegating it to a separate system. This commit also has ergonomic improvements, as there's no need for applications that want to add their own custom renderable components to add specializations of the `check_visibility` system to the schedule. Instead, they only need to ensure that the `ViewVisibility` component is properly kept up to date. The recommended way to do this, and the way that's demonstrated in the `custom_phase_item` and `specialized_mesh_pipeline` examples, is to make `ViewVisibility` a required component and to add the type ID to it in a component add hook. This patch does this for `Mesh3d`, `Mesh2d`, `Sprite`, `Light`, and `Node`, which means that most app code doesn't need to change at all. Note that, although this patch has a large impact on the performance of visibility determination, it doesn't actually improve the end-to-end frame time of `many_cubes`. That's because the render world was already effectively hiding the latency from `mark_view_visibility_as_changed_if_necessary`. This patch is, however, necessary for *further* improvements to `many_cubes` performance. `many_cubes` trace before:  `many_cubes` trace after:  ## Migration Guide * `check_visibility` no longer takes a `QueryFilter`, and there's no need to add it manually to your app schedule anymore for custom rendering items. Instead, entities with custom renderable components should add the appropriate type IDs to `VisibilityClass`. See `custom_phase_item` for an example.
This commit is contained in:
		
							parent
							
								
									ac1faf073f
								
							
						
					
					
						commit
						40df1ea4b6
					
				| @ -132,7 +132,7 @@ use bevy_render::{ | ||||
|     render_resource::Shader, | ||||
|     sync_component::SyncComponentPlugin, | ||||
|     texture::GpuImage, | ||||
|     view::{check_visibility, VisibilitySystems}, | ||||
|     view::VisibilitySystems, | ||||
|     ExtractSchedule, Render, RenderApp, RenderSet, | ||||
| }; | ||||
| 
 | ||||
| @ -383,8 +383,7 @@ impl Plugin for PbrPlugin { | ||||
|                         .in_set(SimulationLightSystems::AssignLightsToClusters) | ||||
|                         .after(TransformSystem::TransformPropagate) | ||||
|                         .after(VisibilitySystems::CheckVisibility) | ||||
|                         .after(CameraUpdateSystem) | ||||
|                         .ambiguous_with(VisibilitySystems::VisibilityChangeDetect), | ||||
|                         .after(CameraUpdateSystem), | ||||
|                     clear_directional_light_cascades | ||||
|                         .in_set(SimulationLightSystems::UpdateDirectionalLightCascades) | ||||
|                         .after(TransformSystem::TransformPropagate) | ||||
| @ -398,8 +397,7 @@ impl Plugin for PbrPlugin { | ||||
|                         // We assume that no entity will be both a directional light and a spot light,
 | ||||
|                         // so these systems will run independently of one another.
 | ||||
|                         // FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481.
 | ||||
|                         .ambiguous_with(update_spot_light_frusta) | ||||
|                         .ambiguous_with(VisibilitySystems::VisibilityChangeDetect), | ||||
|                         .ambiguous_with(update_spot_light_frusta), | ||||
|                     update_point_light_frusta | ||||
|                         .in_set(SimulationLightSystems::UpdateLightFrusta) | ||||
|                         .after(TransformSystem::TransformPropagate) | ||||
| @ -408,7 +406,6 @@ impl Plugin for PbrPlugin { | ||||
|                         .in_set(SimulationLightSystems::UpdateLightFrusta) | ||||
|                         .after(TransformSystem::TransformPropagate) | ||||
|                         .after(SimulationLightSystems::AssignLightsToClusters), | ||||
|                     check_visibility::<WithLight>.in_set(VisibilitySystems::CheckVisibility), | ||||
|                     ( | ||||
|                         check_dir_light_mesh_visibility, | ||||
|                         check_point_light_mesh_visibility, | ||||
| @ -420,8 +417,7 @@ impl Plugin for PbrPlugin { | ||||
|                         // NOTE: This MUST be scheduled AFTER the core renderer visibility check
 | ||||
|                         // because that resets entity `ViewVisibility` for the first view
 | ||||
|                         // which would override any results from this otherwise
 | ||||
|                         .after(VisibilitySystems::CheckVisibility) | ||||
|                         .ambiguous_with(VisibilitySystems::VisibilityChangeDetect), | ||||
|                         .after(VisibilitySystems::CheckVisibility), | ||||
|                 ), | ||||
|             ); | ||||
| 
 | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| use bevy_render::view::Visibility; | ||||
| use bevy_render::view::{self, Visibility}; | ||||
| 
 | ||||
| use super::*; | ||||
| 
 | ||||
| @ -57,8 +57,10 @@ use super::*; | ||||
|     CascadeShadowConfig, | ||||
|     CascadesVisibleEntities, | ||||
|     Transform, | ||||
|     Visibility | ||||
|     Visibility, | ||||
|     VisibilityClass | ||||
| )] | ||||
| #[component(on_add = view::add_visibility_class::<LightVisibilityClass>)] | ||||
| pub struct DirectionalLight { | ||||
|     /// The color of the light.
 | ||||
|     ///
 | ||||
|  | ||||
| @ -13,8 +13,8 @@ use bevy_render::{ | ||||
|     mesh::Mesh3d, | ||||
|     primitives::{Aabb, CascadesFrusta, CubemapFrusta, Frustum, Sphere}, | ||||
|     view::{ | ||||
|         InheritedVisibility, NoFrustumCulling, RenderLayers, ViewVisibility, VisibilityRange, | ||||
|         VisibleEntityRanges, | ||||
|         InheritedVisibility, NoFrustumCulling, RenderLayers, ViewVisibility, VisibilityClass, | ||||
|         VisibilityRange, VisibleEntityRanges, | ||||
|     }, | ||||
| }; | ||||
| use bevy_transform::components::{GlobalTransform, Transform}; | ||||
| @ -503,6 +503,9 @@ pub enum ShadowFilteringMethod { | ||||
|     Temporal, | ||||
| } | ||||
| 
 | ||||
| /// The [`VisibilityClass`] used for all lights (point, directional, and spot).
 | ||||
| pub struct LightVisibilityClass; | ||||
| 
 | ||||
| /// System sets used to run light-related systems.
 | ||||
| #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] | ||||
| pub enum SimulationLightSystems { | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| use bevy_render::view::Visibility; | ||||
| use bevy_render::view::{self, Visibility}; | ||||
| 
 | ||||
| use super::*; | ||||
| 
 | ||||
| @ -21,7 +21,14 @@ use super::*; | ||||
| /// Source: [Wikipedia](https://en.wikipedia.org/wiki/Lumen_(unit)#Lighting)
 | ||||
| #[derive(Component, Debug, Clone, Copy, Reflect)] | ||||
| #[reflect(Component, Default, Debug)] | ||||
| #[require(CubemapFrusta, CubemapVisibleEntities, Transform, Visibility)] | ||||
| #[require(
 | ||||
|     CubemapFrusta, | ||||
|     CubemapVisibleEntities, | ||||
|     Transform, | ||||
|     Visibility, | ||||
|     VisibilityClass | ||||
| )] | ||||
| #[component(on_add = view::add_visibility_class::<LightVisibilityClass>)] | ||||
| pub struct PointLight { | ||||
|     /// The color of this light source.
 | ||||
|     pub color: Color, | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| use bevy_render::view::Visibility; | ||||
| use bevy_render::view::{self, Visibility}; | ||||
| 
 | ||||
| use super::*; | ||||
| 
 | ||||
| @ -9,7 +9,8 @@ use super::*; | ||||
| /// the transform, and can be specified with [`Transform::looking_at`](Transform::looking_at).
 | ||||
| #[derive(Component, Debug, Clone, Copy, Reflect)] | ||||
| #[reflect(Component, Default, Debug)] | ||||
| #[require(Frustum, VisibleMeshEntities, Transform, Visibility)] | ||||
| #[require(Frustum, VisibleMeshEntities, Transform, Visibility, VisibilityClass)] | ||||
| #[component(on_add = view::add_visibility_class::<LightVisibilityClass>)] | ||||
| pub struct SpotLight { | ||||
|     /// The color of the light.
 | ||||
|     ///
 | ||||
|  | ||||
| @ -780,7 +780,7 @@ pub fn queue_material_meshes<M: Material>( | ||||
|         } | ||||
| 
 | ||||
|         let rangefinder = view.rangefinder3d(); | ||||
|         for (render_entity, visible_entity) in visible_entities.iter::<With<Mesh3d>>() { | ||||
|         for (render_entity, visible_entity) in visible_entities.iter::<Mesh3d>() { | ||||
|             let Some(material_asset_id) = render_material_instances.get(visible_entity) else { | ||||
|                 continue; | ||||
|             }; | ||||
|  | ||||
| @ -59,7 +59,7 @@ use self::{ | ||||
|     visibility_buffer_raster_node::MeshletVisibilityBufferRasterPassNode, | ||||
| }; | ||||
| use crate::{graph::NodePbr, Material, MeshMaterial3d, PreviousGlobalTransform}; | ||||
| use bevy_app::{App, Plugin, PostUpdate}; | ||||
| use bevy_app::{App, Plugin}; | ||||
| use bevy_asset::{load_internal_asset, AssetApp, AssetId, Handle}; | ||||
| use bevy_core_pipeline::{ | ||||
|     core_3d::graph::{Core3d, Node3d}, | ||||
| @ -70,7 +70,6 @@ use bevy_ecs::{ | ||||
|     bundle::Bundle, | ||||
|     component::{require, Component}, | ||||
|     entity::Entity, | ||||
|     prelude::With, | ||||
|     query::Has, | ||||
|     reflect::ReflectComponent, | ||||
|     schedule::IntoSystemConfigs, | ||||
| @ -83,8 +82,8 @@ use bevy_render::{ | ||||
|     renderer::RenderDevice, | ||||
|     settings::WgpuFeatures, | ||||
|     view::{ | ||||
|         check_visibility, prepare_view_targets, InheritedVisibility, Msaa, ViewVisibility, | ||||
|         Visibility, VisibilitySystems, | ||||
|         self, prepare_view_targets, InheritedVisibility, Msaa, ViewVisibility, Visibility, | ||||
|         VisibilityClass, | ||||
|     }, | ||||
|     ExtractSchedule, Render, RenderApp, RenderSet, | ||||
| }; | ||||
| @ -219,11 +218,7 @@ impl Plugin for MeshletPlugin { | ||||
|         ); | ||||
| 
 | ||||
|         app.init_asset::<MeshletMesh>() | ||||
|             .register_asset_loader(MeshletMeshLoader) | ||||
|             .add_systems( | ||||
|                 PostUpdate, | ||||
|                 check_visibility::<With<MeshletMesh3d>>.in_set(VisibilitySystems::CheckVisibility), | ||||
|             ); | ||||
|             .register_asset_loader(MeshletMeshLoader); | ||||
|     } | ||||
| 
 | ||||
|     fn finish(&self, app: &mut App) { | ||||
| @ -303,7 +298,8 @@ impl Plugin for MeshletPlugin { | ||||
| /// The meshlet mesh equivalent of [`bevy_render::mesh::Mesh3d`].
 | ||||
| #[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq, From)] | ||||
| #[reflect(Component, Default)] | ||||
| #[require(Transform, PreviousGlobalTransform, Visibility)] | ||||
| #[require(Transform, PreviousGlobalTransform, Visibility, VisibilityClass)] | ||||
| #[component(on_add = view::add_visibility_class::<MeshletMesh3d>)] | ||||
| pub struct MeshletMesh3d(pub Handle<MeshletMesh>); | ||||
| 
 | ||||
| impl From<MeshletMesh3d> for AssetId<MeshletMesh> { | ||||
|  | ||||
| @ -842,7 +842,7 @@ pub fn queue_prepass_material_meshes<M: Material>( | ||||
|             view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS; | ||||
|         } | ||||
| 
 | ||||
|         for (render_entity, visible_entity) in visible_entities.iter::<With<Mesh3d>>() { | ||||
|         for (render_entity, visible_entity) in visible_entities.iter::<Mesh3d>() { | ||||
|             let Some(material_asset_id) = render_material_instances.get(visible_entity) else { | ||||
|                 continue; | ||||
|             }; | ||||
|  | ||||
| @ -1,4 +1,7 @@ | ||||
| use crate::{mesh::Mesh, view::Visibility}; | ||||
| use crate::{ | ||||
|     mesh::Mesh, | ||||
|     view::{self, Visibility, VisibilityClass}, | ||||
| }; | ||||
| use bevy_asset::{AssetId, Handle}; | ||||
| use bevy_derive::{Deref, DerefMut}; | ||||
| use bevy_ecs::{component::Component, prelude::require, reflect::ReflectComponent}; | ||||
| @ -35,7 +38,8 @@ use derive_more::derive::From; | ||||
| /// ```
 | ||||
| #[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq, From)] | ||||
| #[reflect(Component, Default)] | ||||
| #[require(Transform, Visibility)] | ||||
| #[require(Transform, Visibility, VisibilityClass)] | ||||
| #[component(on_add = view::add_visibility_class::<Mesh2d>)] | ||||
| pub struct Mesh2d(pub Handle<Mesh>); | ||||
| 
 | ||||
| impl From<Mesh2d> for AssetId<Mesh> { | ||||
| @ -82,7 +86,8 @@ impl From<&Mesh2d> for AssetId<Mesh> { | ||||
| /// ```
 | ||||
| #[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq, From)] | ||||
| #[reflect(Component, Default)] | ||||
| #[require(Transform, Visibility)] | ||||
| #[require(Transform, Visibility, VisibilityClass)] | ||||
| #[component(on_add = view::add_visibility_class::<Mesh3d>)] | ||||
| pub struct Mesh3d(pub Handle<Mesh>); | ||||
| 
 | ||||
| impl From<Mesh3d> for AssetId<Mesh> { | ||||
|  | ||||
| @ -5,18 +5,21 @@ mod render_layers; | ||||
| 
 | ||||
| use core::any::TypeId; | ||||
| 
 | ||||
| use bevy_ecs::component::ComponentId; | ||||
| use bevy_ecs::entity::EntityHashSet; | ||||
| use bevy_ecs::world::DeferredWorld; | ||||
| use derive_more::derive::{Deref, DerefMut}; | ||||
| pub use range::*; | ||||
| pub use render_layers::*; | ||||
| 
 | ||||
| use bevy_app::{Plugin, PostUpdate}; | ||||
| use bevy_asset::Assets; | ||||
| use bevy_derive::{Deref, DerefMut}; | ||||
| use bevy_ecs::{change_detection::DetectChangesMut as _, prelude::*, query::QueryFilter}; | ||||
| use bevy_ecs::prelude::*; | ||||
| use bevy_hierarchy::{Children, Parent}; | ||||
| use bevy_reflect::{std_traits::ReflectDefault, Reflect}; | ||||
| use bevy_transform::{components::GlobalTransform, TransformSystem}; | ||||
| use bevy_utils::{Parallel, TypeIdMap}; | ||||
| use smallvec::SmallVec; | ||||
| 
 | ||||
| use super::NoCpuCulling; | ||||
| use crate::sync_world::MainEntity; | ||||
| @ -126,6 +129,34 @@ impl InheritedVisibility { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// A bucket into which we group entities for the purposes of visibility.
 | ||||
| ///
 | ||||
| /// Bevy's various rendering subsystems (3D, 2D, UI, etc.) want to be able to
 | ||||
| /// quickly winnow the set of entities to only those that the subsystem is
 | ||||
| /// tasked with rendering, to avoid spending time examining irrelevant entities.
 | ||||
| /// At the same time, Bevy wants the [`check_visibility`] system to determine
 | ||||
| /// all entities' visibilities at the same time, regardless of what rendering
 | ||||
| /// subsystem is responsible for drawing them. Additionally, your application
 | ||||
| /// may want to add more types of renderable objects that Bevy determines
 | ||||
| /// visibility for just as it does for Bevy's built-in objects.
 | ||||
| ///
 | ||||
| /// The solution to this problem is *visibility classes*. A visibility class is
 | ||||
| /// a type, typically the type of a component, that represents the subsystem
 | ||||
| /// that renders it: for example, `Mesh3d`, `Mesh2d`, and `Sprite`. The
 | ||||
| /// [`VisibilityClass`] component stores the visibility class or classes that
 | ||||
| /// the entity belongs to. (Generally, an object will belong to only one
 | ||||
| /// visibility class, but in rare cases it may belong to multiple.)
 | ||||
| ///
 | ||||
| /// When adding a new renderable component, you'll typically want to write an
 | ||||
| /// add-component hook that adds the type ID of that component to the
 | ||||
| /// [`VisibilityClass`] array. See `custom_phase_item` for an example.
 | ||||
| //
 | ||||
| // Note: This can't be a `ComponentId` because the visibility classes are copied
 | ||||
| // into the render world, and component IDs are per-world.
 | ||||
| #[derive(Clone, Component, Default, Reflect, Deref, DerefMut)] | ||||
| #[reflect(Component, Default)] | ||||
| pub struct VisibilityClass(pub SmallVec<[TypeId; 1]>); | ||||
| 
 | ||||
| /// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering.
 | ||||
| ///
 | ||||
| /// Each frame, this will be reset to `false` during [`VisibilityPropagate`] systems in [`PostUpdate`].
 | ||||
| @ -219,56 +250,42 @@ pub struct VisibleEntities { | ||||
| } | ||||
| 
 | ||||
| impl VisibleEntities { | ||||
|     pub fn get<QF>(&self) -> &[Entity] | ||||
|     where | ||||
|         QF: 'static, | ||||
|     { | ||||
|         match self.entities.get(&TypeId::of::<QF>()) { | ||||
|     pub fn get(&self, type_id: TypeId) -> &[Entity] { | ||||
|         match self.entities.get(&type_id) { | ||||
|             Some(entities) => &entities[..], | ||||
|             None => &[], | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn get_mut<QF>(&mut self) -> &mut Vec<Entity> | ||||
|     where | ||||
|         QF: 'static, | ||||
|     { | ||||
|         self.entities.entry(TypeId::of::<QF>()).or_default() | ||||
|     pub fn get_mut(&mut self, type_id: TypeId) -> &mut Vec<Entity> { | ||||
|         self.entities.entry(type_id).or_default() | ||||
|     } | ||||
| 
 | ||||
|     pub fn iter<QF>(&self) -> impl DoubleEndedIterator<Item = &Entity> | ||||
|     where | ||||
|         QF: 'static, | ||||
|     { | ||||
|         self.get::<QF>().iter() | ||||
|     pub fn iter(&self, type_id: TypeId) -> impl DoubleEndedIterator<Item = &Entity> { | ||||
|         self.get(type_id).iter() | ||||
|     } | ||||
| 
 | ||||
|     pub fn len<QF>(&self) -> usize | ||||
|     where | ||||
|         QF: 'static, | ||||
|     { | ||||
|         self.get::<QF>().len() | ||||
|     pub fn len(&self, type_id: TypeId) -> usize { | ||||
|         self.get(type_id).len() | ||||
|     } | ||||
| 
 | ||||
|     pub fn is_empty<QF>(&self) -> bool | ||||
|     where | ||||
|         QF: 'static, | ||||
|     { | ||||
|         self.get::<QF>().is_empty() | ||||
|     pub fn is_empty(&self, type_id: TypeId) -> bool { | ||||
|         self.get(type_id).is_empty() | ||||
|     } | ||||
| 
 | ||||
|     pub fn clear<QF>(&mut self) | ||||
|     where | ||||
|         QF: 'static, | ||||
|     { | ||||
|         self.get_mut::<QF>().clear(); | ||||
|     pub fn clear(&mut self, type_id: TypeId) { | ||||
|         self.get_mut(type_id).clear(); | ||||
|     } | ||||
| 
 | ||||
|     pub fn push<QF>(&mut self, entity: Entity) | ||||
|     where | ||||
|         QF: 'static, | ||||
|     { | ||||
|         self.get_mut::<QF>().push(entity); | ||||
|     pub fn clear_all(&mut self) { | ||||
|         // Don't just nuke the hash table; we want to reuse allocations.
 | ||||
|         for entities in self.entities.values_mut() { | ||||
|             entities.clear(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn push(&mut self, entity: Entity, type_id: TypeId) { | ||||
|         self.get_mut(type_id).push(entity); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @ -332,10 +349,6 @@ pub enum VisibilitySystems { | ||||
|     /// the order of systems within this set is irrelevant, as [`check_visibility`]
 | ||||
|     /// assumes that its operations are irreversible during the frame.
 | ||||
|     CheckVisibility, | ||||
|     /// Label for the system that runs after visibility checking and marks
 | ||||
|     /// entities that have gone from visible to invisible, or vice versa, as
 | ||||
|     /// changed.
 | ||||
|     VisibilityChangeDetect, | ||||
| } | ||||
| 
 | ||||
| pub struct VisibilityPlugin; | ||||
| @ -344,22 +357,21 @@ impl Plugin for VisibilityPlugin { | ||||
|     fn build(&self, app: &mut bevy_app::App) { | ||||
|         use VisibilitySystems::*; | ||||
| 
 | ||||
|         app.configure_sets( | ||||
|         app.register_type::<VisibilityClass>() | ||||
|             .configure_sets( | ||||
|                 PostUpdate, | ||||
|                 (CalculateBounds, UpdateFrusta, VisibilityPropagate) | ||||
|                     .before(CheckVisibility) | ||||
|                     .after(TransformSystem::TransformPropagate), | ||||
|             ) | ||||
|         .configure_sets(PostUpdate, CheckVisibility.ambiguous_with(CheckVisibility)) | ||||
|         .configure_sets(PostUpdate, VisibilityChangeDetect.after(CheckVisibility)) | ||||
|             .init_resource::<PreviousVisibleEntities>() | ||||
|             .add_systems( | ||||
|                 PostUpdate, | ||||
|                 ( | ||||
|                     calculate_bounds.in_set(CalculateBounds), | ||||
|                 (visibility_propagate_system, reset_view_visibility).in_set(VisibilityPropagate), | ||||
|                 check_visibility::<With<Mesh3d>>.in_set(CheckVisibility), | ||||
|                 mark_view_visibility_as_changed_if_necessary.in_set(VisibilityChangeDetect), | ||||
|                     (visibility_propagate_system, reset_view_visibility) | ||||
|                         .in_set(VisibilityPropagate), | ||||
|                     check_visibility.in_set(CheckVisibility), | ||||
|                 ), | ||||
|             ); | ||||
|     } | ||||
| @ -465,9 +477,6 @@ fn propagate_recursive( | ||||
| } | ||||
| 
 | ||||
| /// Stores all entities that were visible in the previous frame.
 | ||||
| ///
 | ||||
| /// At the end of visibility checking, we compare the visible entities against
 | ||||
| /// these and set the [`ViewVisibility`] change flags accordingly.
 | ||||
| #[derive(Resource, Default, Deref, DerefMut)] | ||||
| pub struct PreviousVisibleEntities(EntityHashSet); | ||||
| 
 | ||||
| @ -475,18 +484,16 @@ pub struct PreviousVisibleEntities(EntityHashSet); | ||||
| /// Entities that are visible will be marked as such later this frame
 | ||||
| /// by a [`VisibilitySystems::CheckVisibility`] system.
 | ||||
| fn reset_view_visibility( | ||||
|     mut query: Query<(Entity, &mut ViewVisibility)>, | ||||
|     mut query: Query<(Entity, &ViewVisibility)>, | ||||
|     mut previous_visible_entities: ResMut<PreviousVisibleEntities>, | ||||
| ) { | ||||
|     previous_visible_entities.clear(); | ||||
| 
 | ||||
|     query.iter_mut().for_each(|(entity, mut view_visibility)| { | ||||
|     query.iter_mut().for_each(|(entity, view_visibility)| { | ||||
|         // Record the entities that were previously visible.
 | ||||
|         if view_visibility.get() { | ||||
|             previous_visible_entities.insert(entity); | ||||
|         } | ||||
| 
 | ||||
|         *view_visibility.bypass_change_detection() = ViewVisibility::HIDDEN; | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| @ -496,12 +503,10 @@ fn reset_view_visibility( | ||||
| /// frame, it updates the [`ViewVisibility`] of all entities, and for each view
 | ||||
| /// also compute the [`VisibleEntities`] for that view.
 | ||||
| ///
 | ||||
| /// This system needs to be run for each type of renderable entity. If you add a
 | ||||
| /// new type of renderable entity, you'll need to add an instantiation of this
 | ||||
| /// system to the [`VisibilitySystems::CheckVisibility`] set so that Bevy will
 | ||||
| /// detect visibility properly for those entities.
 | ||||
| pub fn check_visibility<QF>( | ||||
|     mut thread_queues: Local<Parallel<Vec<Entity>>>, | ||||
| /// To ensure that an entity is checked for visibility, make sure that it has a
 | ||||
| /// [`VisibilityClass`] component and that that component is nonempty.
 | ||||
| pub fn check_visibility( | ||||
|     mut thread_queues: Local<Parallel<TypeIdMap<Vec<Entity>>>>, | ||||
|     mut view_query: Query<( | ||||
|         Entity, | ||||
|         &mut VisibleEntities, | ||||
| @ -510,23 +515,20 @@ pub fn check_visibility<QF>( | ||||
|         &Camera, | ||||
|         Has<NoCpuCulling>, | ||||
|     )>, | ||||
|     mut visible_aabb_query: Query< | ||||
|         ( | ||||
|     mut visible_aabb_query: Query<( | ||||
|         Entity, | ||||
|         &InheritedVisibility, | ||||
|         &mut ViewVisibility, | ||||
|         &VisibilityClass, | ||||
|         Option<&RenderLayers>, | ||||
|         Option<&Aabb>, | ||||
|         &GlobalTransform, | ||||
|         Has<NoFrustumCulling>, | ||||
|         Has<VisibilityRange>, | ||||
|         ), | ||||
|         QF, | ||||
|     >, | ||||
|     )>, | ||||
|     visible_entity_ranges: Option<Res<VisibleEntityRanges>>, | ||||
| ) where | ||||
|     QF: QueryFilter + 'static, | ||||
| { | ||||
|     mut previous_visible_entities: ResMut<PreviousVisibleEntities>, | ||||
| ) { | ||||
|     let visible_entity_ranges = visible_entity_ranges.as_deref(); | ||||
| 
 | ||||
|     for (view, mut visible_entities, frustum, maybe_view_mask, camera, no_cpu_culling) in | ||||
| @ -545,6 +547,7 @@ pub fn check_visibility<QF>( | ||||
|                     entity, | ||||
|                     inherited_visibility, | ||||
|                     mut view_visibility, | ||||
|                     visibility_class, | ||||
|                     maybe_entity_mask, | ||||
|                     maybe_model_aabb, | ||||
|                     transform, | ||||
| @ -592,34 +595,70 @@ pub fn check_visibility<QF>( | ||||
|                 } | ||||
| 
 | ||||
|                 // Make sure we don't trigger changed notifications
 | ||||
|                 // unnecessarily.
 | ||||
|                 view_visibility.bypass_change_detection().set(); | ||||
|                 // unnecessarily by checking whether the flag is set before
 | ||||
|                 // setting it.
 | ||||
|                 if !**view_visibility { | ||||
|                     view_visibility.set(); | ||||
|                 } | ||||
| 
 | ||||
|                 queue.push(entity); | ||||
|                 // Add the entity to the queue for all visibility classes the
 | ||||
|                 // entity is in.
 | ||||
|                 for visibility_class_id in visibility_class.iter() { | ||||
|                     queue.entry(*visibility_class_id).or_default().push(entity); | ||||
|                 } | ||||
|             }, | ||||
|         ); | ||||
| 
 | ||||
|         visible_entities.clear::<QF>(); | ||||
|         thread_queues.drain_into(visible_entities.get_mut::<QF>()); | ||||
|         visible_entities.clear_all(); | ||||
| 
 | ||||
|         // Drain all the thread queues into the `visible_entities` list.
 | ||||
|         for class_queues in thread_queues.iter_mut() { | ||||
|             for (class, entities) in class_queues { | ||||
|                 let visible_entities_for_class = visible_entities.get_mut(*class); | ||||
|                 for entity in entities.drain(..) { | ||||
|                     // As we mark entities as visible, we remove them from the
 | ||||
|                     // `previous_visible_entities` list. At the end, all of the
 | ||||
|                     // entities remaining in `previous_visible_entities` will be
 | ||||
|                     // entities that were visible last frame but are no longer
 | ||||
|                     // visible this frame.
 | ||||
|                     previous_visible_entities.remove(&entity); | ||||
| 
 | ||||
|                     visible_entities_for_class.push(entity); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // Now whatever previous visible entities are left are entities that were
 | ||||
|     // visible last frame but just became invisible.
 | ||||
|     for entity in previous_visible_entities.drain() { | ||||
|         if let Ok((_, _, mut view_visibility, _, _, _, _, _, _)) = | ||||
|             visible_aabb_query.get_mut(entity) | ||||
|         { | ||||
|             *view_visibility = ViewVisibility::HIDDEN; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// A system that marks [`ViewVisibility`] components as changed if their
 | ||||
| /// visibility changed this frame.
 | ||||
| /// A generic component add hook that automatically adds the appropriate
 | ||||
| /// [`VisibilityClass`] to an entity.
 | ||||
| ///
 | ||||
| /// Ordinary change detection doesn't work for this use case because we use
 | ||||
| /// multiple [`check_visibility`] systems, and visibility is set if *any one* of
 | ||||
| /// those systems judges the entity to be visible. Thus, in order to perform
 | ||||
| /// change detection, we need this system, which is a follow-up pass that has a
 | ||||
| /// global view of whether the entity became visible or not.
 | ||||
| fn mark_view_visibility_as_changed_if_necessary( | ||||
|     mut query: Query<(Entity, &mut ViewVisibility)>, | ||||
|     previous_visible_entities: Res<PreviousVisibleEntities>, | ||||
| ) { | ||||
|     for (entity, mut view_visibility) in query.iter_mut() { | ||||
|         if previous_visible_entities.contains(&entity) != view_visibility.get() { | ||||
|             view_visibility.set_changed(); | ||||
|         } | ||||
| /// This can be handy when creating custom renderable components. To use this
 | ||||
| /// hook, add it to your renderable component like this:
 | ||||
| ///
 | ||||
| /// ```ignore
 | ||||
| /// #[derive(Component)]
 | ||||
| /// #[component(on_add = add_visibility_class::<MyComponent>)]
 | ||||
| /// struct MyComponent {
 | ||||
| ///     ...
 | ||||
| /// }
 | ||||
| /// ```
 | ||||
| pub fn add_visibility_class<C>(mut world: DeferredWorld<'_>, entity: Entity, _: ComponentId) | ||||
| where | ||||
|     C: 'static, | ||||
| { | ||||
|     if let Some(mut visibility_class) = world.get_mut::<VisibilityClass>(entity) { | ||||
|         visibility_class.push(TypeId::of::<C>()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -27,7 +27,6 @@ use super::{check_visibility, VisibilitySystems}; | ||||
| use crate::sync_world::{MainEntity, MainEntityHashMap}; | ||||
| use crate::{ | ||||
|     camera::Camera, | ||||
|     mesh::Mesh3d, | ||||
|     primitives::Aabb, | ||||
|     render_resource::BufferVec, | ||||
|     renderer::{RenderDevice, RenderQueue}, | ||||
| @ -59,7 +58,7 @@ impl Plugin for VisibilityRangePlugin { | ||||
|                 PostUpdate, | ||||
|                 check_visibility_ranges | ||||
|                     .in_set(VisibilitySystems::CheckVisibility) | ||||
|                     .before(check_visibility::<With<Mesh3d>>), | ||||
|                     .before(check_visibility), | ||||
|             ); | ||||
| 
 | ||||
|         let Some(render_app) = app.get_sub_app_mut(RenderApp) else { | ||||
|  | ||||
| @ -59,7 +59,7 @@ use bevy_render::{ | ||||
|     primitives::Aabb, | ||||
|     render_phase::AddRenderCommand, | ||||
|     render_resource::{Shader, SpecializedRenderPipelines}, | ||||
|     view::{check_visibility, NoFrustumCulling, VisibilitySystems}, | ||||
|     view::{self, NoFrustumCulling, VisibilityClass, VisibilitySystems}, | ||||
|     ExtractSchedule, Render, RenderApp, RenderSet, | ||||
| }; | ||||
| 
 | ||||
| @ -96,12 +96,10 @@ pub enum SpriteSystem { | ||||
| /// Right now, this is used for `Text`.
 | ||||
| #[derive(Component, Reflect, Clone, Copy, Debug, Default)] | ||||
| #[reflect(Component, Default, Debug)] | ||||
| #[require(VisibilityClass)] | ||||
| #[component(on_add = view::add_visibility_class::<Sprite>)] | ||||
| pub struct SpriteSource; | ||||
| 
 | ||||
| /// A convenient alias for `Or<With<Sprite>, With<SpriteSource>>`, for use with
 | ||||
| /// [`bevy_render::view::VisibleEntities`].
 | ||||
| pub type WithSprite = Or<(With<Sprite>, With<SpriteSource>)>; | ||||
| 
 | ||||
| impl Plugin for SpritePlugin { | ||||
|     fn build(&self, app: &mut App) { | ||||
|         load_internal_asset!( | ||||
| @ -139,11 +137,6 @@ impl Plugin for SpritePlugin { | ||||
|                         compute_slices_on_sprite_change, | ||||
|                     ) | ||||
|                         .in_set(SpriteSystem::ComputeSlices), | ||||
|                     ( | ||||
|                         check_visibility::<With<Mesh2d>>, | ||||
|                         check_visibility::<WithSprite>, | ||||
|                     ) | ||||
|                         .in_set(VisibilitySystems::CheckVisibility), | ||||
|                 ), | ||||
|             ); | ||||
| 
 | ||||
|  | ||||
| @ -525,7 +525,7 @@ pub fn queue_material2d_meshes<M: Material2d>( | ||||
|                 view_key |= Mesh2dPipelineKey::DEBAND_DITHER; | ||||
|             } | ||||
|         } | ||||
|         for (render_entity, visible_entity) in visible_entities.iter::<With<Mesh2d>>() { | ||||
|         for (render_entity, visible_entity) in visible_entities.iter::<Mesh2d>() { | ||||
|             let Some(material_asset_id) = render_material_instances.get(visible_entity) else { | ||||
|                 continue; | ||||
|             }; | ||||
|  | ||||
| @ -1,8 +1,7 @@ | ||||
| use core::ops::Range; | ||||
| 
 | ||||
| use crate::{ | ||||
|     texture_atlas::TextureAtlasLayout, ComputedTextureSlices, Sprite, WithSprite, | ||||
|     SPRITE_SHADER_HANDLE, | ||||
|     texture_atlas::TextureAtlasLayout, ComputedTextureSlices, Sprite, SPRITE_SHADER_HANDLE, | ||||
| }; | ||||
| use bevy_asset::{AssetEvent, AssetId, Assets}; | ||||
| use bevy_color::{ColorToComponents, LinearRgba}; | ||||
| @ -551,7 +550,7 @@ pub fn queue_sprites( | ||||
|         view_entities.clear(); | ||||
|         view_entities.extend( | ||||
|             visible_entities | ||||
|                 .iter::<WithSprite>() | ||||
|                 .iter::<Sprite>() | ||||
|                 .map(|(_, e)| e.index() as usize), | ||||
|         ); | ||||
| 
 | ||||
|  | ||||
| @ -7,15 +7,19 @@ use bevy_ecs::{ | ||||
| use bevy_image::Image; | ||||
| use bevy_math::{Rect, UVec2, Vec2}; | ||||
| use bevy_reflect::{std_traits::ReflectDefault, Reflect}; | ||||
| use bevy_render::{sync_world::SyncToRenderWorld, view::Visibility}; | ||||
| use bevy_render::{ | ||||
|     sync_world::SyncToRenderWorld, | ||||
|     view::{self, Visibility, VisibilityClass}, | ||||
| }; | ||||
| use bevy_transform::components::Transform; | ||||
| 
 | ||||
| use crate::{TextureAtlas, TextureAtlasLayout, TextureSlicer}; | ||||
| 
 | ||||
| /// Describes a sprite to be rendered to a 2D camera
 | ||||
| #[derive(Component, Debug, Default, Clone, Reflect)] | ||||
| #[require(Transform, Visibility, SyncToRenderWorld)] | ||||
| #[require(Transform, Visibility, SyncToRenderWorld, VisibilityClass)] | ||||
| #[reflect(Component, Default, Debug)] | ||||
| #[component(on_add = view::add_visibility_class::<Sprite>)] | ||||
| pub struct Sprite { | ||||
|     /// The image used to render the sprite
 | ||||
|     pub image: Handle<Image>, | ||||
|  | ||||
| @ -72,11 +72,7 @@ pub mod prelude { | ||||
| use bevy_app::{prelude::*, Animation}; | ||||
| use bevy_ecs::prelude::*; | ||||
| use bevy_input::InputSystem; | ||||
| use bevy_render::{ | ||||
|     camera::CameraUpdateSystem, | ||||
|     view::{check_visibility, VisibilitySystems}, | ||||
|     RenderApp, | ||||
| }; | ||||
| use bevy_render::{camera::CameraUpdateSystem, RenderApp}; | ||||
| use bevy_transform::TransformSystem; | ||||
| use layout::ui_surface::UiSurface; | ||||
| use stack::ui_stack_system; | ||||
| @ -203,7 +199,6 @@ impl Plugin for UiPlugin { | ||||
|         app.add_systems( | ||||
|             PostUpdate, | ||||
|             ( | ||||
|                 check_visibility::<With<Node>>.in_set(VisibilitySystems::CheckVisibility), | ||||
|                 update_target_camera_system.in_set(UiSystem::Prepare), | ||||
|                 ui_layout_system_config, | ||||
|                 ui_stack_system | ||||
|  | ||||
| @ -6,7 +6,7 @@ use bevy_math::{vec4, Rect, Vec2, Vec4Swizzles}; | ||||
| use bevy_reflect::prelude::*; | ||||
| use bevy_render::{ | ||||
|     camera::{Camera, RenderTarget}, | ||||
|     view::Visibility, | ||||
|     view::{self, Visibility, VisibilityClass}, | ||||
| }; | ||||
| use bevy_sprite::BorderRect; | ||||
| use bevy_transform::components::Transform; | ||||
| @ -328,9 +328,11 @@ impl From<Vec2> for ScrollPosition { | ||||
|     ScrollPosition, | ||||
|     Transform, | ||||
|     Visibility, | ||||
|     VisibilityClass, | ||||
|     ZIndex | ||||
| )] | ||||
| #[reflect(Component, Default, PartialEq, Debug)] | ||||
| #[component(on_add = view::add_visibility_class::<Node>)] | ||||
| #[cfg_attr(
 | ||||
|     feature = "serialize", | ||||
|     derive(serde::Serialize, serde::Deserialize), | ||||
|  | ||||
| @ -386,7 +386,7 @@ pub fn queue_colored_mesh2d( | ||||
|             | Mesh2dPipelineKey::from_hdr(view.hdr); | ||||
| 
 | ||||
|         // Queue all entities visible to that view
 | ||||
|         for (render_entity, visible_entity) in visible_entities.iter::<With<Mesh2d>>() { | ||||
|         for (render_entity, visible_entity) in visible_entities.iter::<Mesh2d>() { | ||||
|             if let Some(mesh_instance) = render_mesh_instances.get(visible_entity) { | ||||
|                 let mesh2d_handle = mesh_instance.mesh_asset_id; | ||||
|                 let mesh2d_transforms = &mesh_instance.transforms; | ||||
|  | ||||
| @ -29,7 +29,7 @@ use bevy::{ | ||||
|             VertexFormat, VertexState, VertexStepMode, | ||||
|         }, | ||||
|         renderer::{RenderDevice, RenderQueue}, | ||||
|         view::{self, ExtractedView, RenderVisibleEntities, VisibilitySystems}, | ||||
|         view::{self, ExtractedView, RenderVisibleEntities, VisibilityClass}, | ||||
|         Render, RenderApp, RenderSet, | ||||
|     }, | ||||
| }; | ||||
| @ -38,9 +38,13 @@ use bytemuck::{Pod, Zeroable}; | ||||
| /// A marker component that represents an entity that is to be rendered using
 | ||||
| /// our custom phase item.
 | ||||
| ///
 | ||||
| /// Note the [`ExtractComponent`] trait implementation. This is necessary to
 | ||||
| /// tell Bevy that this object should be pulled into the render world.
 | ||||
| /// Note the [`ExtractComponent`] trait implementation: this is necessary to
 | ||||
| /// tell Bevy that this object should be pulled into the render world. Also note
 | ||||
| /// the `on_add` hook, which is needed to tell Bevy's `check_visibility` system
 | ||||
| /// that entities with this component need to be examined for visibility.
 | ||||
| #[derive(Clone, Component, ExtractComponent)] | ||||
| #[require(VisibilityClass)] | ||||
| #[component(on_add = view::add_visibility_class::<CustomRenderedEntity>)] | ||||
| struct CustomRenderedEntity; | ||||
| 
 | ||||
| /// Holds a reference to our shader.
 | ||||
| @ -152,10 +156,6 @@ impl Vertex { | ||||
| /// the render phase.
 | ||||
| type DrawCustomPhaseItemCommands = (SetItemPipeline, DrawCustomPhaseItem); | ||||
| 
 | ||||
| /// A query filter that tells [`view::check_visibility`] about our custom
 | ||||
| /// rendered entity.
 | ||||
| type WithCustomRenderedEntity = With<CustomRenderedEntity>; | ||||
| 
 | ||||
| /// A single triangle's worth of vertices, for demonstration purposes.
 | ||||
| static VERTICES: [Vertex; 3] = [ | ||||
|     Vertex::new(vec3(-0.866, -0.5, 0.5), vec3(1.0, 0.0, 0.0)), | ||||
| @ -168,14 +168,7 @@ fn main() { | ||||
|     let mut app = App::new(); | ||||
|     app.add_plugins(DefaultPlugins) | ||||
|         .add_plugins(ExtractComponentPlugin::<CustomRenderedEntity>::default()) | ||||
|         .add_systems(Startup, setup) | ||||
|         // Make sure to tell Bevy to check our entity for visibility. Bevy won't
 | ||||
|         // do this by default, for efficiency reasons.
 | ||||
|         .add_systems( | ||||
|             PostUpdate, | ||||
|             view::check_visibility::<WithCustomRenderedEntity> | ||||
|                 .in_set(VisibilitySystems::CheckVisibility), | ||||
|         ); | ||||
|         .add_systems(Startup, setup); | ||||
| 
 | ||||
|     // We make sure to add these to the render app, not the main app.
 | ||||
|     app.get_sub_app_mut(RenderApp) | ||||
| @ -246,10 +239,7 @@ fn queue_custom_phase_item( | ||||
| 
 | ||||
|         // Find all the custom rendered entities that are visible from this
 | ||||
|         // view.
 | ||||
|         for &entity in view_visible_entities | ||||
|             .get::<WithCustomRenderedEntity>() | ||||
|             .iter() | ||||
|         { | ||||
|         for &entity in view_visible_entities.get::<CustomRenderedEntity>().iter() { | ||||
|             // Ordinarily, the [`SpecializedRenderPipeline::Key`] would contain
 | ||||
|             // some per-view settings, such as whether the view is HDR, but for
 | ||||
|             // simplicity's sake we simply hard-code the view's characteristics,
 | ||||
|  | ||||
| @ -28,7 +28,7 @@ use bevy::{ | ||||
|             RenderPipelineDescriptor, SpecializedMeshPipeline, SpecializedMeshPipelineError, | ||||
|             SpecializedMeshPipelines, TextureFormat, VertexState, | ||||
|         }, | ||||
|         view::{self, ExtractedView, RenderVisibleEntities, ViewTarget, VisibilitySystems}, | ||||
|         view::{self, ExtractedView, RenderVisibleEntities, ViewTarget, VisibilityClass}, | ||||
|         Render, RenderApp, RenderSet, | ||||
|     }, | ||||
| }; | ||||
| @ -97,15 +97,7 @@ fn setup(mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>) { | ||||
| struct CustomRenderedMeshPipelinePlugin; | ||||
| impl Plugin for CustomRenderedMeshPipelinePlugin { | ||||
|     fn build(&self, app: &mut App) { | ||||
|         app.add_plugins(ExtractComponentPlugin::<CustomRenderedEntity>::default()) | ||||
|             .add_systems( | ||||
|                 PostUpdate, | ||||
|                 // Make sure to tell Bevy to check our entity for visibility. Bevy won't
 | ||||
|                 // do this by default, for efficiency reasons.
 | ||||
|                 // This will do things like frustum culling and hierarchy visibility
 | ||||
|                 view::check_visibility::<WithCustomRenderedEntity> | ||||
|                     .in_set(VisibilitySystems::CheckVisibility), | ||||
|             ); | ||||
|         app.add_plugins(ExtractComponentPlugin::<CustomRenderedEntity>::default()); | ||||
| 
 | ||||
|         // We make sure to add these to the render app, not the main app.
 | ||||
|         let Some(render_app) = app.get_sub_app_mut(RenderApp) else { | ||||
| @ -132,9 +124,13 @@ impl Plugin for CustomRenderedMeshPipelinePlugin { | ||||
| /// A marker component that represents an entity that is to be rendered using
 | ||||
| /// our specialized pipeline.
 | ||||
| ///
 | ||||
| /// Note the [`ExtractComponent`] trait implementation. This is necessary to
 | ||||
| /// tell Bevy that this object should be pulled into the render world.
 | ||||
| /// Note the [`ExtractComponent`] trait implementation: this is necessary to
 | ||||
| /// tell Bevy that this object should be pulled into the render world. Also note
 | ||||
| /// the `on_add` hook, which is needed to tell Bevy's `check_visibility` system
 | ||||
| /// that entities with this component need to be examined for visibility.
 | ||||
| #[derive(Clone, Component, ExtractComponent)] | ||||
| #[require(VisibilityClass)] | ||||
| #[component(on_add = view::add_visibility_class::<CustomRenderedEntity>)] | ||||
| struct CustomRenderedEntity; | ||||
| 
 | ||||
| /// The custom draw commands that Bevy executes for each entity we enqueue into
 | ||||
| @ -150,10 +146,6 @@ type DrawSpecializedPipelineCommands = ( | ||||
|     DrawMesh, | ||||
| ); | ||||
| 
 | ||||
| /// A query filter that tells [`view::check_visibility`] about our custom
 | ||||
| /// rendered entity.
 | ||||
| type WithCustomRenderedEntity = With<CustomRenderedEntity>; | ||||
| 
 | ||||
| // This contains the state needed to specialize a mesh pipeline
 | ||||
| #[derive(Resource)] | ||||
| struct CustomMeshPipeline { | ||||
| @ -299,9 +291,8 @@ fn queue_custom_mesh_pipeline( | ||||
| 
 | ||||
|         // Find all the custom rendered entities that are visible from this
 | ||||
|         // view.
 | ||||
|         for &(render_entity, visible_entity) in view_visible_entities | ||||
|             .get::<WithCustomRenderedEntity>() | ||||
|             .iter() | ||||
|         for &(render_entity, visible_entity) in | ||||
|             view_visible_entities.get::<CustomRenderedEntity>().iter() | ||||
|         { | ||||
|             // Get the mesh instance
 | ||||
|             let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(visible_entity) | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Patrick Walton
						Patrick Walton