Skip to content

Commit

Permalink
#12502 Remove limit on RenderLayers. (#13317)
Browse files Browse the repository at this point in the history
# Objective

Remove the limit of `RenderLayer` by using a growable mask using
`SmallVec`.

Changes adopted from @UkoeHB's initial PR here
#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 <[email protected]>
  • Loading branch information
tychedelia and robtfm committed May 16, 2024
1 parent 05e2552 commit 4c3b767
Show file tree
Hide file tree
Showing 16 changed files with 202 additions and 106 deletions.
19 changes: 19 additions & 0 deletions benches/benches/bevy_render/render_layers.rs
Original file line number Diff line number Diff line change
@@ -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);
6 changes: 3 additions & 3 deletions crates/bevy_dev_tools/src/ui_debug_overlay/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -101,15 +101,15 @@ fn update_debug_camera(
},
..default()
},
LAYOUT_DEBUG_LAYERS,
LAYOUT_DEBUG_LAYERS.clone(),
DebugOverlayCamera,
Name::new("Layout Debug Camera"),
))
.id()
};
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 {
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_gizmos/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
}
}
}
8 changes: 4 additions & 4 deletions crates/bevy_gizmos/src/pipeline_2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
}

Expand Down
8 changes: 4 additions & 4 deletions crates/bevy_gizmos/src/pipeline_3d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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;
}

Expand Down Expand Up @@ -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);
Expand All @@ -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;
}

Expand Down
27 changes: 14 additions & 13 deletions crates/bevy_pbr/src/light/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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(),
}
},
),
Expand All @@ -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(),
}
},
),
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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,
Expand All @@ -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;
}

Expand Down Expand Up @@ -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,
Expand All @@ -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;
}

Expand Down Expand Up @@ -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,
Expand All @@ -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;
}

Expand Down
32 changes: 26 additions & 6 deletions crates/bevy_pbr/src/render/light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!
Expand Down Expand Up @@ -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,
));
Expand Down Expand Up @@ -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>,
Expand Down Expand Up @@ -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
Expand All @@ -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;
Expand All @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_pbr/src/render/mesh_view_types.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 3 additions & 3 deletions crates/bevy_pbr/src/render/pbr_functions.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_render/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_render/src/camera/camera.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
14 changes: 1 addition & 13 deletions crates/bevy_render/src/view/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,6 @@ pub struct ViewUniform {
frustum: [Vec4; 6],
color_grading: ColorGradingUniform,
mip_bias: f32,
render_layers: u32,
}

#[derive(Resource)]
Expand Down Expand Up @@ -715,7 +714,6 @@ pub fn prepare_view_uniforms(
Option<&Frustum>,
Option<&TemporalJitter>,
Option<&MipBias>,
Option<&RenderLayers>,
)>,
) {
let view_iter = views.iter();
Expand All @@ -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;
Expand Down Expand Up @@ -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(),
}),
};

Expand Down
1 change: 0 additions & 1 deletion crates/bevy_render/src/view/view.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,4 @@ struct View {
frustum: array<vec4<f32>, 6>,
color_grading: ColorGrading,
mip_bias: f32,
render_layers: u32,
};

0 comments on commit 4c3b767

Please sign in to comment.