#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>
This commit is contained in:
charlotte 2024-05-16 09:15:47 -07:00 committed by GitHub
parent 05e2552a68
commit 4c3b7679ec
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 202 additions and 106 deletions

View 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);

View file

@ -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 {

View file

@ -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(),
}
}
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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)

View file

@ -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;

View file

@ -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;
}

View file

@ -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.

View file

@ -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 {

View file

@ -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(),
}),
};

View file

@ -28,5 +28,4 @@ struct View {
frustum: array<vec4<f32>, 6>,
color_grading: ColorGrading,
mip_bias: f32,
render_layers: u32,
};

View file

@ -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;
}

View file

@ -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");
}
}

View file

@ -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),