mirror of
https://github.com/bevyengine/bevy
synced 2024-11-21 20:23:28 +00:00
# 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>
This commit is contained in:
parent
05e2552a68
commit
4c3b7679ec
16 changed files with 202 additions and 106 deletions
19
benches/benches/bevy_render/render_layers.rs
Normal file
19
benches/benches/bevy_render/render_layers.rs
Normal file
|
@ -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);
|
|
@ -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::<UiGizmosDebug>()) {
|
||||
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 {
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<GlobalLightMeta>,
|
||||
mut light_meta: ResMut<LightMeta>,
|
||||
views: Query<
|
||||
(Entity, &ExtractedView, &ExtractedClusterConfig),
|
||||
(
|
||||
Entity,
|
||||
&ExtractedView,
|
||||
&ExtractedClusterConfig,
|
||||
Option<&RenderLayers>,
|
||||
),
|
||||
With<SortedRenderPhase<Transparent3d>>,
|
||||
>,
|
||||
ambient_light: Res<AmbientLight>,
|
||||
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(),
|
||||
}),
|
||||
};
|
||||
|
||||
|
|
|
@ -28,5 +28,4 @@ struct View {
|
|||
frustum: array<vec4<f32>, 6>,
|
||||
color_grading: ColorGrading,
|
||||
mip_bias: f32,
|
||||
render_layers: u32,
|
||||
};
|
||||
|
|
|
@ -429,7 +429,7 @@ pub fn check_visibility<QF>(
|
|||
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<QF>(
|
|||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Layer> 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::<LayerMask>() * 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<Item = Layer> {
|
||||
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<Item = Layer> + '_ {
|
||||
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<Item = Layer> + '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 {
|
|||
<RenderLayers as FromIterator<Layer>>::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::<Vec<_>>();
|
||||
assert_eq!(tricky_layers, out, "tricky layers roundtrip");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
Loading…
Reference in a new issue