From 4c3b7679ec88044418a52ab834cb7e73c522edb1 Mon Sep 17 00:00:00 2001 From: charlotte Date: Thu, 16 May 2024 09:15:47 -0700 Subject: [PATCH] #12502 Remove limit on RenderLayers. (#13317) # Objective Remove the limit of `RenderLayer` by using a growable mask using `SmallVec`. Changes adopted from @UkoeHB's initial PR here https://github.com/bevyengine/bevy/pull/12502 that contained additional changes related to propagating render layers. Changes ## Solution The main thing needed to unblock this is removing `RenderLayers` from our shader code. This primarily affects `DirectionalLight`. We are now computing a `skip` field on the CPU that is then used to skip the light in the shader. ## Testing Checked a variety of examples and did a quick benchmark on `many_cubes`. There were some existing problems identified during the development of the original pr (see: https://discord.com/channels/691052431525675048/1220477928605749340/1221190112939872347). This PR shouldn't change any existing behavior besides removing the layer limit (sans the comment in migration about `all` layers no longer being possible). --- ## Changelog Removed the limit on `RenderLayers` by using a growable bitset that only allocates when layers greater than 64 are used. ## Migration Guide - `RenderLayers::all()` no longer exists. Entities expecting to be visible on all layers, e.g. lights, should compute the active layers that are in use. --------- Co-authored-by: robtfm <50659922+robtfm@users.noreply.github.com> --- benches/benches/bevy_render/render_layers.rs | 19 +++ .../src/ui_debug_overlay/mod.rs | 6 +- crates/bevy_gizmos/src/config.rs | 2 +- crates/bevy_gizmos/src/pipeline_2d.rs | 8 +- crates/bevy_gizmos/src/pipeline_3d.rs | 8 +- crates/bevy_pbr/src/light/mod.rs | 27 ++-- crates/bevy_pbr/src/render/light.rs | 32 +++- .../bevy_pbr/src/render/mesh_view_types.wgsl | 2 +- crates/bevy_pbr/src/render/pbr_functions.wgsl | 6 +- crates/bevy_render/Cargo.toml | 2 +- crates/bevy_render/src/camera/camera.rs | 2 +- crates/bevy_render/src/view/mod.rs | 14 +- crates/bevy_render/src/view/view.wgsl | 1 - crates/bevy_render/src/view/visibility/mod.rs | 6 +- .../src/view/visibility/render_layers.rs | 152 ++++++++++++------ examples/3d/render_to_texture.rs | 21 ++- 16 files changed, 202 insertions(+), 106 deletions(-) create mode 100644 benches/benches/bevy_render/render_layers.rs diff --git a/benches/benches/bevy_render/render_layers.rs b/benches/benches/bevy_render/render_layers.rs new file mode 100644 index 0000000000..84f6b89077 --- /dev/null +++ b/benches/benches/bevy_render/render_layers.rs @@ -0,0 +1,19 @@ +use criterion::{black_box, criterion_group, criterion_main, Criterion}; + +use bevy_render::view::RenderLayers; + +fn render_layers(c: &mut Criterion) { + c.bench_function("layers_intersect", |b| { + let layer_a = RenderLayers::layer(1).with(2); + let layer_b = RenderLayers::layer(1); + b.iter(|| { + black_box(layer_a.intersects(&layer_b)) + }); + }); +} + +criterion_group!( + benches, + render_layers, +); +criterion_main!(benches); 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 3029a05c9b..6382cfa903 100644 --- a/crates/bevy_dev_tools/src/ui_debug_overlay/mod.rs +++ b/crates/bevy_dev_tools/src/ui_debug_overlay/mod.rs @@ -28,7 +28,7 @@ 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); +pub const LAYOUT_DEBUG_LAYERS: RenderLayers = RenderLayers::layer(16); #[derive(Clone, Copy)] struct LayoutRect { @@ -101,7 +101,7 @@ fn update_debug_camera( }, ..default() }, - LAYOUT_DEBUG_LAYERS, + LAYOUT_DEBUG_LAYERS.clone(), 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 = LAYOUT_DEBUG_LAYERS.clone(); } 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 92f6962c19..c3030d9a34 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 660cec02c9..1a86976ff0 100644 --- a/crates/bevy_gizmos/src/pipeline_2d.rs +++ b/crates/bevy_gizmos/src/pipeline_2d.rs @@ -269,9 +269,9 @@ fn queue_line_gizmos_2d( let mesh_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples()) | Mesh2dPipelineKey::from_hdr(view.hdr); + let render_layers = render_layers.unwrap_or_default(); 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(render_layers) { continue; } @@ -325,9 +325,9 @@ fn queue_line_joint_gizmos_2d( let mesh_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples()) | Mesh2dPipelineKey::from_hdr(view.hdr); + let render_layers = render_layers.unwrap_or_default(); 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(render_layers) { continue; } diff --git a/crates/bevy_gizmos/src/pipeline_3d.rs b/crates/bevy_gizmos/src/pipeline_3d.rs index e247220d54..bdcd75764b 100644 --- a/crates/bevy_gizmos/src/pipeline_3d.rs +++ b/crates/bevy_gizmos/src/pipeline_3d.rs @@ -303,7 +303,7 @@ 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 render_layers = render_layers.unwrap_or_default(); let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples()) | MeshPipelineKey::from_hdr(view.hdr); @@ -325,7 +325,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(render_layers) { continue; } @@ -389,7 +389,7 @@ 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 render_layers = render_layers.unwrap_or_default(); let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples()) | MeshPipelineKey::from_hdr(view.hdr); @@ -411,7 +411,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(render_layers) { continue; } diff --git a/crates/bevy_pbr/src/light/mod.rs b/crates/bevy_pbr/src/light/mod.rs index 0cbe8afab8..00bf0fa81d 100644 --- a/crates/bevy_pbr/src/light/mod.rs +++ b/crates/bevy_pbr/src/light/mod.rs @@ -26,6 +26,7 @@ use crate::*; mod ambient_light; pub use ambient_light::AmbientLight; + mod point_light; pub use point_light::PointLight; mod spot_light; @@ -1018,7 +1019,7 @@ pub(crate) fn directional_light_order( .then_with(|| entity_1.cmp(entity_2)) // stable } -#[derive(Clone, Copy)] +#[derive(Clone)] // data required for assigning lights to clusters pub(crate) struct PointLightAssignmentData { entity: Entity, @@ -1108,7 +1109,7 @@ pub(crate) fn assign_lights_to_clusters( shadows_enabled: point_light.shadows_enabled, range: point_light.range, spot_light_angle: None, - render_layers: maybe_layers.copied().unwrap_or_default(), + render_layers: maybe_layers.unwrap_or_default().clone(), } }, ), @@ -1125,7 +1126,7 @@ pub(crate) fn assign_lights_to_clusters( 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: maybe_layers.unwrap_or_default().clone(), } }, ), @@ -1199,7 +1200,7 @@ pub(crate) fn assign_lights_to_clusters( mut visible_lights, ) in &mut views { - let view_layers = maybe_layers.copied().unwrap_or_default(); + let view_layers = maybe_layers.unwrap_or_default(); let clusters = clusters.into_inner(); if matches!(config, ClusterConfig::None) { @@ -1926,7 +1927,7 @@ pub fn check_light_mesh_visibility( continue; } - let view_mask = maybe_view_mask.copied().unwrap_or_default(); + let view_mask = maybe_view_mask.unwrap_or_default(); for ( entity, @@ -1942,8 +1943,8 @@ pub fn check_light_mesh_visibility( continue; } - let entity_mask = maybe_entity_mask.copied().unwrap_or_default(); - if !view_mask.intersects(&entity_mask) { + let entity_mask = maybe_entity_mask.unwrap_or_default(); + if !view_mask.intersects(entity_mask) { continue; } @@ -2016,7 +2017,7 @@ pub fn check_light_mesh_visibility( continue; } - let view_mask = maybe_view_mask.copied().unwrap_or_default(); + let view_mask = maybe_view_mask.unwrap_or_default(); let light_sphere = Sphere { center: Vec3A::from(transform.translation()), radius: point_light.range, @@ -2036,8 +2037,8 @@ pub fn check_light_mesh_visibility( continue; } - let entity_mask = maybe_entity_mask.copied().unwrap_or_default(); - if !view_mask.intersects(&entity_mask) { + let entity_mask = maybe_entity_mask.unwrap_or_default(); + if !view_mask.intersects(entity_mask) { continue; } @@ -2091,7 +2092,7 @@ pub fn check_light_mesh_visibility( continue; } - let view_mask = maybe_view_mask.copied().unwrap_or_default(); + let view_mask = maybe_view_mask.unwrap_or_default(); let light_sphere = Sphere { center: Vec3A::from(transform.translation()), radius: point_light.range, @@ -2111,8 +2112,8 @@ pub fn check_light_mesh_visibility( continue; } - let entity_mask = maybe_entity_mask.copied().unwrap_or_default(); - if !view_mask.intersects(&entity_mask) { + let entity_mask = maybe_entity_mask.unwrap_or_default(); + if !view_mask.intersects(entity_mask) { continue; } diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 8248b93dc7..ec654c22dd 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -173,7 +173,7 @@ pub struct GpuDirectionalLight { num_cascades: u32, cascades_overlap_proportion: f32, depth_texture_base_index: u32, - render_layers: u32, + skip: u32, } // NOTE: These must match the bit flags in bevy_pbr/src/render/mesh_view_types.wgsl! @@ -488,7 +488,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: maybe_layers.unwrap_or_default().clone(), }, render_visible_entities, )); @@ -684,7 +684,12 @@ pub fn prepare_lights( mut global_light_meta: ResMut, mut light_meta: ResMut, views: Query< - (Entity, &ExtractedView, &ExtractedClusterConfig), + ( + Entity, + &ExtractedView, + &ExtractedClusterConfig, + Option<&RenderLayers>, + ), With>, >, ambient_light: Res, @@ -904,6 +909,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 +924,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 +936,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, maybe_layers) in &views { let point_light_depth_texture = texture_cache.get( &render_device, TextureDescriptor { @@ -1128,11 +1134,25 @@ pub fn prepare_lights( // directional lights let mut directional_depth_texture_array_index = 0u32; + let view_layers = maybe_layers.unwrap_or_default(); 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 !view_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) diff --git a/crates/bevy_pbr/src/render/mesh_view_types.wgsl b/crates/bevy_pbr/src/render/mesh_view_types.wgsl index 76e43eed2d..1b5d1b9076 100644 --- a/crates/bevy_pbr/src/render/mesh_view_types.wgsl +++ b/crates/bevy_pbr/src/render/mesh_view_types.wgsl @@ -33,7 +33,7 @@ struct DirectionalLight { num_cascades: u32, cascades_overlap_proportion: f32, depth_texture_base_index: u32, - render_layers: u32, + skip: 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 4bcbaabfac..f816c60436 100644 --- a/crates/bevy_pbr/src/render/pbr_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_functions.wgsl @@ -398,10 +398,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/Cargo.toml b/crates/bevy_render/Cargo.toml index 7b0511dbd2..a22597b662 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -100,7 +100,7 @@ profiling = { version = "1", features = [ ], optional = true } async-channel = "2.2.0" nonmax = "0.5" -smallvec = "1.11" +smallvec = { version = "1.11", features = ["const_new"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] # Omit the `glsl` feature in non-WebAssembly by default. diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index 0a028be4ce..d0d9a4a6e1 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -926,7 +926,7 @@ pub fn extract_cameras( } if let Some(render_layers) = render_layers { - commands.insert(*render_layers); + commands.insert(render_layers.clone()); } if let Some(perspective) = projection { diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs index 2c7e4c9e04..0a6dcc5e00 100644 --- a/crates/bevy_render/src/view/mod.rs +++ b/crates/bevy_render/src/view/mod.rs @@ -418,7 +418,6 @@ pub struct ViewUniform { frustum: [Vec4; 6], color_grading: ColorGradingUniform, mip_bias: f32, - render_layers: u32, } #[derive(Resource)] @@ -715,7 +714,6 @@ pub fn prepare_view_uniforms( Option<&Frustum>, Option<&TemporalJitter>, Option<&MipBias>, - Option<&RenderLayers>, )>, ) { let view_iter = views.iter(); @@ -727,16 +725,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; @@ -779,7 +768,6 @@ pub fn prepare_view_uniforms( frustum, color_grading: extracted_view.color_grading.clone().into(), 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 6cf0b75a48..4537a09428 100644 --- a/crates/bevy_render/src/view/view.wgsl +++ b/crates/bevy_render/src/view/view.wgsl @@ -28,5 +28,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 f23a01e441..b05451b8ca 100644 --- a/crates/bevy_render/src/view/visibility/mod.rs +++ b/crates/bevy_render/src/view/visibility/mod.rs @@ -429,7 +429,7 @@ pub fn check_visibility( continue; } - let view_mask = maybe_view_mask.copied().unwrap_or_default(); + let view_mask = maybe_view_mask.unwrap_or_default(); visible_aabb_query.par_iter_mut().for_each_init( || thread_queues.borrow_local_mut(), @@ -451,8 +451,8 @@ pub fn check_visibility( return; } - let entity_mask = maybe_entity_mask.copied().unwrap_or_default(); - if !view_mask.intersects(&entity_mask) { + let entity_mask = maybe_entity_mask.unwrap_or_default(); + if !view_mask.intersects(entity_mask) { return; } diff --git a/crates/bevy_render/src/view/visibility/render_layers.rs b/crates/bevy_render/src/view/visibility/render_layers.rs index 2a0e5da7d8..9cfcb0798d 100644 --- a/crates/bevy_render/src/view/visibility/render_layers.rs +++ b/crates/bevy_render/src/view/visibility/render_layers.rs @@ -1,11 +1,12 @@ use bevy_ecs::prelude::{Component, ReflectComponent}; use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::Reflect; +use smallvec::SmallVec; -type LayerMask = u32; +pub const DEFAULT_LAYERS: &RenderLayers = &RenderLayers::layer(0); /// An identifier for a rendering layer. -pub type Layer = u8; +pub type Layer = usize; /// Describes which rendering layers an entity belongs to. /// @@ -20,9 +21,15 @@ pub type Layer = u8; /// An entity with this component without any layers is invisible. /// /// Entities without this component belong to layer `0`. -#[derive(Component, Copy, Clone, Reflect, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Component, Clone, Reflect, PartialEq, Eq, PartialOrd, Ord)] #[reflect(Component, Default, PartialEq)] -pub struct RenderLayers(LayerMask); +pub struct RenderLayers(SmallVec<[u64; 1]>); + +impl Default for &RenderLayers { + fn default() -> Self { + DEFAULT_LAYERS + } +} impl std::fmt::Debug for RenderLayers { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -41,27 +48,25 @@ impl FromIterator for RenderLayers { impl Default for RenderLayers { /// By default, this structure includes layer `0`, which represents the first layer. fn default() -> Self { - RenderLayers::layer(0) + let (_, bit) = Self::layer_info(0); + RenderLayers(SmallVec::from_const([bit])) } } impl RenderLayers { - /// The total number of layers supported. - pub const TOTAL_LAYERS: usize = std::mem::size_of::() * 8; - /// Create a new `RenderLayers` belonging to the given layer. pub const fn layer(n: Layer) -> Self { - RenderLayers(0).with(n) - } - - /// Create a new `RenderLayers` that belongs to all layers. - pub const fn all() -> Self { - RenderLayers(u32::MAX) + let (buffer_index, bit) = Self::layer_info(n); + assert!( + buffer_index < 1, + "layer is out of bounds for const construction" + ); + RenderLayers(SmallVec::from_const([bit])) } /// Create a new `RenderLayers` that belongs to no layers. pub const fn none() -> Self { - RenderLayers(0) + RenderLayers(SmallVec::from_const([0])) } /// Create a `RenderLayers` from a list of layers. @@ -72,33 +77,28 @@ impl RenderLayers { /// 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`. + /// to multiple rendering layers. #[must_use] - pub const fn with(mut self, layer: Layer) -> Self { - assert!((layer as usize) < Self::TOTAL_LAYERS); - self.0 |= 1 << layer; + pub fn with(mut self, layer: Layer) -> Self { + let (buffer_index, bit) = Self::layer_info(layer); + self.extend_buffer(buffer_index + 1); + self.0[buffer_index] |= bit; self } /// Removes the given rendering layer. - /// - /// # 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); + pub fn without(mut self, layer: Layer) -> Self { + let (buffer_index, bit) = Self::layer_info(layer); + if buffer_index < self.0.len() { + self.0[buffer_index] &= !bit; + } self } /// 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)) + pub fn iter(&self) -> impl Iterator + '_ { + self.0.iter().copied().zip(0..).flat_map(Self::iter_layers) } /// Determine if a `RenderLayers` intersects another. @@ -108,40 +108,95 @@ impl 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 + // Check for the common case where the view layer and entity layer + // both point towards our default layer. + if self.0.as_ptr() == other.0.as_ptr() { + return true; + } + + for (self_layer, other_layer) in self.0.iter().zip(other.0.iter()) { + if (*self_layer & *other_layer) != 0 { + return true; + } + } + + false } /// get the bitmask representation of the contained layers - pub fn bits(&self) -> u32 { - self.0 + pub fn bits(&self) -> &[u64] { + self.0.as_slice() + } + + const fn layer_info(layer: usize) -> (usize, u64) { + 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.0.len(), other_len); + self.0.reserve_exact(new_size - self.0.len()); + self.0.resize(new_size, 0u64); + } + + fn iter_layers(buffer_and_offset: (u64, usize)) -> impl Iterator + 'static { + let (mut buffer, mut layer) = buffer_and_offset; + layer *= 64; + std::iter::from_fn(move || { + if buffer == 0 { + return None; + } + let next = buffer.trailing_zeros() + 1; + buffer >>= next; + layer += next as usize; + Some(layer - 1) + }) } } #[cfg(test)] mod rendering_mask_tests { use super::{Layer, RenderLayers}; + use smallvec::SmallVec; #[test] fn rendering_mask_sanity() { + let layer_0 = RenderLayers::layer(0); + assert_eq!(layer_0.0.len(), 1, "layer 0 is one buffer"); + assert_eq!(layer_0.0[0], 1, "layer 0 is mask 1"); + let layer_1 = RenderLayers::layer(1); + assert_eq!(layer_1.0.len(), 1, "layer 1 is one buffer"); + assert_eq!(layer_1.0[0], 2, "layer 1 is mask 2"); + let layer_0_1 = RenderLayers::layer(0).with(1); + assert_eq!(layer_0_1.0.len(), 1, "layer 0 + 1 is one buffer"); + assert_eq!(layer_0_1.0[0], 3, "layer 0 + 1 is mask 3"); + let layer_0_1_without_0 = layer_0_1.without(0); assert_eq!( - RenderLayers::TOTAL_LAYERS, - 32, - "total layers is what we think it is" + layer_0_1_without_0.0.len(), + 1, + "layer 0 + 1 - 0 is one buffer" ); - 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!(layer_0_1_without_0.0[0], 2, "layer 0 + 1 - 0 is mask 2"); + let layer_0_2345 = RenderLayers::layer(0).with(2345); + assert_eq!(layer_0_2345.0.len(), 37, "layer 0 + 2345 is 37 buffers"); + assert_eq!(layer_0_2345.0[0], 1, "layer 0 + 2345 is mask 1"); assert_eq!( - RenderLayers::layer(0).with(1).without(0).0, - 2, - "layer 0 + 1 - 0 is mask 2" + layer_0_2345.0[36], 2199023255552, + "layer 0 + 2345 is mask 2199023255552" + ); + assert!( + layer_0_2345.intersects(&layer_0), + "layer 0 + 2345 intersects 0" ); assert!( RenderLayers::layer(1).intersects(&RenderLayers::layer(1)), "layers match like layers" ); assert!( - RenderLayers::layer(0).intersects(&RenderLayers(1)), + RenderLayers::layer(0).intersects(&RenderLayers(SmallVec::from_const([1]))), "a layer of 0 means the mask is just 1 bit" ); @@ -162,7 +217,7 @@ mod rendering_mask_tests { "masks with differing layers do not match" ); assert!( - !RenderLayers(0).intersects(&RenderLayers(0)), + !RenderLayers::none().intersects(&RenderLayers::none()), "empty masks don't match" ); assert_eq!( @@ -182,5 +237,10 @@ mod rendering_mask_tests { >::from_iter(vec![0, 1, 2]), "from_layers and from_iter are equivalent" ); + + let tricky_layers = vec![0, 5, 17, 55, 999, 1025, 1026]; + let layers = RenderLayers::from_layers(&tricky_layers); + let out = layers.iter().collect::>(); + assert_eq!(tricky_layers, out, "tricky layers roundtrip"); } } diff --git a/examples/3d/render_to_texture.rs b/examples/3d/render_to_texture.rs index b468347831..e2597fd4f8 100644 --- a/examples/3d/render_to_texture.rs +++ b/examples/3d/render_to_texture.rs @@ -82,19 +82,17 @@ fn setup( ..default() }, FirstPassCube, - first_pass_layer, + first_pass_layer.clone(), )); - // 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(), + first_pass_layer.clone(), )); commands.spawn(( @@ -136,6 +134,17 @@ fn setup( MainPassCube, )); + // Light + // NOTE: we add the light to both 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. + commands.spawn(( + PointLightBundle { + transform: Transform::from_translation(Vec3::new(0.0, 0.0, 10.0)), + ..default() + }, + RenderLayers::layer(0).with(1), + )); // The main pass camera. commands.spawn(Camera3dBundle { transform: Transform::from_xyz(0.0, 0.0, 15.0).looking_at(Vec3::ZERO, Vec3::Y),