diff --git a/Cargo.toml b/Cargo.toml index 019ce0c6e9059..d48b179f8d58c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -815,6 +815,17 @@ description = "Demonstrates use of a normal map and depth map for parallax mappi category = "3D Rendering" wasm = true +[[example]] +name = "render_layers" +path = "examples/3d/render_layers.rs" +doc-scrape-examples = true + +[package.metadata.example.render_layers] +name = "Render Layers" +description = "Load a scene from a glTF file and render it with different render layers." +category = "3D Rendering" +wasm = true + [[example]] name = "render_to_texture" path = "examples/3d/render_to_texture.rs" diff --git a/assets/shaders/custom_material.vert b/assets/shaders/custom_material.vert index 91d660ec3194c..88bde8c046cd9 100644 --- a/assets/shaders/custom_material.vert +++ b/assets/shaders/custom_material.vert @@ -6,7 +6,7 @@ layout(location = 2) in vec2 Vertex_Uv; layout(location = 0) out vec2 v_Uv; -layout(set = 0, binding = 0) uniform CameraViewProj { +layout(set = 0, binding = 0) uniform CameraLayerProj { mat4 ViewProj; mat4 View; mat4 InverseView; diff --git a/crates/bevy_dev_tools/src/ui_debug_overlay/mod.rs b/crates/bevy_dev_tools/src/ui_debug_overlay/mod.rs index 3029a05c9b6cc..a7be3eca61dad 100644 --- a/crates/bevy_dev_tools/src/ui_debug_overlay/mod.rs +++ b/crates/bevy_dev_tools/src/ui_debug_overlay/mod.rs @@ -12,7 +12,7 @@ use bevy_math::{Vec2, Vec3Swizzles}; use bevy_render::{ camera::RenderTarget, prelude::*, - view::{RenderLayers, VisibilitySystems}, + view::{RenderLayer, RenderLayers, VisibilitySystems}, }; use bevy_transform::{prelude::GlobalTransform, TransformSystem}; use bevy_ui::{DefaultUiCamera, Display, Node, Style, TargetCamera, UiScale}; @@ -27,8 +27,8 @@ mod inset; /// The [`Camera::order`] index used by the layout debug camera. pub const LAYOUT_DEBUG_CAMERA_ORDER: isize = 255; -/// The [`RenderLayers`] used by the debug gizmos and the debug camera. -pub const LAYOUT_DEBUG_LAYERS: RenderLayers = RenderLayers::none().with(16); +/// The [`RenderLayer`] used by the debug gizmos and the debug camera. +pub const LAYOUT_DEBUG_LAYER: RenderLayer = RenderLayer(16); #[derive(Clone, Copy)] struct LayoutRect { @@ -101,7 +101,7 @@ fn update_debug_camera( }, ..default() }, - LAYOUT_DEBUG_LAYERS, + RenderLayers::from_layer(LAYOUT_DEBUG_LAYER), DebugOverlayCamera, Name::new("Layout Debug Camera"), )) @@ -109,7 +109,7 @@ fn update_debug_camera( }; if let Some((config, _)) = gizmo_config.get_config_mut_dyn(&TypeId::of::()) { config.enabled = true; - config.render_layers = LAYOUT_DEBUG_LAYERS; + config.render_layers = RenderLayers::from_layer(LAYOUT_DEBUG_LAYER); } let cam = *options.layout_gizmos_camera.get_or_insert_with(spawn_cam); let Ok(mut cam) = debug_cams.get_mut(cam) else { diff --git a/crates/bevy_gizmos/src/config.rs b/crates/bevy_gizmos/src/config.rs index 92f6962c19384..c3030d9a3488f 100644 --- a/crates/bevy_gizmos/src/config.rs +++ b/crates/bevy_gizmos/src/config.rs @@ -197,7 +197,7 @@ impl From<&GizmoConfig> for GizmoMeshConfig { GizmoMeshConfig { line_perspective: item.line_perspective, line_style: item.line_style, - render_layers: item.render_layers, + render_layers: item.render_layers.clone(), } } } diff --git a/crates/bevy_gizmos/src/pipeline_2d.rs b/crates/bevy_gizmos/src/pipeline_2d.rs index 5110779078cbf..a29141ff3904f 100644 --- a/crates/bevy_gizmos/src/pipeline_2d.rs +++ b/crates/bevy_gizmos/src/pipeline_2d.rs @@ -20,7 +20,7 @@ use bevy_render::{ render_phase::{AddRenderCommand, DrawFunctions, SetItemPipeline, SortedRenderPhase}, render_resource::*, texture::BevyDefault, - view::{ExtractedView, Msaa, RenderLayers, ViewTarget}, + view::{ExtractedRenderLayers, ExtractedView, Msaa, ViewTarget}, Render, RenderApp, RenderSet, }; use bevy_sprite::{Mesh2dPipeline, Mesh2dPipelineKey, SetMesh2dViewBindGroup}; @@ -257,7 +257,7 @@ fn queue_line_gizmos_2d( mut views: Query<( &ExtractedView, &mut SortedRenderPhase, - Option<&RenderLayers>, + Option<&ExtractedRenderLayers>, )>, ) { let draw_function = draw_functions.read().get_id::().unwrap(); @@ -267,8 +267,7 @@ fn queue_line_gizmos_2d( | Mesh2dPipelineKey::from_hdr(view.hdr); for (entity, handle, config) in &line_gizmos { - let render_layers = render_layers.copied().unwrap_or_default(); - if !config.render_layers.intersects(&render_layers) { + if !config.render_layers.intersects_extracted(render_layers) { continue; } @@ -310,7 +309,7 @@ fn queue_line_joint_gizmos_2d( mut views: Query<( &ExtractedView, &mut SortedRenderPhase, - Option<&RenderLayers>, + Option<&ExtractedRenderLayers>, )>, ) { let draw_function = draw_functions @@ -323,8 +322,7 @@ fn queue_line_joint_gizmos_2d( | Mesh2dPipelineKey::from_hdr(view.hdr); for (entity, handle, config) in &line_gizmos { - let render_layers = render_layers.copied().unwrap_or_default(); - if !config.render_layers.intersects(&render_layers) { + if !config.render_layers.intersects_extracted(render_layers) { continue; } diff --git a/crates/bevy_gizmos/src/pipeline_3d.rs b/crates/bevy_gizmos/src/pipeline_3d.rs index ca53a1cdf2d54..80c6e04835cc1 100644 --- a/crates/bevy_gizmos/src/pipeline_3d.rs +++ b/crates/bevy_gizmos/src/pipeline_3d.rs @@ -24,7 +24,7 @@ use bevy_render::{ render_phase::{AddRenderCommand, DrawFunctions, SetItemPipeline, SortedRenderPhase}, render_resource::*, texture::BevyDefault, - view::{ExtractedView, Msaa, RenderLayers, ViewTarget}, + view::{ExtractedRenderLayers, ExtractedView, Msaa, ViewTarget}, Render, RenderApp, RenderSet, }; use bevy_utils::tracing::error; @@ -282,7 +282,7 @@ fn queue_line_gizmos_3d( mut views: Query<( &ExtractedView, &mut SortedRenderPhase, - Option<&RenderLayers>, + Option<&ExtractedRenderLayers>, ( Has, Has, @@ -300,8 +300,6 @@ fn queue_line_gizmos_3d( (normal_prepass, depth_prepass, motion_vector_prepass, deferred_prepass), ) in &mut views { - let render_layers = render_layers.copied().unwrap_or_default(); - let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples()) | MeshPipelineKey::from_hdr(view.hdr); @@ -322,7 +320,7 @@ fn queue_line_gizmos_3d( } for (entity, handle, config) in &line_gizmos { - if !config.render_layers.intersects(&render_layers) { + if !config.render_layers.intersects_extracted(render_layers) { continue; } @@ -365,7 +363,7 @@ fn queue_line_joint_gizmos_3d( mut views: Query<( &ExtractedView, &mut SortedRenderPhase, - Option<&RenderLayers>, + Option<&ExtractedRenderLayers>, ( Has, Has, @@ -386,8 +384,6 @@ fn queue_line_joint_gizmos_3d( (normal_prepass, depth_prepass, motion_vector_prepass, deferred_prepass), ) in &mut views { - let render_layers = render_layers.copied().unwrap_or_default(); - let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples()) | MeshPipelineKey::from_hdr(view.hdr); @@ -408,7 +404,7 @@ fn queue_line_joint_gizmos_3d( } for (entity, handle, config) in &line_gizmos { - if !config.render_layers.intersects(&render_layers) { + if !config.render_layers.intersects_extracted(render_layers) { continue; } diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index c87f14c6afe05..dd85da675df67 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -1,7 +1,6 @@ // FIXME(3492): remove once docs are ready #![allow(missing_docs)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] -#![forbid(unsafe_code)] #![doc( html_logo_url = "https://bevyengine.org/assets/icon.png", html_favicon_url = "https://bevyengine.org/assets/icon.png" diff --git a/crates/bevy_pbr/src/light/mod.rs b/crates/bevy_pbr/src/light/mod.rs index c73dcaf525b2d..6a0438d6e7569 100644 --- a/crates/bevy_pbr/src/light/mod.rs +++ b/crates/bevy_pbr/src/light/mod.rs @@ -13,10 +13,14 @@ use bevy_render::{ primitives::{Aabb, CascadesFrusta, CubemapFrusta, Frustum, HalfSpace, Sphere}, render_resource::BufferBindingType, renderer::RenderDevice, - view::{InheritedVisibility, RenderLayers, ViewVisibility, VisibleEntities}, + view::{ + derive_render_layers, derive_render_layers_ptr, extract_camera_layer, CameraLayer, + InheritedRenderLayers, InheritedVisibility, RenderLayers, RenderLayersPtr, RenderLayersRef, + ViewVisibility, VisibleEntities, + }, }; use bevy_transform::components::{GlobalTransform, Transform}; -use bevy_utils::tracing::warn; +use bevy_utils::{tracing::warn, warn_once}; use crate::*; @@ -1003,7 +1007,6 @@ pub(crate) fn directional_light_order( .then_with(|| entity_1.cmp(entity_2)) // stable } -#[derive(Clone, Copy)] // data required for assigning lights to clusters pub(crate) struct PointLightAssignmentData { entity: Entity, @@ -1011,7 +1014,7 @@ pub(crate) struct PointLightAssignmentData { range: f32, shadows_enabled: bool, spot_light_angle: Option, - render_layers: RenderLayers, + render_layers: RenderLayersPtr, } impl PointLightAssignmentData { @@ -1023,6 +1026,15 @@ impl PointLightAssignmentData { } } +// SAFETY: `PointLightAssignmentData` is only used in `assign_lights_to_clusters`, where it is not reused +// between system calls. +#[allow(unsafe_code)] +unsafe impl Send for PointLightAssignmentData {} +// SAFETY: `PointLightAssignmentData` is only used in `assign_lights_to_clusters`, where it is not reused +// between system calls. +#[allow(unsafe_code)] +unsafe impl Sync for PointLightAssignmentData {} + #[derive(Resource, Default)] pub struct GlobalVisiblePointLights { entities: HashSet, @@ -1052,7 +1064,7 @@ pub(crate) fn assign_lights_to_clusters( &Frustum, &ClusterConfig, &mut Clusters, - Option<&RenderLayers>, + Option<&CameraLayer>, Option<&mut VisiblePointLights>, )>, point_lights_query: Query<( @@ -1060,6 +1072,7 @@ pub(crate) fn assign_lights_to_clusters( &GlobalTransform, &PointLight, Option<&RenderLayers>, + Option<&InheritedRenderLayers>, &ViewVisibility, )>, spot_lights_query: Query<( @@ -1067,6 +1080,7 @@ pub(crate) fn assign_lights_to_clusters( &GlobalTransform, &SpotLight, Option<&RenderLayers>, + Option<&InheritedRenderLayers>, &ViewVisibility, )>, mut lights: Local>, @@ -1086,14 +1100,17 @@ pub(crate) fn assign_lights_to_clusters( .iter() .filter(|(.., visibility)| visibility.get()) .map( - |(entity, transform, point_light, maybe_layers, _visibility)| { + |(entity, transform, point_light, maybe_groups, maybe_inherited, _visibility)| { PointLightAssignmentData { entity, transform: GlobalTransform::from_translation(transform.translation()), shadows_enabled: point_light.shadows_enabled, range: point_light.range, spot_light_angle: None, - render_layers: maybe_layers.copied().unwrap_or_default(), + render_layers: derive_render_layers_ptr_for_light( + maybe_inherited, + maybe_groups, + ), } }, ), @@ -1103,14 +1120,17 @@ pub(crate) fn assign_lights_to_clusters( .iter() .filter(|(.., visibility)| visibility.get()) .map( - |(entity, transform, spot_light, maybe_layers, _visibility)| { + |(entity, transform, spot_light, maybe_groups, maybe_inherited, _visibility)| { PointLightAssignmentData { entity, transform: *transform, shadows_enabled: spot_light.shadows_enabled, range: spot_light.range, spot_light_angle: Some(spot_light.outer_angle), - render_layers: maybe_layers.copied().unwrap_or_default(), + render_layers: derive_render_layers_ptr_for_light( + maybe_inherited, + maybe_groups, + ), } }, ), @@ -1180,11 +1200,12 @@ pub(crate) fn assign_lights_to_clusters( frustum, config, clusters, - maybe_layers, + maybe_view, mut visible_lights, ) in &mut views { - let view_layers = maybe_layers.copied().unwrap_or_default(); + let view_layer = extract_camera_layer(maybe_view); + let clusters = clusters.into_inner(); if matches!(config, ClusterConfig::None) { @@ -1406,8 +1427,12 @@ pub(crate) fn assign_lights_to_clusters( let mut update_from_light_intersections = |visible_lights: &mut Vec| { for light in &lights { - // check if the light layers overlap the view layers - if !view_layers.intersects(&light.render_layers) { + // check if the light groups overlap the view groups + // SAFETY: `lights` is cleared at the start of this system call, and is populated from + // immutable queries. + #[allow(unsafe_code)] + let light_renderlayers = unsafe { light.render_layers.get() }; + if !view_layer.intersects(light_renderlayers) { continue; } @@ -1817,6 +1842,7 @@ pub fn update_spot_light_frusta( } } +#[allow(clippy::too_many_arguments)] pub fn check_light_mesh_visibility( visible_point_lights: Query<&VisiblePointLights>, mut point_lights: Query<( @@ -1825,6 +1851,7 @@ pub fn check_light_mesh_visibility( &CubemapFrusta, &mut CubemapVisibleEntities, Option<&RenderLayers>, + Option<&InheritedRenderLayers>, )>, mut spot_lights: Query<( &SpotLight, @@ -1832,6 +1859,7 @@ pub fn check_light_mesh_visibility( &Frustum, &mut VisibleEntities, Option<&RenderLayers>, + Option<&InheritedRenderLayers>, )>, mut directional_lights: Query< ( @@ -1839,6 +1867,7 @@ pub fn check_light_mesh_visibility( &CascadesFrusta, &mut CascadesVisibleEntities, Option<&RenderLayers>, + Option<&InheritedRenderLayers>, &mut ViewVisibility, ), Without, @@ -1849,6 +1878,7 @@ pub fn check_light_mesh_visibility( &InheritedVisibility, &mut ViewVisibility, Option<&RenderLayers>, + Option<&InheritedRenderLayers>, Option<&Aabb>, Option<&GlobalTransform>, ), @@ -1872,8 +1902,14 @@ pub fn check_light_mesh_visibility( } // Directional lights - for (directional_light, frusta, mut visible_entities, maybe_view_mask, light_view_visibility) in - &mut directional_lights + for ( + directional_light, + frusta, + mut visible_entities, + maybe_view_mask, + maybe_vm_inherited, + light_view_visibility, + ) in &mut directional_lights { // Re-use already allocated entries where possible. let mut views_to_remove = Vec::new(); @@ -1903,13 +1939,15 @@ pub fn check_light_mesh_visibility( continue; } - let view_mask = maybe_view_mask.copied().unwrap_or_default(); + // Get render layers for this light. + let view_mask = derive_render_layers_for_light(maybe_vm_inherited, maybe_view_mask); for ( entity, inherited_visibility, mut view_visibility, maybe_entity_mask, + maybe_em_inherited, maybe_aabb, maybe_transform, ) in &mut visible_entity_query @@ -1918,8 +1956,10 @@ pub fn check_light_mesh_visibility( continue; } - let entity_mask = maybe_entity_mask.copied().unwrap_or_default(); - if !view_mask.intersects(&entity_mask) { + // Note: A visible entity may have a camera on it, but in this case we ignore the camera and + // treat it as a normal entity. The CameraLayer component controls what the camera can see, while + // RenderLayers on the camera entity controls who can see the camera entity. + if !view_mask.intersects(&derive_render_layers(maybe_em_inherited, maybe_entity_mask)) { continue; } @@ -1972,6 +2012,7 @@ pub fn check_light_mesh_visibility( cubemap_frusta, mut cubemap_visible_entities, maybe_view_mask, + maybe_vm_inherited, )) = point_lights.get_mut(light_entity) { for visible_entities in cubemap_visible_entities.iter_mut() { @@ -1983,7 +2024,8 @@ pub fn check_light_mesh_visibility( continue; } - let view_mask = maybe_view_mask.copied().unwrap_or_default(); + let view_mask = derive_render_layers_for_light(maybe_vm_inherited, maybe_view_mask); + let light_sphere = Sphere { center: Vec3A::from(transform.translation()), radius: point_light.range, @@ -1994,6 +2036,7 @@ pub fn check_light_mesh_visibility( inherited_visibility, mut view_visibility, maybe_entity_mask, + maybe_em_inherited, maybe_aabb, maybe_transform, ) in &mut visible_entity_query @@ -2002,8 +2045,9 @@ pub fn check_light_mesh_visibility( continue; } - let entity_mask = maybe_entity_mask.copied().unwrap_or_default(); - if !view_mask.intersects(&entity_mask) { + if !view_mask + .intersects(&derive_render_layers(maybe_em_inherited, maybe_entity_mask)) + { continue; } @@ -2038,8 +2082,14 @@ pub fn check_light_mesh_visibility( } // Spot lights - if let Ok((point_light, transform, frustum, mut visible_entities, maybe_view_mask)) = - spot_lights.get_mut(light_entity) + if let Ok(( + point_light, + transform, + frustum, + mut visible_entities, + maybe_view_mask, + maybe_vm_inherited, + )) = spot_lights.get_mut(light_entity) { visible_entities.entities.clear(); @@ -2048,7 +2098,8 @@ pub fn check_light_mesh_visibility( continue; } - let view_mask = maybe_view_mask.copied().unwrap_or_default(); + let view_mask = derive_render_layers_for_light(maybe_vm_inherited, maybe_view_mask); + let light_sphere = Sphere { center: Vec3A::from(transform.translation()), radius: point_light.range, @@ -2059,6 +2110,7 @@ pub fn check_light_mesh_visibility( inherited_visibility, mut view_visibility, maybe_entity_mask, + maybe_em_inherited, maybe_aabb, maybe_transform, ) in &mut visible_entity_query @@ -2067,8 +2119,9 @@ pub fn check_light_mesh_visibility( continue; } - let entity_mask = maybe_entity_mask.copied().unwrap_or_default(); - if !view_mask.intersects(&entity_mask) { + if !view_mask + .intersects(&derive_render_layers(maybe_em_inherited, maybe_entity_mask)) + { continue; } @@ -2096,6 +2149,39 @@ pub fn check_light_mesh_visibility( } } +fn derive_render_layers_ptr_for_light( + maybe_inherited: Option<&InheritedRenderLayers>, + maybe_layers: Option<&RenderLayers>, +) -> RenderLayersPtr { + let render_layers = derive_render_layers_ptr(maybe_inherited, maybe_layers); + // SAFETY: The pointer points to references within the lifetime of this function. + #[allow(unsafe_code)] + let len = unsafe { render_layers.get().len() }; + if len > 1 { + warn_once!( + "light detected with more than one RenderLayer, this may cause unexpected \ + behavior (missing shadows or shadows where they should not exist)" + ); + } + + render_layers +} + +fn derive_render_layers_for_light<'a>( + maybe_inherited: Option<&'a InheritedRenderLayers>, + maybe_layers: Option<&'a RenderLayers>, +) -> RenderLayersRef<'a> { + let render_layers = derive_render_layers(maybe_inherited, maybe_layers); + if render_layers.get().len() > 1 { + warn_once!( + "light detected with more than one RenderLayer, this may cause unexpected \ + behavior (missing shadows or shadows where they should not exist)" + ); + } + + render_layers +} + #[cfg(test)] mod test { use super::*; diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 2dacc9943c57d..78a9fea3c0c7f 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -14,7 +14,10 @@ use bevy_render::{ render_resource::*, renderer::{RenderContext, RenderDevice, RenderQueue}, texture::*, - view::{ExtractedView, RenderLayers, ViewVisibility, VisibleEntities}, + view::{ + extract_render_layers, ExtractedRenderLayers, ExtractedView, InheritedRenderLayers, + RenderLayers, ViewVisibility, VisibleEntities, + }, Extract, }; use bevy_transform::{components::GlobalTransform, prelude::Transform}; @@ -51,7 +54,7 @@ pub struct ExtractedDirectionalLight { pub cascade_shadow_config: CascadeShadowConfig, pub cascades: EntityHashMap>, pub frusta: EntityHashMap>, - pub render_layers: RenderLayers, + pub render_layers: ExtractedRenderLayers, } #[derive(Copy, Clone, ShaderType, Default, Debug)] @@ -164,6 +167,7 @@ pub struct GpuDirectionalCascade { #[derive(Copy, Clone, ShaderType, Default, Debug)] pub struct GpuDirectionalLight { + skip: u32, cascades: [GpuDirectionalCascade; MAX_CASCADES_PER_LIGHT], color: Vec4, dir_to_light: Vec3, @@ -173,7 +177,6 @@ pub struct GpuDirectionalLight { num_cascades: u32, cascades_overlap_proportion: f32, depth_texture_base_index: u32, - render_layers: u32, } // NOTE: These must match the bit flags in bevy_pbr/src/render/mesh_view_types.wgsl! @@ -346,6 +349,7 @@ pub fn extract_lights( &GlobalTransform, &ViewVisibility, Option<&RenderLayers>, + Option<&InheritedRenderLayers>, ), Without, >, @@ -467,7 +471,8 @@ pub fn extract_lights( frusta, transform, view_visibility, - maybe_layers, + maybe_groups, + maybe_inherited, ) in &directional_lights { if !view_visibility.get() { @@ -488,7 +493,7 @@ pub fn extract_lights( cascade_shadow_config: cascade_config.clone(), cascades: cascades.cascades.clone(), frusta: frusta.frusta.clone(), - render_layers: maybe_layers.copied().unwrap_or_default(), + render_layers: extract_render_layers(maybe_inherited, maybe_groups), }, render_visible_entities, )); @@ -684,7 +689,12 @@ pub fn prepare_lights( mut global_light_meta: ResMut, mut light_meta: ResMut, views: Query< - (Entity, &ExtractedView, &ExtractedClusterConfig), + ( + Entity, + &ExtractedView, + &ExtractedClusterConfig, + &ExtractedRenderLayers, + ), With>, >, ambient_light: Res, @@ -904,6 +914,8 @@ pub fn prepare_lights( .len() .min(MAX_CASCADES_PER_LIGHT); gpu_directional_lights[index] = GpuDirectionalLight { + // Set to true later when necessary. + skip: 0u32, // Filled in later. cascades: [GpuDirectionalCascade::default(); MAX_CASCADES_PER_LIGHT], // premultiply color by illuminance @@ -917,7 +929,6 @@ pub fn prepare_lights( num_cascades: num_cascades as u32, cascades_overlap_proportion: light.cascade_shadow_config.overlap_proportion, depth_texture_base_index: num_directional_cascades_enabled as u32, - render_layers: light.render_layers.bits(), }; if index < directional_shadow_enabled_count { num_directional_cascades_enabled += num_cascades; @@ -930,7 +941,7 @@ pub fn prepare_lights( .write_buffer(&render_device, &render_queue); // set up light data for each view - for (entity, extracted_view, clusters) in &views { + for (entity, extracted_view, clusters, extracted_render_layers) in &views { let point_light_depth_texture = texture_cache.get( &render_device, TextureDescriptor { @@ -1130,8 +1141,21 @@ pub fn prepare_lights( for (light_index, &(light_entity, light)) in directional_lights .iter() .enumerate() - .take(directional_shadow_enabled_count) + .take(MAX_DIRECTIONAL_LIGHTS) { + let gpu_light = &mut gpu_lights.directional_lights[light_index]; + + // Check if the light intersects with the view. + if !extracted_render_layers.intersects(&light.render_layers) { + gpu_light.skip = 1u32; + continue; + } + + // Only deal with cascades when shadows are enabled. + if (gpu_light.flags & DirectionalLightFlags::SHADOWS_ENABLED.bits()) == 0u32 { + continue; + } + let cascades = light .cascades .get(&entity) @@ -1149,12 +1173,11 @@ pub fn prepare_lights( .zip(&light.cascade_shadow_config.bounds) .enumerate() { - gpu_lights.directional_lights[light_index].cascades[cascade_index] = - GpuDirectionalCascade { - view_projection: cascade.view_projection, - texel_size: cascade.texel_size, - far_bound: *bound, - }; + gpu_light.cascades[cascade_index] = GpuDirectionalCascade { + view_projection: cascade.view_projection, + texel_size: cascade.texel_size, + far_bound: *bound, + }; let depth_texture_view = directional_light_depth_texture diff --git a/crates/bevy_pbr/src/render/mesh_view_types.wgsl b/crates/bevy_pbr/src/render/mesh_view_types.wgsl index 76e43eed2d541..b46e5edf2da08 100644 --- a/crates/bevy_pbr/src/render/mesh_view_types.wgsl +++ b/crates/bevy_pbr/src/render/mesh_view_types.wgsl @@ -23,6 +23,7 @@ struct DirectionalCascade { } struct DirectionalLight { + skip: u32, cascades: array, color: vec4, direction_to_light: vec3, @@ -33,7 +34,6 @@ struct DirectionalLight { num_cascades: u32, cascades_overlap_proportion: f32, depth_texture_base_index: u32, - render_layers: u32, }; const DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT: u32 = 1u; diff --git a/crates/bevy_pbr/src/render/pbr_functions.wgsl b/crates/bevy_pbr/src/render/pbr_functions.wgsl index 1c602944662c5..f28422c8355aa 100644 --- a/crates/bevy_pbr/src/render/pbr_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_functions.wgsl @@ -277,10 +277,10 @@ fn apply_pbr_lighting( // directional lights (direct) let n_directional_lights = view_bindings::lights.n_directional_lights; for (var i: u32 = 0u; i < n_directional_lights; i = i + 1u) { - // check the directional light render layers intersect the view render layers - // note this is not necessary for point and spot lights, as the relevant lights are filtered in `assign_lights_to_clusters` + // check if this light should be skipped, which occurs if this light does not intersect with the view + // note point and spot lights aren't skippable, as the relevant lights are filtered in `assign_lights_to_clusters` let light = &view_bindings::lights.directional_lights[i]; - if ((*light).render_layers & view_bindings::view.render_layers) == 0u { + if (*light).skip != 0u { continue; } diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index 728a863b703cf..fea5d8700a2a7 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -6,7 +6,10 @@ use crate::{ render_asset::RenderAssets, render_graph::{InternedRenderSubGraph, RenderSubGraph}, render_resource::TextureView, - view::{ColorGrading, ExtractedView, ExtractedWindows, RenderLayers, VisibleEntities}, + view::{ + extract_camera_layer, CameraLayer, ColorGrading, ExtractedView, ExtractedWindows, + VisibleEntities, + }, Extract, }; use bevy_asset::{AssetEvent, AssetId, Assets, Handle}; @@ -811,7 +814,7 @@ pub fn extract_cameras( Option<&ColorGrading>, Option<&Exposure>, Option<&TemporalJitter>, - Option<&RenderLayers>, + Option<&CameraLayer>, Option<&Projection>, )>, >, @@ -828,7 +831,7 @@ pub fn extract_cameras( color_grading, exposure, temporal_jitter, - render_layers, + camera_layer, projection, ) in query.iter() { @@ -888,16 +891,13 @@ pub fn extract_cameras( }, visible_entities.clone(), *frustum, + extract_camera_layer(camera_layer), )); if let Some(temporal_jitter) = temporal_jitter { commands.insert(temporal_jitter.clone()); } - if let Some(render_layers) = render_layers { - commands.insert(*render_layers); - } - if let Some(perspective) = projection { commands.insert(perspective.clone()); } diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs index 0d1958bf7d4de..7a847c0f78b01 100644 --- a/crates/bevy_render/src/view/mod.rs +++ b/crates/bevy_render/src/view/mod.rs @@ -48,6 +48,7 @@ impl Plugin for ViewPlugin { .register_type::() .register_type::() .register_type::() + .register_type::() .register_type::() .register_type::() .register_type::() @@ -177,7 +178,6 @@ pub struct ViewUniform { frustum: [Vec4; 6], color_grading: ColorGrading, mip_bias: f32, - render_layers: u32, } #[derive(Resource, Default)] @@ -375,7 +375,6 @@ pub fn prepare_view_uniforms( Option<&Frustum>, Option<&TemporalJitter>, Option<&MipBias>, - Option<&RenderLayers>, )>, ) { let view_iter = views.iter(); @@ -387,16 +386,7 @@ pub fn prepare_view_uniforms( else { return; }; - for ( - entity, - extracted_camera, - extracted_view, - frustum, - temporal_jitter, - mip_bias, - maybe_layers, - ) in &views - { + for (entity, extracted_camera, extracted_view, frustum, temporal_jitter, mip_bias) in &views { let viewport = extracted_view.viewport.as_vec4(); let unjittered_projection = extracted_view.projection; let mut projection = unjittered_projection; @@ -439,7 +429,6 @@ pub fn prepare_view_uniforms( frustum, color_grading: extracted_view.color_grading, mip_bias: mip_bias.unwrap_or(&MipBias(0.0)).0, - render_layers: maybe_layers.copied().unwrap_or_default().bits(), }), }; diff --git a/crates/bevy_render/src/view/view.wgsl b/crates/bevy_render/src/view/view.wgsl index 237113b713a0d..0fe74860dbe60 100644 --- a/crates/bevy_render/src/view/view.wgsl +++ b/crates/bevy_render/src/view/view.wgsl @@ -22,5 +22,4 @@ struct View { frustum: array, 6>, color_grading: ColorGrading, mip_bias: f32, - render_layers: u32, }; diff --git a/crates/bevy_render/src/view/visibility/mod.rs b/crates/bevy_render/src/view/visibility/mod.rs index d7fd09ba7a353..e0399c4d249ca 100644 --- a/crates/bevy_render/src/view/visibility/mod.rs +++ b/crates/bevy_render/src/view/visibility/mod.rs @@ -1,6 +1,8 @@ +mod propagate_render_layers; mod render_layers; use bevy_derive::Deref; +pub use propagate_render_layers::*; pub use render_layers::*; use bevy_app::{Plugin, PostUpdate}; @@ -199,8 +201,8 @@ pub enum VisibilitySystems { UpdatePerspectiveFrusta, /// Label for the [`update_frusta`] system. UpdateProjectionFrusta, - /// Label for the system propagating the [`InheritedVisibility`] in a - /// [`hierarchy`](bevy_hierarchy). + /// Label for the system propagating [`InheritedVisibility`] and applying [`PropagateRenderLayers`] + /// in a [`hierarchy`](bevy_hierarchy). VisibilityPropagate, /// Label for the [`check_visibility`] system updating [`ViewVisibility`] /// of each entity and the [`VisibleEntities`] of each view. @@ -213,42 +215,48 @@ impl Plugin for VisibilityPlugin { fn build(&self, app: &mut bevy_app::App) { use VisibilitySystems::*; - app.add_systems( - PostUpdate, - ( - calculate_bounds.in_set(CalculateBounds), - update_frusta:: - .in_set(UpdateOrthographicFrusta) - .after(camera_system::) - .after(TransformSystem::TransformPropagate) - // We assume that no camera will have more than one projection component, - // 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_frusta::) - .ambiguous_with(update_frusta::), - update_frusta:: - .in_set(UpdatePerspectiveFrusta) - .after(camera_system::) - .after(TransformSystem::TransformPropagate) - // We assume that no camera will have more than one projection component, - // 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_frusta::), - update_frusta:: - .in_set(UpdateProjectionFrusta) - .after(camera_system::) - .after(TransformSystem::TransformPropagate), - (visibility_propagate_system, reset_view_visibility).in_set(VisibilityPropagate), - check_visibility - .in_set(CheckVisibility) - .after(CalculateBounds) - .after(UpdateOrthographicFrusta) - .after(UpdatePerspectiveFrusta) - .after(UpdateProjectionFrusta) - .after(VisibilityPropagate) - .after(TransformSystem::TransformPropagate), - ), - ); + app.add_plugins(PropagateRenderLayersPlugin) + .configure_sets( + PostUpdate, + PropagateRenderLayersSet.in_set(VisibilityPropagate), + ) + .add_systems( + PostUpdate, + ( + calculate_bounds.in_set(CalculateBounds), + update_frusta:: + .in_set(UpdateOrthographicFrusta) + .after(camera_system::) + .after(TransformSystem::TransformPropagate) + // We assume that no camera will have more than one projection component, + // 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_frusta::) + .ambiguous_with(update_frusta::), + update_frusta:: + .in_set(UpdatePerspectiveFrusta) + .after(camera_system::) + .after(TransformSystem::TransformPropagate) + // We assume that no camera will have more than one projection component, + // 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_frusta::), + update_frusta:: + .in_set(UpdateProjectionFrusta) + .after(camera_system::) + .after(TransformSystem::TransformPropagate), + (visibility_propagate_system, reset_view_visibility) + .in_set(VisibilityPropagate), + check_visibility + .in_set(CheckVisibility) + .after(CalculateBounds) + .after(UpdateOrthographicFrusta) + .after(UpdatePerspectiveFrusta) + .after(UpdateProjectionFrusta) + .after(VisibilityPropagate) + .after(TransformSystem::TransformPropagate), + ), + ); } } @@ -375,7 +383,7 @@ pub fn check_visibility( mut view_query: Query<( &mut VisibleEntities, &Frustum, - Option<&RenderLayers>, + Option<&CameraLayer>, &Camera, )>, mut visible_aabb_query: Query<( @@ -383,18 +391,20 @@ pub fn check_visibility( &InheritedVisibility, &mut ViewVisibility, Option<&RenderLayers>, + Option<&InheritedRenderLayers>, Option<&Aabb>, &GlobalTransform, Has, )>, deterministic_rendering_config: Res, ) { - for (mut visible_entities, frustum, maybe_view_mask, camera) in &mut view_query { + for (mut visible_entities, frustum, maybe_camera_view, camera) in &mut view_query { if !camera.is_active { continue; } - let view_mask = maybe_view_mask.copied().unwrap_or_default(); + let default_camera_view = CameraLayer::default(); + let camera_view = maybe_camera_view.unwrap_or(&default_camera_view); visible_entities.entities.clear(); visible_aabb_query.par_iter_mut().for_each(|query_item| { @@ -402,7 +412,8 @@ pub fn check_visibility( entity, inherited_visibility, mut view_visibility, - maybe_entity_mask, + maybe_layers, + maybe_inherited, maybe_model_aabb, transform, no_frustum_culling, @@ -414,8 +425,9 @@ pub fn check_visibility( return; } - let entity_mask = maybe_entity_mask.copied().unwrap_or_default(); - if !view_mask.intersects(&entity_mask) { + // Check if this camera can see the entity. + if !camera_view.entity_is_visible(&derive_render_layers(maybe_inherited, maybe_layers)) + { return; } diff --git a/crates/bevy_render/src/view/visibility/propagate_render_layers.rs b/crates/bevy_render/src/view/visibility/propagate_render_layers.rs new file mode 100644 index 0000000000000..671020e0071a0 --- /dev/null +++ b/crates/bevy_render/src/view/visibility/propagate_render_layers.rs @@ -0,0 +1,1217 @@ +/* +# Notes on propagation algorithm for PropagateRenderLayers + +The propagation algorithm is in control of updating InheritedRenderLayers on the descendents of +entities with PropagateRenderLayers. It must take into account changes in propagated values, +changes in RenderLayers on descendents, the possibility of entities gaining/losing PropagateRenderLayers, +and all potential hierarchy mutations (which can occur simultaneously with the other factors). + +At the same time, the algorithm must be efficient. It should not update entities more than once (unless +necessary), and it should not update entities that don't need to be updated. + +These goals are achieved with a sequence of update-handling steps that are logically accumulative. As much +as possible, each step covers only update-reasons not covered by previous steps. Note that most steps include +recursion for passing updates down hierarchies, however each step has slightly different propagation rules as +described below. + + +## Contributing factors + +Dimensions: +- InheritedRenderLayers and PropagateRenderLayers should not exist on the same entity +- RenderLayers can be added/changed/removed +- CameraLayer can be added/changed/removed +- PropagateRenderLayers can be added/changed/removed +- Camera can be added/removed (changes don't matter) +- Entities can gain a parent with PropagateRenderLayers +- Entities can gain a parent without PropagateRenderLayers + - The entity may or may not have InheritedRenderLayers already + - The parent may or may not have InheritedRenderLayers already +- Entities can become un-parented + +Actions: +- This algorithm can: add/remove/change InheritedRenderLayers + - When updating an entity, we always compute a fresh InheritedRenderLayers::computed value. + + +## Propagation algorithm + +These steps must be done in order, and they are computed 'accumulatively', which means once +an entity has been updated it won't be updated again (unless explicitly specified). + +We take note of a couple 'problems' that are challenging edge cases, and how they are handled. + +### Propagator Repair + +- If an entity has both InheritedRenderLayers and PropagateRenderLayers, then remove its InheritedRenderLayers. + - SYSTEM: clean_propagators +- If a propagator has an updated or new propagate-value, then propagate it. Stop propagating only if another propagator +is encountered. + - SYSTEM: propagate_updated_propagators + - Possible change sources: RenderLayers (add/remove/change), CameraLayer (add/remove/change), Camera (add/remove), + PropagateRenderLayers (add/change). +- If a propagator gains non-propagator children, then propagate it to the new children. Stop propagating if the +propagator entity is already known to a descendent (do not mark those descendents as updated), or if another +propagator is encountered. + - SYSTEM: propagate_to_new_children + - PROBLEM: If a new entity is inserted between a propagator and another entity, then the propagator will run in + this step, and the new entity's non-propagator children will run in "If a non-propagator entity is parented to a + non-propagator". This step will insert an InheritedRenderLayers component to the new entity, but it *won't* be + added to the pre-existing entity which already records the propagator. When the pre-eisting entity gets updated + because of being parented to a non-propagator, the parent's InheritedRenderLayers component won't be available + since its insertion is deferred. + - SOLUTION: See "If a non-propagator entity is parented to a non-propagator". + +### Non-Propagator Hierarchy Repair + +- If a non-propagator entity with InheritedRenderLayers is un-parented, then remove InheritedRenderLayers from the +entity and its children (stopping at propagators). + - SYSTEM: handle_orphaned_nonpropagators + - Iterate all children even if one without InheritedRenderLayers is encountered. This ensures 'gaps' caused + by hierarchy changes won't cause problems. For example, an entity without InheritedRenderLayers parented to a + descendent of an un-parented entity will want to pull inheritance from its parent, but removal of + InheritedRenderLayers is deferred so the component it would access would be stale. +- If an entity loses a PropagateRenderLayers component, then inherit its parent's propagator entity, and propagate +that to its own children (stopping when a propagator is encountered or if an entity is non-updated and has an +InheritedRenderLayers that matches the propagator). If the parent isn't a propagator and doesn't +have InheritedRenderLayers (or there is no parent), then remove InheritedRenderLayers from the entity and its +children (stopping at propagators and non-updated descendents without InheritedRenderLayers). Skip already-updated +entities that lost PropagateRenderLayers. + - SYSTEM: handle_lost_propagator + - The goal of this step is to update the span of entities starting at the entity that lost a + PropagateRenderLayers component, and ending at entities that aren't potentially-invalid. + - If the parent is marked updated (but the entity is not marked updated), then this entity's propagator + stays the same (see "If a propagator gains non-propagator children") (this can only happen if InheritedRenderLayers + was manually inserted by a user, since the entity would not have had InheritedRenderLayers in the previous tick + because we force-remove it if an entity has PropagateRenderLayers). In that case, instead of using the parent's + InheritedRenderLayers, recompute this entity's InheritedRenderLayers from its existing propagator (we assume it + is invalid/stale since the entity used to be a propagator). + - PROBLEM: What if multiple entities in a hierarchy lose PropagateRenderLayers components? + - SOLUTION: Ignore the updated_entities cache and force-update children even if they were previously updated. + This will cause some redundant work, but gives the correct result. + - If a child is marked as updated, then always insert/remove InheritedRenderLayers components to match + the desired policy (regardless of it the entity has InheritedRenderLayers), in case previous deferred + updates of this type need to be overwritten. +- If a non-propagator entity is parented to a non-propagator, then propagate the parent's InheritedRenderLayers +propagator entity (stopping at propagators and descendents that share the parent's propagator). If the parent doesn't +have InheritedRenderLayers, then remove InheritedRenderLayers from the entity and its children (stopping at propagators +and children that don't have InheritedRenderLayers and aren't updated). Skip already-updated entities that were parented +to a non-propagator. + - SYSTEMS: handle_new_children_nonpropagator, handle_new_parent_nonpropagator + - The goal of this step is to update the span of entities starting at the entity that was reparented, and ending + at entities that aren't potentially-invalid. + - If the parent is marked updated (but the entity is not marked updated), then this entity's propagator + stays the same (see "If a propagator gains non-propagator children"). In that case, do not mark the entity updated + and simply skip it. + - Force-update children for the same reason as "If an entity loses a PropagateRenderLayers component". + - The implementation does not iterate non-propagator entities without InheritedRenderLayers that were parented + to entities without InheritedRenderLayers. Issues that can arise from that case, such as other hierarchy moves + below or above an entity, will be handled correctly by this and previous steps. + - Note that the challenging hierarchy reasoning used here is necessary to allow efficient queries. A careless + solution would require iterating all entities with Parent or Children changes, and traversing the hierarchy many + times redundantly. + +### Non-Propagator Targeted Repair + +- If a non-propagator entity with InheritedRenderLayers has an added/removed/changed RenderLayers, then recompute +its InheritedRenderLayers::computed field. Skip already-updated entities. + - SYSTEM: handle_modified_renderlayers + + +## Performance + +The base-line performance cost of this algorithm comes from detecting changes, which requires iterating queries. +- All entities with `Children` and `PropagateRenderLayers` are iterated twice (can potentially be reduced to once). +- All entities with `Children` and `InheritedRenderLayers` are iterated once. +- All entities with `Parent` and `InheritedRenderLayers` are iterated once. +- All entities with `RenderLayers` and `InheritedRenderLayers` are iterated once. +- `RemovedComponents` is iterated twice. +- `RemovedComponents` is iterated once. +- `RemovedComponents` is iterated once. +- `RemovedComponents` is iterated once. +*/ + +#![allow(clippy::manual_map, clippy::collapsible_match)] + +use crate::view::*; + +use crate::prelude::Camera; +use bevy_app::PostUpdate; +use bevy_derive::Deref; +use bevy_ecs::entity::EntityHashSet; +use bevy_ecs::prelude::*; +use bevy_hierarchy::{Children, Parent}; +use bevy_utils::error_once; +use bevy_utils::tracing::warn; + +/// System set that applies [`PropagateRenderLayers`] by updating [`InheritedRenderLayers`] components on +/// entities. +/// +/// Runs in [`PostUpdate`] in the [`VisibilityPropagate`](VisibilitySystems::VisibilityPropagate) set. +#[derive(SystemSet, Debug, Clone, Hash, Eq, PartialEq)] +pub struct PropagateRenderLayersSet; + +pub(crate) struct PropagateRenderLayersPlugin; + +impl Plugin for PropagateRenderLayersPlugin { + fn build(&self, app: &mut App) { + app.init_resource::() + .add_systems( + PostUpdate, + ( + clean_propagators, + propagate_updated_propagators, + propagate_to_new_children, + handle_orphaned_nonpropagators, + handle_lost_propagator, + handle_new_children_nonpropagator, + handle_new_parent_nonpropagator, + apply_deferred, + handle_modified_renderlayers, //does not have deferred commands + ) + .chain() + .in_set(PropagateRenderLayersSet), + ); + } +} + +/// Component on an entity that causes it to propagate a [`RenderLayers`] value to its children. +/// +/// Entities with this component will ignore [`RenderLayers`] propagated by parents. +/// +/// See [`RenderLayers`] and [`CameraLayer`]. +#[derive(Component)] +pub enum PropagateRenderLayers { + /// If the entity has a [`RenderLayers`] component, that value is propagated, otherwise a default + /// [`RenderLayers`] is propagated. + /// + /// Note that it is allowed to add a [`RenderLayers`] component to a camera for propagation. + Auto, + /// If the entity has a [`Camera`] component and a [`CameraLayer`] component, propagates + /// [`CameraLayer::get_layers`]. Uses [`CameraLayer::default`] if there is no [`CameraLayer`]. + /// + /// If there is no [`Camera`] component, a warning will be logged and an empty [`RenderLayers`] will be propagated. + Camera, + /// Propagates a custom [`RenderLayers`]. + Custom(RenderLayers), +} + +impl PropagateRenderLayers { + pub fn get_render_layers<'a>( + &'a self, + saved: &mut RenderLayers, + layers: Option<&'a RenderLayers>, + view: Option<&CameraLayer>, + is_camera: bool, + ) -> RenderLayersRef<'a> { + match self { + Self::Auto => { + let Some(layers) = layers else { + return RenderLayersRef::Val(RenderLayers::default()); + }; + RenderLayersRef::Ref(layers) + } + Self::Camera => { + if !is_camera { + warn!("failed propagating PropagateRenderLayers::Camera, entity doesn't have a camera"); + return RenderLayersRef::Val(RenderLayers::empty()); + }; + let default_camera_layer = CameraLayer::default(); + let view = view.unwrap_or(&default_camera_layer); + + // Reuse saved allocation. + saved.clear(); + if let Some(layer) = view.layer() { + saved.add(layer); + } + let mut temp = RenderLayers::empty(); + std::mem::swap(&mut temp, saved); + RenderLayersRef::Val(temp) + } + Self::Custom(layers) => RenderLayersRef::Ref(layers), + } + } +} + +/// Component on an entity that stores the result of merging the entity's [`RenderLayers`] +/// component with the [`RenderLayers`] of an entity propagated by the entity's parent. +/// +/// See [`PropagateRenderLayers`]. +/// +/// This is updated in [`PostUpdate`] in the [`PropagateRenderLayersSet`]. +/// The component will be automatically added or removed depending on if it is needed. +/// +/// ### Merge details +/// +/// The merge direction is `entity_renderLayers.merge(propagated_renderLayers)` +/// (see [`RenderLayers::merge`]). +/// This means the entity's affiliated camera will be prioritized over the propagated affiliated camera. +#[derive(Component, Debug, Clone)] +pub struct InheritedRenderLayers { + /// The entity that propagated a [`RenderLayers`] to this entity. + /// + /// This is cached so children of this entity can update themselves without needing to traverse the + /// entire hierarchy. + pub propagator: Entity, + /// The [`RenderLayers`] computed by merging the [`RenderLayers`] of the `Self::propagator` entity into + /// the node's [`RenderLayers`] component. + /// + /// This is cached for efficient access in the [`check_visibility`] system. + pub computed: RenderLayers, +} + +impl InheritedRenderLayers { + /// Makes an empty `InheritedRenderLayers`. + pub fn empty() -> Self { + Self { + propagator: Entity::PLACEHOLDER, + computed: RenderLayers::empty(), + } + } +} + +/// Contains the final [`RenderLayers`] of an entity for extraction to the render world. +#[derive(Component, Debug, Deref)] +pub struct ExtractedRenderLayers(RenderLayers); + +/// Evaluates an entity's possible `RenderLayers` and `InheritedRenderLayers` components to get a +/// final [`ExtractedRenderLayers`] for the render world. +/// +/// Potentially allocates if [`InheritedRenderLayers`] or [`RenderLayers`] is allocated. +pub fn extract_render_layers( + inherited: Option<&InheritedRenderLayers>, + render_layers: Option<&RenderLayers>, +) -> ExtractedRenderLayers { + ExtractedRenderLayers( + inherited + .map(|i| &i.computed) + .or(render_layers) + .cloned() + .unwrap_or(RenderLayers::default()), + ) +} + +/// Evaluates a camera's possible `CameraLayer` component to get a +/// final [`ExtractedRenderLayers`] for the render world. +/// +/// Potentially allocates if [`InheritedRenderLayers`] or [`CameraLayer`] is allocated. +pub fn extract_camera_layer(camera_layer: Option<&CameraLayer>) -> ExtractedRenderLayers { + ExtractedRenderLayers( + camera_layer + .map(|i| i.get_layers()) + .unwrap_or(RenderLayers::default()), + ) +} + +/// Derives a [`RenderLayersRef`] from an optional [`InheritedRenderLayers`] and [`RenderLayers`]. +/// +/// Returns in order of priority: +/// - [`InheritedRenderLayers::computed`] +/// - [`RenderLayers`] +/// - [`RenderLayers::default`] +pub fn derive_render_layers<'a>( + inherited: Option<&'a InheritedRenderLayers>, + render_layers: Option<&'a RenderLayers>, +) -> RenderLayersRef<'a> { + if let Some(inherited) = inherited { + RenderLayersRef::Ref(&inherited.computed) + } else if let Some(render_layers) = render_layers { + RenderLayersRef::Ref(render_layers) + } else { + RenderLayersRef::Val(RenderLayers::default()) + } +} + +/// Derives a [`RenderLayersPtr`] from an optional [`InheritedRenderLayers`] and [`RenderLayers`]. +/// +/// See [`derive_render_layers`]. +pub fn derive_render_layers_ptr( + inherited: Option<&InheritedRenderLayers>, + render_layers: Option<&RenderLayers>, +) -> RenderLayersPtr { + if let Some(inherited) = inherited { + RenderLayersPtr::Ptr(&inherited.computed) + } else if let Some(render_layers) = render_layers { + RenderLayersPtr::Ptr(render_layers) + } else { + RenderLayersPtr::Val(RenderLayers::default()) + } +} + +#[derive(Resource, Default)] +struct PropagateRenderLayersEntityCache { + /// Buffered to absorb spurious allocations of propagated values during traversals. + saved: RenderLayers, + /// Updated entities. + /// + /// This is cleared at the start of [`PropagateRenderLayersSet`]. + entities: EntityHashSet, +} + +impl PropagateRenderLayersEntityCache { + fn insert(&mut self, entity: Entity) { + self.entities.insert(entity); + } + + fn contains(&self, entity: Entity) -> bool { + self.entities.contains(&entity) + } + + fn clear(&mut self) { + self.entities.clear(); + } + + fn saved(&mut self) -> &mut RenderLayers { + &mut self.saved + } +} + +/// Removes `InheritedRenderLayers` from entities with `PropagateRenderLayers`. +fn clean_propagators( + mut commands: Commands, + dirty_propagators: Query, With)>, +) { + for dirty in dirty_propagators.iter() { + if let Some(mut entity) = commands.get_entity(dirty) { + entity.remove::(); + } + } +} + +/// Propagates propagation values that have changed. +//todo: Detect if the propagated value has actually changed? Hard to expect this would matter in practice. +#[allow(clippy::too_many_arguments)] +fn propagate_updated_propagators( + mut commands: Commands, + mut updated_entities: ResMut, + // Detect added/changed: RenderLayers, CameraLayer, Camera (added only), PropagateRenderLayers. + changed_propagators: Query< + ( + Entity, + &Children, + Option<&RenderLayers>, + Option<&CameraLayer>, + Has, + &PropagateRenderLayers, + ), + Or<( + Changed, + Changed, + Added, + Changed, + )>, + >, + // Detect removed: RenderLayers, CameraLayer, Camera. + mut removed_renderlayers: RemovedComponents, + mut removed_cameralayer: RemovedComponents, + mut removed_camera: RemovedComponents, + all_propagators: Query<( + Entity, + &Children, + Option<&RenderLayers>, + Option<&CameraLayer>, + Has, + &PropagateRenderLayers, + )>, + // Query for getting Children. + children_query: Query<&Children>, + // Query for updating InheritedRenderLayers on non-propagator entities. + mut maybe_inherited: Query< + (Option<&RenderLayers>, Option<&mut InheritedRenderLayers>), + Without, + >, +) { + // Track updated entities to prevent redundant updates, as `Commands` changes are deferred. + // + // Using this ensures that once mutations to entities with PropagateRenderLayers have been + // propagated, all entities affected by those changes won't be mutated again. This makes it + // safe to read parent InheritedRenderLayers (in the other cases that need to be handled) in + // order to 'pull in' inherited changes when needed. See algorithm description for more details. + updated_entities.clear(); + + // Collect aggregate iterator for all propagators that need to propagate. + let propagators = changed_propagators.iter().chain( + // IMPORTANT: Removals should be ordered first if propagate_to_new_children is merged + // into changed_propagators. + removed_renderlayers + .read() + .chain(removed_cameralayer.read()) + .chain(removed_camera.read()) + .filter_map(|e| all_propagators.get(e).ok()), + ); + + // Propagate each propagator. + for (propagator, children, maybe_render_layers, maybe_camera_layer, maybe_camera, propagate) in + propagators + { + // There can be duplicates due to component removals. + if updated_entities.contains(propagator) { + continue; + } + + // Get value to propagate. + let mut propagated = propagate.get_render_layers( + updated_entities.saved(), + maybe_render_layers, + maybe_camera_layer, + maybe_camera, + ); + + // Propagate + updated_entities.insert(propagator); + + for child in children.iter().copied() { + apply_full_propagation( + &mut commands, + &mut updated_entities, + &children_query, + &mut maybe_inherited, + propagator, + &propagated, + child, + ); + } + + // Reclaim memory + propagated.reclaim(updated_entities.saved()); + } +} + +/// Applies propagation to entities for `apply_full_propagation`. +// Note: This does not require checking updated_entities because all children will be fresh. +fn apply_full_propagation( + commands: &mut Commands, + updated_entities: &mut PropagateRenderLayersEntityCache, + children_query: &Query<&Children>, + maybe_inherited: &mut Query< + (Option<&RenderLayers>, Option<&mut InheritedRenderLayers>), + Without, + >, + propagator: Entity, + propagated: &RenderLayers, + entity: Entity, +) { + // Leave if entity doesn't exist or has PropagateRenderLayers. + let Ok((maybe_render_layers, maybe_inherited_layers)) = maybe_inherited.get_mut(entity) else { + return; + }; + + // Update inherited value or insert a new one. + let empty_render_layers = RenderLayers::empty(); + let initial_layers = maybe_render_layers.unwrap_or(&empty_render_layers); + let apply_changes = |layers: &mut InheritedRenderLayers| { + layers.propagator = propagator; + layers.computed.set_from(initial_layers); + layers.computed.merge(propagated); + }; + + if let Some(mut inherited) = maybe_inherited_layers { + apply_changes(&mut inherited); + } else { + let mut new = InheritedRenderLayers::empty(); + apply_changes(&mut new); + commands.entity(entity).insert(new); + } + + // Mark as updated. + updated_entities.insert(entity); + + // Continue propagation to children. + let Ok(children) = children_query.get(entity) else { + return; + }; + for child in children.iter().copied() { + apply_full_propagation( + commands, + updated_entities, + children_query, + maybe_inherited, + propagator, + propagated, + child, + ); + } +} + +/// Propagates propagation values to new children of a propagator. +//todo: Can be merged with apply_full_propagation at the cost of code density. Must make sure iterating +// this comes *after* iterating removals, because this step must come *after* all propagation value changes +// are handled. +fn propagate_to_new_children( + mut commands: Commands, + mut updated_entities: ResMut, + // Changed children. + changed_children: Query< + ( + Entity, + &Children, + Option<&RenderLayers>, + Option<&CameraLayer>, + Has, + &PropagateRenderLayers, + ), + Changed, + >, + // Query for getting Children. + children_query: Query<&Children>, + // Query for updating InheritedRenderLayers on non-propagator entities. + mut maybe_inherited: Query< + (Option<&RenderLayers>, Option<&mut InheritedRenderLayers>), + Without, + >, +) { + // Propagate each propagator that has new children. + for (propagator, children, maybe_render_layers, maybe_camera_layer, maybe_camera, propagate) in + changed_children.iter() + { + // The propagator could have been updated in a previous step. + if updated_entities.contains(propagator) { + continue; + } + + // Get value to propagate. + let mut propagated = propagate.get_render_layers( + updated_entities.saved(), + maybe_render_layers, + maybe_camera_layer, + maybe_camera, + ); + + // Propagate + updated_entities.insert(propagator); + + for child in children.iter().copied() { + apply_new_children_propagation( + &mut commands, + &mut updated_entities, + &children_query, + &mut maybe_inherited, + propagator, + &propagated, + child, + ); + } + + // Reclaim memory + propagated.reclaim(updated_entities.saved()); + } +} + +/// Applies propagation to entities for `propagate_to_new_children`. +// Note: This does not require checking updated_entities because all children will be fresh. +fn apply_new_children_propagation( + commands: &mut Commands, + updated_entities: &mut PropagateRenderLayersEntityCache, + children_query: &Query<&Children>, + maybe_inherited: &mut Query< + (Option<&RenderLayers>, Option<&mut InheritedRenderLayers>), + Without, + >, + propagator: Entity, + propagated: &RenderLayers, + entity: Entity, +) { + // Leave if entity doesn't exist or has PropagateRenderLayers. + let Ok((maybe_render_layers, maybe_inherited_layers)) = maybe_inherited.get_mut(entity) else { + return; + }; + + // Leave if the propagator is already known (implying this is a pre-existing child). + if maybe_inherited_layers + .as_ref() + .map(|i| i.propagator == propagator) + .unwrap_or(false) + { + return; + } + + // Update inherited value or insert a new one. + let empty_render_layers = RenderLayers::empty(); + let initial_layers = maybe_render_layers.unwrap_or(&empty_render_layers); + let apply_changes = |layers: &mut InheritedRenderLayers| { + layers.propagator = propagator; + layers.computed.set_from(initial_layers); + layers.computed.merge(propagated); + }; + + if let Some(mut inherited) = maybe_inherited_layers { + apply_changes(&mut inherited); + } else { + let mut new = InheritedRenderLayers::empty(); + apply_changes(&mut new); + commands.entity(entity).insert(new); + } + + // Mark as updated. + updated_entities.insert(entity); + + // Continue propagation to children. + let Ok(children) = children_query.get(entity) else { + return; + }; + for child in children.iter().copied() { + apply_new_children_propagation( + commands, + updated_entities, + children_query, + maybe_inherited, + propagator, + propagated, + child, + ); + } +} + +/// Removes `InheritedRenderLayers` from orphaned branches of the hierarchy. +fn handle_orphaned_nonpropagators( + mut commands: Commands, + mut updated_entities: ResMut, + // Orphaned non-propagator entities that previously had InheritedRenderLayers. + mut removed_parents: RemovedComponents, + orphaned: Query< + (Entity, Option<&Children>), + ( + Without, + With, + Without, + ), + >, + // Query for getting non-propagator children. + nonpropagators: Query, Without>, +) { + for (orphan, maybe_children) in removed_parents.read().filter_map(|r| orphaned.get(r).ok()) { + apply_orphan_cleanup( + &mut commands, + &mut updated_entities, + &nonpropagators, + orphan, + maybe_children, + ); + } +} + +/// Applies propagation for `handle_orphaned_nonpropagators`. +fn apply_orphan_cleanup( + commands: &mut Commands, + updated_entities: &mut PropagateRenderLayersEntityCache, + nonpropagators: &Query, Without>, + entity: Entity, + maybe_children: Option<&Children>, +) { + // Remove InheritedRenderLayers. + if let Some(mut entity) = commands.get_entity(entity) { + entity.remove::(); + } + + // Mark as updated. + updated_entities.insert(entity); + + // Update non-propagator children. + let Some(children) = maybe_children else { + return; + }; + for child in children.iter().copied() { + // Ignore children that have PropagateRenderLayers. + let Ok(maybe_children) = nonpropagators.get(child) else { + continue; + }; + + apply_orphan_cleanup( + commands, + updated_entities, + nonpropagators, + child, + maybe_children, + ); + } +} + +/// Handles entities that lost the `PropagateRenderLayers` component. +fn handle_lost_propagator( + mut commands: Commands, + mut updated_entities: ResMut, + // Entities that lost PropagateRenderLayers + mut removed_propagate: RemovedComponents, + unpropagated: Query<(Entity, Option<&Parent>), Without>, + // Query for accessing propagators + all_propagators: Query<( + Entity, + Option<&RenderLayers>, + Option<&CameraLayer>, + Has, + &PropagateRenderLayers, + )>, + // Query for getting Children. + children_query: Query<&Children>, + // Query for updating InheritedRenderLayers on non-propagator entities. + mut maybe_inherited: Query< + (Option<&RenderLayers>, Option<&mut InheritedRenderLayers>), + Without, + >, +) { + for (entity, maybe_parent) in removed_propagate + .read() + .filter_map(|r| unpropagated.get(r).ok()) + { + // Skip already-updated entities. + if updated_entities.contains(entity) { + continue; + } + + // Apply propagation. + + // Case 1: no parent + // - Policy: remove all + + // Case 2: parent is a non-propagator without InheritedRenderLayers (not marked updated) + // - Policy: remove all + + // Case 3: parent is a non-propagator with InheritedRenderLayers (not marked updated) + // - Subcase 1: Parent's propagator doesn't exit + // - Policy: remove all (note: this is not an error, because the propagation step to hendle it may not + // have executed yet) + // - Subcase 2: Parent's propagator exists + // - Policy: Compute propagation value from parent's propagator + + // Case 4: parent is a non-propagator marked updated + // - Subcase 1: Self doesn't have InheritedRenderLayers + // - Policy: remove all + // - Subcase 2: Propagator stored in self's InheritedRenderLayers doesn't exist + // - Policy: remove all + // - Subcase 3: Recalculate InheritedRenderLayers with self-stored propagator + // - Policy: propagate value derived from propagator + + // Case 5: parent is a propagator + // - Policy: propagate value derived from propagator + + // Case 6: parent is missing + // - (this is a hierarchy error but we don't check it) + // - Policy: remove all + + // Check cases where a value should be propagated. + let propagator = if let Some(parent) = maybe_parent { + if let Ok((propagator, ..)) = all_propagators.get(**parent) { + // Case 5 + Some(propagator) + } else if updated_entities.contains(**parent) { + // Parent was marked updated (but self was not) + if let Ok((_, maybe_inherited)) = maybe_inherited.get(entity) { + if let Some(inherited) = maybe_inherited { + // Case 4-2, 4-3 + Some(inherited.propagator) + } else { + // Case 4-1 + None + } + } else { + // We already know entity is a non-propagator and exists + unreachable!(); + } + } else { + // Parent was not marked updated + if let Ok((_, maybe_inherited)) = maybe_inherited.get(**parent) { + if let Some(inherited) = maybe_inherited { + // Case 3-1, 3-2 + Some(inherited.propagator) + } else { + // Case 2 + None + } + } else { + // Case 6 + None + } + } + } else { + // Case 1 + None + }; + + // Propagate if possible + // - Case 3-2, 4-3 + // - Cases 3-1, 4-2 are filtered out here. + if let Some(( + propagator, + maybe_render_layers, + maybe_camera_layer, + maybe_camera, + propagate, + )) = propagator.and_then(|p| all_propagators.get(p).ok()) + { + // Propagation value + let mut propagated = propagate.get_render_layers( + updated_entities.saved(), + maybe_render_layers, + maybe_camera_layer, + maybe_camera, + ); + + // Pre-update the entity as a hack for case 4-3. If we don't do this then + // the entity will be caught in "Leave if entity is non-updated and inherits a matching propagator." + // - Note: Case 4-3 is compensating for users manually inserting InheritedRenderLayers + // components, so this could be simplified if that's deemed overkill (we don't fully compensate for + // manual messing with InheritedRenderLayers anyway, so there is no real reliability for doing so). + updated_entities.insert(entity); + + apply_full_propagation_force_update( + &mut commands, + &mut updated_entities, + &children_query, + &mut maybe_inherited, + propagator, + &propagated, + entity, + ); + + // Reclaim memory + propagated.reclaim(updated_entities.saved()); + // In all other cases, remove all InheritedRenderLayers. + } else { + apply_full_propagation_force_remove( + &mut commands, + &mut updated_entities, + &children_query, + &maybe_inherited, + entity, + ); + } + } +} + +/// Applies propagation to entities for `handle_lost_propagator` and `handle_new_children_nonpropagator`. +fn apply_full_propagation_force_update( + commands: &mut Commands, + updated_entities: &mut PropagateRenderLayersEntityCache, + children_query: &Query<&Children>, + maybe_inherited: &mut Query< + (Option<&RenderLayers>, Option<&mut InheritedRenderLayers>), + Without, + >, + propagator: Entity, + propagated: &RenderLayers, + entity: Entity, +) { + // Leave if entity doesn't exist or has PropagateRenderLayers. + let Ok((maybe_render_layers, maybe_inherited_layers)) = maybe_inherited.get_mut(entity) else { + return; + }; + + // Leave if entity is non-updated and inherits a matching propagator. + if let Some(inherited) = maybe_inherited_layers.as_ref() { + if (inherited.propagator == propagator) && !updated_entities.contains(entity) { + return; + } + } + + // Force-update + let empty_render_layers = RenderLayers::empty(); + let initial_layers = maybe_render_layers.unwrap_or(&empty_render_layers); + + let mut new = InheritedRenderLayers::empty(); + if let Some(mut inherited) = maybe_inherited_layers { + // Steal existing allocation if useful. + if inherited.computed.is_allocated() { + std::mem::swap(&mut inherited.computed, &mut new.computed); + } + } + + new.propagator = propagator; + new.computed.set_from(initial_layers); + new.computed.merge(propagated); + commands.entity(entity).insert(new); + + // Mark as updated. + updated_entities.insert(entity); + + // Continue propagation to children. + let Ok(children) = children_query.get(entity) else { + return; + }; + for child in children.iter().copied() { + apply_full_propagation_force_update( + commands, + updated_entities, + children_query, + maybe_inherited, + propagator, + propagated, + child, + ); + } +} + +/// Applies `InheritedRenderLayers` removal to entities for `handle_lost_propagator`, +/// `handle_new_children_nonpropagator`, and `handle_new_parent_nonpropagator`. +fn apply_full_propagation_force_remove( + commands: &mut Commands, + updated_entities: &mut PropagateRenderLayersEntityCache, + children_query: &Query<&Children>, + maybe_inherited: &Query< + (Option<&RenderLayers>, Option<&mut InheritedRenderLayers>), + Without, + >, + entity: Entity, +) { + // Leave if entity doesn't exist or has PropagateRenderLayers. + let Ok((_, maybe_inherited_inner)) = maybe_inherited.get(entity) else { + return; + }; + + // Leave if entity is non-updated and doesn't have InheritedRenderLayers. + if maybe_inherited_inner.is_none() && !updated_entities.contains(entity) { + return; + } + + // Force-remove InheritedRenderLayers + commands.entity(entity).remove::(); + + // Mark as updated. + updated_entities.insert(entity); + + // Continue propagation to children. + let Ok(children) = children_query.get(entity) else { + return; + }; + for child in children.iter().copied() { + apply_full_propagation_force_remove( + commands, + updated_entities, + children_query, + maybe_inherited, + child, + ); + } +} + +/// Handles non-propagator entities with `InheritedRenderLayers` whose children changed. +fn handle_new_children_nonpropagator( + mut commands: Commands, + mut updated_entities: ResMut, + // Entities with InheritedRenderLayers that changed children + inherited_with_children: Query< + (Entity, &Children), + ( + Changed, + With, + Without, + ), + >, + // Query for accessing propagators + all_propagators: Query<( + Entity, + Option<&RenderLayers>, + Option<&CameraLayer>, + Has, + &PropagateRenderLayers, + )>, + // Query for getting Children. + children_query: Query<&Children>, + // Query for updating InheritedRenderLayers on non-propagator entities. + mut maybe_inherited: Query< + (Option<&RenderLayers>, Option<&mut InheritedRenderLayers>), + Without, + >, +) { + for (entity, children) in inherited_with_children.iter() { + // Skip entity if already updated, which implies children are already in an accurate state. + if updated_entities.contains(entity) { + continue; + } + + // Get the inherited component. We need to do a lookup due to query conflict (Error B0001). + let inherited_propagator = maybe_inherited.get(entity).unwrap().1.unwrap().propagator; + + let Ok((propagator, maybe_render_layers, maybe_camera_layer, maybe_camera, propagate)) = + all_propagators.get(inherited_propagator) + else { + // Remove InheritedRenderLayers from descendents if the propagator is missing + // - This is either an error caused by manually modifying InheritedRenderLayers, or is caused by a + // reparenting + propagator despawn. + + // Iterate children + for child in children.iter().copied() { + // Skip children that were already updated. + // - Note that this can happen e.g. because the child lost the PropagateRenderLayers component. + if updated_entities.contains(child) { + continue; + } + + // Propagate + apply_full_propagation_force_remove( + &mut commands, + &mut updated_entities, + &children_query, + &maybe_inherited, + child, + ); + } + + continue; + }; + + // Get value to propagate. + let mut propagated = propagate.get_render_layers( + updated_entities.saved(), + maybe_render_layers, + maybe_camera_layer, + maybe_camera, + ); + + // Iterate children + for child in children.iter().copied() { + // Skip children that were already updated. We only skip updated children of this initial high-level + // loop, not children within the recursion which need to be force-updated. The 'span' of entities + // we update in this step starts at non-updated children of an entity with InheritedRenderLayers. + // - Note that this can happen e.g. because the child lost the PropagateRenderLayers component. + if updated_entities.contains(child) { + continue; + } + + // Propagate + apply_full_propagation_force_update( + &mut commands, + &mut updated_entities, + &children_query, + &mut maybe_inherited, + propagator, + &propagated, + child, + ); + } + + // Reclaim memory + propagated.reclaim(updated_entities.saved()); + } +} + +/// Handles non-propagator entities with `InheritedRenderLayers` whose parents changed. +/// - Since `handle_new_children_nonpropagator` handles all cases where the parent has `InheritedRenderLayers`, this +/// system just needs to remove `InheritedRenderLayers` from non-updated entities and their non-updated descendents +/// that have `InheritedRenderLayers` (stopping at propagators and non-updated descendents without +/// `InheritedRenderLayers`). +/// - We skip non-updated entities whose parents are updated, because that implies the current `InheritedRenderLayers` +/// propagator is accurate. +fn handle_new_parent_nonpropagator( + mut commands: Commands, + mut updated_entities: ResMut, + // Entities with InheritedRenderLayers that changed parents + inherited_with_parent: Query< + (Entity, Option<&Children>, &Parent), + ( + Changed, + With, + Without, + ), + >, + // Query for Children + children_query: Query<&Children>, + // Query for updating InheritedRenderLayers on non-propagator entities. + maybe_inherited: Query< + (Option<&RenderLayers>, Option<&mut InheritedRenderLayers>), + Without, + >, +) { + for (entity, maybe_children, parent) in inherited_with_parent.iter() { + // Skip entity if already updated + if updated_entities.contains(entity) { + continue; + } + + // Skip entity if parent was updated + if updated_entities.contains(**parent) { + continue; + } + + // Remove from self. + commands.entity(entity).remove::(); + + // Mark as updated. + updated_entities.insert(entity); + + // Iterate children. + // - We assume the parent of this entity does NOT have InheritedRenderLayers, so neither should its + // descendents. + let Some(children) = maybe_children else { + continue; + }; + for child in children.iter().copied() { + apply_full_propagation_force_remove( + &mut commands, + &mut updated_entities, + &children_query, + &maybe_inherited, + child, + ); + } + } +} + +/// Handles added/removed/changed `RenderLayers` for entities with existing `InheritedRenderLayers`. +fn handle_modified_renderlayers( + mut updated_entities: ResMut, + // Entities with InheritedRenderLayers that changed RenderLayers + inherited_changed: Query< + Entity, + ( + Changed, + With, + Without, + ), + >, + // RenderLayers removals. + mut removed_renderlayers: RemovedComponents, + // Query for accessing propagators + all_propagators: Query<( + Entity, + Option<&RenderLayers>, + Option<&CameraLayer>, + Has, + &PropagateRenderLayers, + )>, + // Query for updating InheritedRenderLayers on non-propagator entities. + mut inherited: Query< + (Option<&RenderLayers>, &mut InheritedRenderLayers), + Without, + >, +) { + for entity in inherited_changed.iter().chain(removed_renderlayers.read()) { + // Skip entity if already updated. + if updated_entities.contains(entity) { + continue; + } + + // Skip entity if it's a propagator or doesn't exist. + let Ok((entity_render_layers, mut inherited)) = inherited.get_mut(entity) else { + continue; + }; + + // Skip entity if propagator is missing. + // - This is an error, hierarchy steps should have marked this entity as updated. + let Ok((_propagator, maybe_render_layers, maybe_camera_layer, maybe_camera, propagate)) = + all_propagators.get(inherited.propagator) + else { + error_once!("hierarchy error: propagator missing for {entity} in `handle_modified_renderlayers`"); + continue; + }; + + // Get propagated value. + let mut propagated = propagate.get_render_layers( + updated_entities.saved(), + maybe_render_layers, + maybe_camera_layer, + maybe_camera, + ); + + // Update entity value. + inherited + .computed + .set_from(entity_render_layers.unwrap_or(&RenderLayers::empty())); + inherited.computed.merge(&propagated); + + // Mark updated (in case of duplicates due to removals). + updated_entities.insert(entity); + + // Reclaim memory + propagated.reclaim(updated_entities.saved()); + } +} diff --git a/crates/bevy_render/src/view/visibility/render_layers.rs b/crates/bevy_render/src/view/visibility/render_layers.rs index 2a0e5da7d89fc..3d34b3eff8a03 100644 --- a/crates/bevy_render/src/view/visibility/render_layers.rs +++ b/crates/bevy_render/src/view/visibility/render_layers.rs @@ -1,154 +1,512 @@ -use bevy_ecs::prelude::{Component, ReflectComponent}; -use bevy_reflect::std_traits::ReflectDefault; +use crate::view::*; + +use bevy_derive::{Deref, DerefMut}; +use bevy_ecs::prelude::Component; use bevy_reflect::Reflect; +use bevy_utils::warn_once; + +use smallvec::SmallVec; +use std::ops::Deref; -type LayerMask = u32; +/// Records the highest [`RenderLayer`] that can be added to a [`RenderLayers`] before +/// a warning is emitted. +/// +/// We issue a warning because [`RenderLayers`] allocates in order to have enough room for a given +/// [`RenderLayer`], which is an index into a growable bitset. Large [`RenderLayer`] values can consume +/// a lot of memory since [`RenderLayers`] and [`InheritedRenderLayers`] are potentially on many entities. +pub const RENDER_LAYERS_WARNING_LIMIT: usize = 1024; + +/// Records the highest [`RenderLayer`] that can be added to a [`RenderLayers`] before +/// a panic occurs. +/// +/// We panic because [`RenderLayers`] allocates in order to have enough room for a given +/// [`RenderLayer`], which is an index into a growable bitset. Large [`RenderLayer`] values can consume +/// a lot of memory since [`RenderLayers`] and [`InheritedRenderLayers`] are potentially on many entities. +pub const RENDER_LAYERS_PANIC_LIMIT: usize = 1_000_000; -/// An identifier for a rendering layer. -pub type Layer = u8; +/// The default [`RenderLayer`]. +pub static DEFAULT_RENDER_LAYER: RenderLayer = RenderLayer(0); + +/// Wraps a specific render layer that can be stored in [`RenderLayers`]. +/// +/// Stores an index into the [`RenderLayers`] internal bitmask. +//todo: Upper limit policy for render layer indices. +#[derive(Reflect, Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Deref, DerefMut)] +#[reflect(Default, PartialEq)] +pub struct RenderLayer(pub usize); + +impl RenderLayer { + /// Returns `true` if equal to [`DEFAULT_RENDER_LAYER`]. + pub fn is_default(&self) -> bool { + *self == DEFAULT_RENDER_LAYER + } +} + +impl From for RenderLayer { + fn from(layer: usize) -> Self { + Self(layer) + } +} + +impl Default for RenderLayer { + fn default() -> Self { + DEFAULT_RENDER_LAYER + } +} -/// Describes which rendering layers an entity belongs to. +/// Component on an entity that controls which cameras can see it. /// -/// Cameras with this component will only render entities with intersecting -/// layers. +/// Records a growable bitmask of flags. +/// Individual render layers can be defined with [`RenderLayer`], which is an index +/// into the internal bitmask. /// -/// There are 32 layers numbered `0` - [`TOTAL_LAYERS`](RenderLayers::TOTAL_LAYERS). Entities may -/// belong to one or more layers, or no layer at all. +/// `RenderLayers::default()` starts with [`DEFAULT_RENDER_LAYER`], which is the global default +/// layer. /// -/// The [`Default`] instance of `RenderLayers` contains layer `0`, the first layer. +/// See [`CameraLayer`] for camera-specific details. /// -/// An entity with this component without any layers is invisible. +/// ### Entity default behavior /// -/// Entities without this component belong to layer `0`. -#[derive(Component, Copy, Clone, Reflect, PartialEq, Eq, PartialOrd, Ord)] +/// All entities without a [`RenderLayers`] component are in [`DEFAULT_RENDER_LAYER`] by +/// default (layer 0). If you add a [`RenderLayers`] component to an entity, it may no longer +/// be in the default layer if the [`RenderLayers`] component doesn't include it. +/// +/// For example, if you do `entity.insert(RenderLayers::from(RenderLayer(1)))`, then `entity` +/// will only be in layer 1. You can instead do: +/// +/** +```ignore +// Option 1: default +let mut layers = RenderLayers::default(); +layers.add(RenderLayer(1)); +entity.insert(layers); + +// Option 2: explicit +let mut layers = RenderLayers::from(0); +layers.add(RenderLayer(1)); +entity.insert(layers); + +// Option 3: manual +let layers = RenderLayers::from_layers(&[0, 1]); +entity.insert(layers); +``` +*/ +/// +/// Similarly, if an entity without [`RenderLayers`] inherits from an entity with [`PropagateRenderLayers`] that +/// doesn't propagate layer 0, then the entity's computed [`InheritedRenderLayers`] won't have layer 0 and the +/// entity won't be visible to layer 0. +/// +/// ### Performance +/// +/// `RenderLayers` occupies 24 bytes on the stack. +/// +/// `RenderLayers` can store up to `RenderLayer(63)` without allocating. Allocations occur in 8-byte +/// increments, so the second allocation will occur after `RenderLayer(127)`, and so on. +/// +/// See [`RENDER_LAYERS_WARNING_LIMIT`] and [`RENDER_LAYERS_PANIC_LIMIT`] for `RenderLayers` restrictions. +#[derive(Component, Clone, PartialEq, Reflect)] #[reflect(Component, Default, PartialEq)] -pub struct RenderLayers(LayerMask); +pub struct RenderLayers { + layers: SmallVec<[u64; 1]>, +} + +impl RenderLayers { + /// Makes a new `RenderLayers` with no layers. + pub fn empty() -> Self { + Self { + layers: SmallVec::default(), + } + } + + /// Makes a new `RenderLayers` from a single [`RenderLayer`]. + pub fn from_layer(layer: impl Into) -> Self { + let mut layers = Self { + layers: SmallVec::default(), + }; + layers.add(layer); + layers + } + + /// Makes a new `RenderLayers` from a slice. + pub fn from_layers + Copy>(layers: &[T]) -> Self { + layers.iter().map(|l| (*l).into()).collect() + } + + /// Adds a [`RenderLayer`]. + pub fn add(&mut self, layer: impl Into) -> &mut Self { + let (buffer_index, bit) = Self::layer_info(*(layer.into())); + self.extend_buffer(buffer_index + 1); + self.layers[buffer_index] |= bit; + self + } + + /// Removes a [`RenderLayer`]. + /// + /// Does not shrink the internal buffer even if doing so is possible after + /// removing the layer. We assume if you added a large layer then it is + /// possible you may re-add another large layer. + pub fn remove(&mut self, layer: impl Into) -> &mut Self { + let (buffer_index, bit) = Self::layer_info(*(layer.into())); + if buffer_index >= self.layers.len() { + return self; + } + self.layers[buffer_index] &= !bit; + self + } + + /// Clears all stored render layers without deallocating. + pub fn clear(&mut self) { + self.layers.clear(); + } + + /// Copies `other` into `Self`. + /// + /// This is more efficient than cloning `other` if you want to reuse a `RenderLayers` + /// that is potentially allocated. + pub fn set_from(&mut self, other: &Self) { + self.layers.clear(); + self.layers.reserve_exact(other.layers.len()); + self.layers.extend_from_slice(other.layers.as_slice()); + } + + /// Merges `other` into `Self`. + /// + /// After merging, `Self` will include all set bits from `other` and `Self`. + /// + /// Will allocate if necessary to include all set bits of `other`. + pub fn merge(&mut self, other: &Self) { + self.extend_buffer(other.layers.len()); + + for (self_layer, other_layer) in self.layers.iter_mut().zip(other.layers.iter()) { + *self_layer |= *other_layer; + } + } + + /// Gets the number of stored layers. + /// + /// Equivalent to `self.iter().count()`. + pub fn len(&self) -> usize { + self.iter().count() + } + + /// Iterates the internal render layers. + pub fn iter(&self) -> impl Iterator + '_ { + self.layers.iter().copied().flat_map(Self::iter_layers) + } + + /// Returns `true` if the specified render layer is included in this `RenderLayers`. + pub fn contains(&self, layer: impl Into) -> bool { + let (buffer_index, bit) = Self::layer_info(*(layer.into())); + if buffer_index >= self.layers.len() { + return false; + } + (self.layers[buffer_index] & bit) != 0 + } + + /// Returns `true` if `Self` and `other` contain any matching layers. + pub fn intersects(&self, other: &Self) -> bool { + for (self_layer, other_layer) in self.layers.iter().zip(other.layers.iter()) { + if (*self_layer & *other_layer) != 0 { + return true; + } + } + + false + } + + /// Returns `true` if `Self` intersects with an [`ExtractedRenderLayers`]. + /// + /// If `extracted` is `None`, then intersections is tested using [`RenderLayers::default`]. + pub fn intersects_extracted(&self, extracted: Option<&ExtractedRenderLayers>) -> bool { + let default_render_layers = RenderLayers::default(); + let layers = extracted.map(|i| &**i).unwrap_or(&default_render_layers); + self.intersects(layers) + } + + /// Gets the bitmask representation of the contained layers + /// as a slice of bitmasks. + pub fn bits(&self) -> &[u64] { + self.layers.as_slice() + } + + /// Returns `true` if the internal bitmask is on the heap. + pub fn is_allocated(&self) -> bool { + self.layers.spilled() + } + + fn layer_info(layer: usize) -> (usize, u64) { + if layer > RENDER_LAYERS_WARNING_LIMIT { + warn_once!("RenderLayers encountered a layer {layer} that exceeded the warning limit \ + RENDER_LAYERS_WARNING_LIMIT = {RENDER_LAYERS_WARNING_LIMIT}, you can ignore this message if \ + that is not a bug"); + } + if layer > RENDER_LAYERS_PANIC_LIMIT { + panic!("RenderLayers encountered a layer {layer} that exceeded the maximum upper bound on number of \ + layers RENDER_LAYERS_PANIC_LIMIT = {RENDER_LAYERS_PANIC_LIMIT}"); + } + + let buffer_index = layer / 64; + let bit_index = layer % 64; + let bit = 1u64 << bit_index; + + (buffer_index, bit) + } + + fn extend_buffer(&mut self, other_len: usize) { + let new_size = std::cmp::max(self.layers.len(), other_len); + self.layers.reserve_exact(new_size - self.layers.len()); + self.layers.resize(new_size, 0u64); + } + + fn iter_layers(mut buffer: u64) -> impl Iterator + 'static { + let mut layer: usize = 0; + std::iter::from_fn(move || { + if buffer == 0 { + return None; + } + let next = buffer.trailing_zeros() + 1; + buffer >>= next; + layer += next as usize; + Some(RenderLayer(layer - 1)) + }) + } +} + +impl> FromIterator for RenderLayers { + fn from_iter>(i: T) -> Self { + i.into_iter().fold(Self::empty(), |mut mask, g| { + mask.add(g); + mask + }) + } +} + +impl Default for RenderLayers { + fn default() -> Self { + Self::from_layer(DEFAULT_RENDER_LAYER) + } +} impl std::fmt::Debug for RenderLayers { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_tuple("RenderLayers") - .field(&self.iter().collect::>()) + .field(&self.iter().map(|l| *l).collect::>()) .finish() } } -impl FromIterator for RenderLayers { - fn from_iter>(i: T) -> Self { - i.into_iter().fold(Self::none(), |mask, g| mask.with(g)) - } +/// Stores a [`RenderLayers`] reference or value. +/// +/// Useful for unwrapping an optional reference with fallback to a default value. +pub enum RenderLayersRef<'a> { + None, + Ref(&'a RenderLayers), + Val(RenderLayers), } -impl Default for RenderLayers { - /// By default, this structure includes layer `0`, which represents the first layer. - fn default() -> Self { - RenderLayers::layer(0) +impl<'a> RenderLayersRef<'a> { + /// Moves `self` into `other` if `self` is on the heap and `other` is not. + /// + /// Sets self to [`Self::None`]. + /// + /// Returns `true` if reclamation occurred. + pub(crate) fn reclaim(&mut self, other: &mut RenderLayers) -> bool { + match self { + Self::Val(layers) => { + if !layers.is_allocated() || other.is_allocated() { + return false; + } + *other = std::mem::take(layers); + *self = Self::None; + true + } + _ => false, + } } } -impl RenderLayers { - /// The total number of layers supported. - pub const TOTAL_LAYERS: usize = std::mem::size_of::() * 8; +impl<'a> Deref for RenderLayersRef<'a> { + type Target = RenderLayers; + + fn deref(&self) -> &Self::Target { + self.get() + } +} - /// Create a new `RenderLayers` belonging to the given layer. - pub const fn layer(n: Layer) -> Self { - RenderLayers(0).with(n) +impl<'a> RenderLayersRef<'a> { + /// Gets a reference to the internal [`RenderLayers`]. + /// + /// Panices if in state [`Self::None`]. + pub fn get(&self) -> &RenderLayers { + match self { + Self::None => { + panic!("RenderLayersRef cannot be dereferenced when empty"); + } + Self::Ref(layers) => layers, + Self::Val(layers) => layers, + } } +} - /// Create a new `RenderLayers` that belongs to all layers. - pub const fn all() -> Self { - RenderLayers(u32::MAX) +/// Stores a [`RenderLayers`] pointer or value. +/// +/// Useful as an alternative to [`RenderLayersRef`] when you can't store a reference, for example within a [`Local`] +/// that buffers a cache that is rewritten every system call. +pub enum RenderLayersPtr { + Ptr(*const RenderLayers), + Val(RenderLayers), +} + +impl RenderLayersPtr { + /// Gets a reference to the internal [`RenderLayers`]. + /// + /// # Safety + /// Safety must be established by the user. + pub unsafe fn get(&self) -> &RenderLayers { + match self { + // SAFETY: Safety is established by the caller. + Self::Ptr(layers) => unsafe { layers.as_ref().unwrap() }, + Self::Val(layers) => layers, + } } +} + +/// Component on camera entities that controls which [`RenderLayer`] is visible to the camera. +/// +/// Cameras can see *at most* one [`RenderLayer`]. +/// A camera without the `CameraLayer` component will see the [`DEFAULT_RENDER_LAYER`] layer. +/// A camera with [`CameraLayer::empty`] will see no entities. +/// +/// Cameras use entities' [`InheritedRenderLayers`] to determine visibility, with a fall-back to the +/// entity's [`RenderLayers`]. If an entity does not have [`InheritedRenderLayers`] +/// or [`RenderLayers`] components, then the camera will only see it if the camera can +/// view the [`DEFAULT_RENDER_LAYER`] layer. +/// +/// A default `CameraLayer` will contain [`DEFAULT_RENDER_LAYER`]. +#[derive(Component, Debug, Clone, PartialEq, Reflect)] +#[reflect(Component, Default, PartialEq)] +pub struct CameraLayer { + layer: Option, +} - /// Create a new `RenderLayers` that belongs to no layers. - pub const fn none() -> Self { - RenderLayers(0) +impl CameraLayer { + /// Makes a new `CameraLayer` with no visible [`RenderLayer`]. + pub fn empty() -> Self { + Self { layer: None } } - /// Create a `RenderLayers` from a list of layers. - pub fn from_layers(layers: &[Layer]) -> Self { - layers.iter().copied().collect() + /// Makes a new `CameraLayer` with a [`RenderLayer`]. + pub fn new(layer: impl Into) -> Self { + Self { + layer: Some(layer.into()), + } } - /// Add the given layer. - /// - /// This may be called multiple times to allow an entity to belong - /// to multiple rendering layers. The maximum layer is `TOTAL_LAYERS - 1`. - /// - /// # Panics - /// Panics when called with a layer greater than `TOTAL_LAYERS - 1`. - #[must_use] - pub const fn with(mut self, layer: Layer) -> Self { - assert!((layer as usize) < Self::TOTAL_LAYERS); - self.0 |= 1 << layer; + /// Sets the [`RenderLayer`]. + pub fn set(&mut self, layer: impl Into) -> &mut Self { + self.layer = Some(layer.into()); self } - /// Removes the given rendering layer. + /// Removes the current [`RenderLayer`]. /// - /// # Panics - /// Panics when called with a layer greater than `TOTAL_LAYERS - 1`. - #[must_use] - pub const fn without(mut self, layer: Layer) -> Self { - assert!((layer as usize) < Self::TOTAL_LAYERS); - self.0 &= !(1 << layer); - self + /// The camera will see nothing after this is called. + pub fn clear(&mut self) { + self.layer = None; } - /// Get an iterator of the layers. - pub fn iter(&self) -> impl Iterator { - let total: Layer = std::convert::TryInto::try_into(Self::TOTAL_LAYERS).unwrap(); - let mask = *self; - (0..total).filter(move |g| RenderLayers::layer(*g).intersects(&mask)) + /// Returns the current [`RenderLayer`] if there is one. + pub fn layer(&self) -> Option { + self.layer } - /// Determine if a `RenderLayers` intersects another. - /// - /// `RenderLayers`s intersect if they share any common layers. + /// Returns `true` if the specified render layer equals this `CameraLayer`. + pub fn equals(&self, layer: impl Into) -> bool { + self.layer == Some(layer.into()) + } + + /// Returns `true` if the entity with the specified [`RenderLayers`] is visible + /// to the `camera` that has this `CameraLayer`. + pub fn entity_is_visible(&self, layers: &RenderLayers) -> bool { + let Some(layer) = self.layer else { + return false; + }; + layers.contains(layer) + } + + /// Converts the internal [`RenderLayer`] into a [`RenderLayers`]. /// - /// A `RenderLayers` with no layers will not match any other - /// `RenderLayers`, even another with no layers. - pub fn intersects(&self, other: &RenderLayers) -> bool { - (self.0 & other.0) > 0 + /// Returns an empty [`RenderLayers`] if there is no stored layer. + pub fn get_layers(&self) -> RenderLayers { + match self.layer { + Some(layer) => RenderLayers::from_layer(layer), + None => RenderLayers::empty(), + } } +} - /// get the bitmask representation of the contained layers - pub fn bits(&self) -> u32 { - self.0 +impl Default for CameraLayer { + /// Equivalent to `Self::new(DEFAULT_RENDER_LAYER)`. + fn default() -> Self { + Self::new(DEFAULT_RENDER_LAYER) } } #[cfg(test)] mod rendering_mask_tests { - use super::{Layer, RenderLayers}; + use super::{RenderLayer, RenderLayers, DEFAULT_RENDER_LAYER}; + use smallvec::SmallVec; #[test] fn rendering_mask_sanity() { assert_eq!( - RenderLayers::TOTAL_LAYERS, - 32, - "total layers is what we think it is" + RenderLayers::default().len(), + 1, + "default layer contains only one layer" + ); + assert!( + RenderLayers::default().contains(DEFAULT_RENDER_LAYER), + "default layer contains default" + ); + assert_eq!( + RenderLayers::from_layer(1).len(), + 1, + "from contains 1 layer" + ); + assert!( + RenderLayers::from_layer(1).contains(RenderLayer(1)), + "contains is accurate" + ); + assert!( + !RenderLayers::from_layer(1).contains(RenderLayer(2)), + "contains fails when expected" + ); + + assert_eq!( + RenderLayers::from_layer(0).add(1).layers[0], + 3, + "layer 0 + 1 is mask 3" ); - assert_eq!(RenderLayers::layer(0).0, 1, "layer 0 is mask 1"); - assert_eq!(RenderLayers::layer(1).0, 2, "layer 1 is mask 2"); - assert_eq!(RenderLayers::layer(0).with(1).0, 3, "layer 0 + 1 is mask 3"); assert_eq!( - RenderLayers::layer(0).with(1).without(0).0, + RenderLayers::from_layer(0).add(1).remove(0).layers[0], 2, "layer 0 + 1 - 0 is mask 2" ); assert!( - RenderLayers::layer(1).intersects(&RenderLayers::layer(1)), + RenderLayers::from_layer(1).intersects(&RenderLayers::from_layer(1)), "layers match like layers" ); assert!( - RenderLayers::layer(0).intersects(&RenderLayers(1)), + RenderLayers::from_layer(0).intersects(&RenderLayers { + layers: SmallVec::from_slice(&[1]) + }), "a layer of 0 means the mask is just 1 bit" ); assert!( - RenderLayers::layer(0) - .with(3) - .intersects(&RenderLayers::layer(3)), + RenderLayers::from_layer(0) + .add(3) + .intersects(&RenderLayers::from_layer(3)), "a mask will match another mask containing any similar layers" ); @@ -158,19 +516,24 @@ mod rendering_mask_tests { ); assert!( - !RenderLayers::layer(0).intersects(&RenderLayers::layer(1)), + !RenderLayers::from_layer(0).intersects(&RenderLayers::from_layer(1)), "masks with differing layers do not match" ); assert!( - !RenderLayers(0).intersects(&RenderLayers(0)), + !RenderLayers::empty().intersects(&RenderLayers::empty()), "empty masks don't match" ); assert_eq!( RenderLayers::from_layers(&[0, 2, 16, 30]) .iter() .collect::>(), - vec![0, 2, 16, 30], - "from_layers and get_layers should roundtrip" + vec![ + RenderLayer(0), + RenderLayer(2), + RenderLayer(16), + RenderLayer(30) + ], + "from and get_layers should roundtrip" ); assert_eq!( format!("{:?}", RenderLayers::from_layers(&[0, 1, 2, 3])).as_str(), @@ -179,7 +542,7 @@ mod rendering_mask_tests { ); assert_eq!( RenderLayers::from_layers(&[0, 1, 2]), - >::from_iter(vec![0, 1, 2]), + >::from_iter(vec![0, 1, 2]), "from_layers and from_iter are equivalent" ); } diff --git a/examples/2d/pixel_grid_snap.rs b/examples/2d/pixel_grid_snap.rs index 075dc06818f39..0eb143cad15b8 100644 --- a/examples/2d/pixel_grid_snap.rs +++ b/examples/2d/pixel_grid_snap.rs @@ -7,7 +7,7 @@ use bevy::{ render_resource::{ Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, }, - view::RenderLayers, + view::{CameraLayer, RenderLayer, RenderLayers}, }, sprite::MaterialMesh2dBundle, window::WindowResized, @@ -19,12 +19,12 @@ const RES_WIDTH: u32 = 160; /// In-game resolution height. const RES_HEIGHT: u32 = 90; -/// Default render layers for pixel-perfect rendering. +/// Default render layer for pixel-perfect rendering. /// You can skip adding this component, as this is the default. -const PIXEL_PERFECT_LAYERS: RenderLayers = RenderLayers::layer(0); +const PIXEL_PERFECT_LAYER: RenderLayer = RenderLayer(0); -/// Render layers for high-resolution rendering. -const HIGH_RES_LAYERS: RenderLayers = RenderLayers::layer(1); +/// Render layer for high-resolution rendering. +const HIGH_RES_LAYER: RenderLayer = RenderLayer(1); fn main() { App::new() @@ -44,7 +44,7 @@ struct Canvas; #[derive(Component)] struct InGameCamera; -/// Camera that renders the [`Canvas`] (and other graphics on [`HIGH_RES_LAYERS`]) to the screen. +/// Camera that renders the [`Canvas`] (and other graphics on [`HIGH_RES_LAYER`]) to the screen. #[derive(Component)] struct OuterCamera; @@ -60,7 +60,7 @@ fn setup_sprite(mut commands: Commands, asset_server: Res) { ..default() }, Rotate, - PIXEL_PERFECT_LAYERS, + RenderLayers::from_layer(PIXEL_PERFECT_LAYER), )); // the sample sprite that will be rendered to the high-res "outer world" @@ -71,7 +71,7 @@ fn setup_sprite(mut commands: Commands, asset_server: Res) { ..default() }, Rotate, - HIGH_RES_LAYERS, + RenderLayers::from_layer(HIGH_RES_LAYER), )); } @@ -89,7 +89,7 @@ fn setup_mesh( ..default() }, Rotate, - PIXEL_PERFECT_LAYERS, + RenderLayers::from_layer(PIXEL_PERFECT_LAYER), )); } @@ -122,7 +122,7 @@ fn setup_camera(mut commands: Commands, mut images: ResMut>) { let image_handle = images.add(canvas); - // this camera renders whatever is on `PIXEL_PERFECT_LAYERS` to the canvas + // this camera renders whatever is on `PIXEL_PERFECT_LAYER` to the canvas commands.spawn(( Camera2dBundle { camera: Camera { @@ -134,7 +134,7 @@ fn setup_camera(mut commands: Commands, mut images: ResMut>) { ..default() }, InGameCamera, - PIXEL_PERFECT_LAYERS, + CameraLayer::new(PIXEL_PERFECT_LAYER), )); // spawn the canvas @@ -144,12 +144,16 @@ fn setup_camera(mut commands: Commands, mut images: ResMut>) { ..default() }, Canvas, - HIGH_RES_LAYERS, + RenderLayers::from_layer(HIGH_RES_LAYER), )); - // the "outer" camera renders whatever is on `HIGH_RES_LAYERS` to the screen. + // the "outer" camera renders whatever is on `HIGH_RES_LAYER` to the screen. // here, the canvas and one of the sample sprites will be rendered by this camera - commands.spawn((Camera2dBundle::default(), OuterCamera, HIGH_RES_LAYERS)); + commands.spawn(( + Camera2dBundle::default(), + OuterCamera, + CameraLayer::new(HIGH_RES_LAYER), + )); } /// Rotates entities to demonstrate grid snapping. diff --git a/examples/3d/render_layers.rs b/examples/3d/render_layers.rs new file mode 100644 index 0000000000000..7feee93d3d9b9 --- /dev/null +++ b/examples/3d/render_layers.rs @@ -0,0 +1,248 @@ +//! Load a scene from a glTF file and render it with different render layers. + +use bevy::{ + color::palettes, + pbr::DirectionalLightShadowMap, + prelude::*, + render::camera::Viewport, + render::view::{CameraLayer, PropagateRenderLayers, RenderLayers}, + window::PrimaryWindow, +}; + +fn main() { + App::new() + .insert_resource(DirectionalLightShadowMap { size: 4096 }) + .add_plugins(DefaultPlugins) + .add_systems(Startup, setup) + .add_systems(Update, (toggle_layers_camera1, toggle_layers_camera2)) + .run(); +} + +#[derive(Component)] +struct Camera1; + +#[derive(Component)] +struct Camera2; + +fn setup( + mut commands: Commands, + asset_server: Res, + mut meshes: ResMut>, + mut materials: ResMut>, + window: Query<&Window, With>, +) { + // Camera 1 + let window = window.single(); + let camera1 = commands + .spawn(( + Camera3dBundle { + camera: Camera { + viewport: Some(Viewport { + physical_position: UVec2 { + x: window.physical_width() / 4, + y: 0, + }, + physical_size: UVec2 { + x: window.physical_width() / 2, + y: window.physical_height() / 2, + }, + ..Default::default() + }), + ..Default::default() + }, + transform: Transform::from_xyz(0., 1.4, 2.0) + .looking_at(Vec3::new(0., 0.3, 0.0), Vec3::Y), + ..default() + }, + EnvironmentMapLight { + diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"), + specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), + intensity: 1500.0, + }, + CameraLayer::new(0), + Camera1, + )) + .id(); + + // Camera 2 + let camera2 = commands + .spawn(( + Camera3dBundle { + camera: Camera { + order: 1, + viewport: Some(Viewport { + physical_position: UVec2 { + x: window.physical_width() / 4, + y: window.physical_height() / 2, + }, + physical_size: UVec2 { + x: window.physical_width() / 2, + y: window.physical_height() / 2, + }, + ..Default::default() + }), + ..Default::default() + }, + transform: Transform::from_xyz(0., 1.4, 2.0) + .looking_at(Vec3::new(0., 0.3, 0.0), Vec3::Y), + ..default() + }, + EnvironmentMapLight { + diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"), + specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), + intensity: 1500.0, + }, + CameraLayer::new(0), + Camera2, + )) + .id(); + + // Plane + commands.spawn(( + PbrBundle { + mesh: meshes.add(Plane3d::default().mesh().size(5000.0, 5000.0)), + material: materials.add(Color::srgb(0.3, 0.5, 0.3)), + ..default() + }, + RenderLayers::from_layer(0), + )); + + // Text (camera 1) + commands.spawn(( + TextBundle::from_section( + "Camera 1:\n\ + Press '1..3' to toggle mesh render layers\n\ + Press '4..6' to toggle directional light render layers", + TextStyle { + font_size: 20., + ..default() + }, + ) + .with_style(Style { + position_type: PositionType::Absolute, + top: Val::Px(12.0), + left: Val::Px(12.0), + ..default() + }), + TargetCamera(camera1), + )); + + // Text (camera 2) + commands.spawn(( + TextBundle::from_section( + "Camera 2:\n\ + Press 'Q/W/E' to toggle mesh render layers\n\ + Press 'R/T/Y' to toggle directional light render layers", + TextStyle { + font_size: 20., + ..default() + }, + ) + .with_style(Style { + position_type: PositionType::Absolute, + top: Val::Px(12.0), + left: Val::Px(12.0), + ..default() + }), + TargetCamera(camera2), + )); + + // Spawn three copies of the scene, each with a different render layer. + for i in 0..3 { + commands.spawn(( + SceneBundle { + transform: Transform::from_xyz(i as f32 - 1.0, 0.0, 0.0), + scene: asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0"), + ..default() + }, + RenderLayers::from_layer(i + 1), + PropagateRenderLayers::Auto, + )); + } + + // Spawn three directional lights, each with a different render layer. + let colors = [ + palettes::basic::RED, + palettes::basic::GREEN, + palettes::basic::NAVY, + ]; + for (i, color) in (0..3).zip(colors.iter()) { + commands.spawn(( + DirectionalLightBundle { + transform: Transform::from_xyz(4.0, 25.0, 8.0).looking_at(Vec3::ZERO, Vec3::Y), + directional_light: DirectionalLight { + shadows_enabled: true, + illuminance: 100_000.0, + color: (*color).into(), + ..default() + }, + ..default() + }, + RenderLayers::from_layer(i + 4), + )); + } +} + +fn toggle_layers_camera1( + mut query_camera: Query<&mut CameraLayer, With>, + keyboard: Res>, +) { + let Ok(mut camera_view) = query_camera.get_single_mut() else { + return; + }; + + if keyboard.just_pressed(KeyCode::Digit1) { + toggle_camera_layer(&mut camera_view, 1); + } + if keyboard.just_pressed(KeyCode::Digit2) { + toggle_camera_layer(&mut camera_view, 2); + } + if keyboard.just_pressed(KeyCode::Digit3) { + toggle_camera_layer(&mut camera_view, 3); + } + if keyboard.just_pressed(KeyCode::Digit4) { + toggle_camera_layer(&mut camera_view, 4); + } + if keyboard.just_pressed(KeyCode::Digit5) { + toggle_camera_layer(&mut camera_view, 5); + } + if keyboard.just_pressed(KeyCode::Digit6) { + toggle_camera_layer(&mut camera_view, 6); + } +} + +fn toggle_layers_camera2( + mut query_camera: Query<&mut CameraLayer, With>, + keyboard: Res>, +) { + let Ok(mut camera_view) = query_camera.get_single_mut() else { + return; + }; + + if keyboard.just_pressed(KeyCode::KeyQ) { + toggle_camera_layer(&mut camera_view, 1); + } + if keyboard.just_pressed(KeyCode::KeyW) { + toggle_camera_layer(&mut camera_view, 2); + } + if keyboard.just_pressed(KeyCode::KeyE) { + toggle_camera_layer(&mut camera_view, 3); + } + if keyboard.just_pressed(KeyCode::KeyR) { + toggle_camera_layer(&mut camera_view, 4); + } + if keyboard.just_pressed(KeyCode::KeyT) { + toggle_camera_layer(&mut camera_view, 5); + } + if keyboard.just_pressed(KeyCode::KeyY) { + toggle_camera_layer(&mut camera_view, 6); + } +} + +fn toggle_camera_layer(camera_view: &mut CameraLayer, layer: usize) { + if camera_view.equals(layer) { + camera_view.clear(); + } else { + camera_view.set(layer); + } +} diff --git a/examples/3d/render_to_texture.rs b/examples/3d/render_to_texture.rs index b468347831ca4..7effd275dea5c 100644 --- a/examples/3d/render_to_texture.rs +++ b/examples/3d/render_to_texture.rs @@ -8,7 +8,7 @@ use bevy::{ render_resource::{ Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, }, - view::RenderLayers, + view::{CameraLayer, RenderLayer, RenderLayers}, }, }; @@ -71,7 +71,7 @@ fn setup( }); // This specifies the layer used for the first pass, which will be attached to the first pass camera and cube. - let first_pass_layer = RenderLayers::layer(1); + let first_pass_layer = RenderLayer(1); // The cube that will be rendered to the texture. commands.spawn(( @@ -82,19 +82,17 @@ fn setup( ..default() }, FirstPassCube, - first_pass_layer, + RenderLayers::from_layer(first_pass_layer), )); - // Light - // NOTE: we add the light to all layers so it affects both the rendered-to-texture cube, and the cube on which we display the texture - // Setting the layer to RenderLayers::layer(0) would cause the main view to be lit, but the rendered-to-texture cube to be unlit. - // Setting the layer to RenderLayers::layer(1) would cause the rendered-to-texture cube to be lit, but the main view to be unlit. + // Light for the first pass + // NOTE: Lights only work properly when in one render layer. commands.spawn(( PointLightBundle { transform: Transform::from_translation(Vec3::new(0.0, 0.0, 10.0)), ..default() }, - RenderLayers::all(), + RenderLayers::from_layer(first_pass_layer), )); commands.spawn(( @@ -110,7 +108,8 @@ fn setup( .looking_at(Vec3::ZERO, Vec3::Y), ..default() }, - first_pass_layer, + // view the first pass layer + CameraLayer::new(first_pass_layer), )); let cube_size = 4.0; @@ -136,6 +135,14 @@ fn setup( MainPassCube, )); + // Light for the main pass cube + // NOTE: Lights only work properly when in one render layer, so we need separate lights for the first and + // main passes. + commands.spawn((PointLightBundle { + transform: Transform::from_translation(Vec3::new(0.0, 0.0, 10.0)), + ..default() + },)); + // The main pass camera. commands.spawn(Camera3dBundle { transform: Transform::from_xyz(0.0, 0.0, 15.0).looking_at(Vec3::ZERO, Vec3::Y), diff --git a/examples/README.md b/examples/README.md index 3f749784e7780..59695c266c411 100644 --- a/examples/README.md +++ b/examples/README.md @@ -143,6 +143,7 @@ Example | Description [Parenting](../examples/3d/parenting.rs) | Demonstrates parent->child relationships and relative transformations [Physically Based Rendering](../examples/3d/pbr.rs) | Demonstrates use of Physically Based Rendering (PBR) properties [Reflection Probes](../examples/3d/reflection_probes.rs) | Demonstrates reflection probes +[Render Layers](../examples/3d/render_layers.rs) | Load a scene from a glTF file and render it with different render layers. [Render to Texture](../examples/3d/render_to_texture.rs) | Shows how to render to a texture, useful for mirrors, UI, or exporting images [Screen Space Ambient Occlusion](../examples/3d/ssao.rs) | A scene showcasing screen space ambient occlusion [Shadow Biases](../examples/3d/shadow_biases.rs) | Demonstrates how shadow biases affect shadows in a 3d scene