-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Unify RenderLayers and TargetCamera (Part 1: RenderGroups) [ADOPT ME] #12502
base: main
Are you sure you want to change the base?
Conversation
I want to point out that there's a couple other options here - not that they are necessarily better. One option is to combine the 'inherited' status as a boolean property within the You could take that further and have the property be an enum: enum RenderGroupInheritance {
/// This render group was calculated automatically
Implicit = 0,
/// This render group was explicitly set on the entity, but should not cascade to descendants
Explicit,
/// This render group has been explicitly set on the entity, and should cascade to descendants
ExplicitPropagating
} This means reducing the total number of components involved on each entity. Whether that is an optimization or de-optimization, I don't know. It also raises the question: if an explicit propagating entity has an explicit non-propagating child, what should the render groups for its grandchild be? |
Working on the implementation for propagating render groups now. I think I can manage the following rules without needing to change the time complexity of existing code:
Allowing It is also possible, although I think probably better for a follow-up PR, to allow a component
EDIT: Given the complexity of the propagation algorithm for this PR, I doubt |
Something that would be helpful, I think, is a high-level description of the propagation algorithm. For example, the stylesheet propagator that I wrote a few months ago could be described by the following summary: "Starting with a query of all UI entities that can be styled, start by identifying the roots and then recursively visit all descendants accessible via the query. Each visited node calculates the composition of the inherited style with the style defined on that node, if any. Mutations to the state of each node are applied differentially to minimize change detection signals." |
I wrote a description and notes at the top of the |
That looks really good. Part of the motivation for wanting this description is to be able to make an argument as to why this approach is better than alternatives (such as an exclusive system with world access, or a giant query with a bunch of optional components which are then accessed via entity id instead of sequential scanning), both of which are obvious patterns for hierarchical traversals. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great! Thanks for tackling this, it's going to be a huge help.
Regarding your open questions:
How to put reasonable limits on RenderLayer?
I think, at best, we should warn. My use case is a little niche because I'm allowing users to spawn cameras at will, but I'd rather have them work around poor performance than be arbitrarily limited. In my case, the allocations from going about 64 layers are unlikely to matter, especially compared to whatever other lurking inefficiencies there are in the renderer with having so many cameras.
This PBR shader uses render layers internally (GpuDirectionalLight and ViewUniform and DirectionalLight and View).
I commented on the issue here #12468 (comment), but I think the preference is to remove RenderLayers
from the shader code entirely and compute light visibility on the cpu as property of the view. We could include layers as a dynamic array on the view itself, but iirc there were problems including the layers on the light struct as they're already nested inside another array.
/// A default `CameraView` will include the [`DEFAULT_RENDER_LAYER`]. | ||
#[derive(Component, Debug, Clone, PartialEq, Reflect)] | ||
#[reflect(Component, Default, PartialEq)] | ||
pub struct CameraView { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: I can't decide if "view" is too overloaded here. I totally understand the description of the component as "things I can see", but might get confused in it's relation to something like ExtractedView
in render world.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah it's pretty overloaded, but it feels really comfortable for this use-case.
crates/bevy_pbr/src/render/light.rs
Outdated
for (light_index, &(light_entity, light)) in directional_lights | ||
.iter() | ||
.enumerate() | ||
.take(directional_shadow_enabled_count) | ||
.take(MAX_DIRECTIONAL_LIGHTS) | ||
{ | ||
let gpu_light = &mut gpu_lights.directional_lights[light_index]; | ||
|
||
// Check if the light intersects with the view. | ||
if extracted_render_groups.intersects(&light.render_groups) { | ||
gpu_light.skip = 1u32; | ||
continue; | ||
} | ||
|
||
// Only deal with cascades when shadows are enabled. | ||
if (gpu_light.flags & DirectionalLightFlags::SHADOWS_ENABLED.bits()) == 0u32 { | ||
continue; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changing this may invalidate directional_depth_texture_array_index
, someone who understands this needs to check.
Ok the check has been moved to the CPU code. I added a |
Added a warning at 1024 layers, and a panic failsafe if you exceed 1mill, which would be 24MB per RenderLayers struct. |
@alice-i-cecile @UkoeHB I tried to make an example, but I couldn't get it to work - possibly I am doing something wrong. Here's what I did:
At this point I would have expected the second model not to be visible. However, both models were visible. |
A default RenderGroups starts with the default render layer. Use |
If I try
|
I changed the camera to |
Can you share your code? Maybe in a gist. If there is a bug then I will fix it ASAP. |
Start with the existing fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn((
DirectionalLightBundle {
transform: Transform::from_xyz(4.0, 25.0, 8.0).looking_at(Vec3::ZERO, Vec3::Y),
directional_light: DirectionalLight {
shadows_enabled: true,
..default()
},
..default()
},
RenderGroups::default().add(1).clone(),
));
commands.spawn((
Camera3dBundle {
transform: Transform::from_xyz(-0.5, 0.9, 1.5)
.looking_at(Vec3::new(-0.5, 0.3, 0.0), Vec3::Y),
..default()
},
EnvironmentMapLight {
diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),
specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
intensity: 1500.0,
},
CameraView::default().add(1).clone(),
));
// Spawn the scene as a child of this entity at the given transform
commands.spawn((
SceneBundle {
transform: Transform::from_xyz(-1.0, 0.0, 0.0),
scene: asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0"),
..default()
},
RenderGroups::default().add(1).clone(),
));
// Spawn a second scene, and add a tag component to be able to target it later
commands.spawn((
SceneBundle {
scene: asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0"),
..default()
},
MovedScene,
RenderGroups::default().add(2).clone(),
));
} |
The generated |
You added a new example but didn't add metadata for it. Please update the root Cargo.toml file. |
…ts can see a mesh but the camera can't see the lights
…ghts now intersect entities again but warn if they have more than one RenderLayer
Changing this to ADOPT ME status since I don't have time or motivation to continue working on it. |
Opened a PR to port removing the layer limit (which seemed less controversial) #13317 |
# 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]>
@tychedelia doc for RenderLayers still contains mention of |
@alice-i-cecile Sorry for the annoying ping, may I adopt this? I am not sure if this is still ongoing. I literally made my rtts a few x transforms away from my game. It is driving me insane the ugly code |
@Sirmadeira this PR originally died because one of the rendering SMEs blocked it. You'll want to get consensus/full approval from the rendering guys before putting any work in. |
Yep, I'd like to see this work in some form, but I definitely recommend building consensus first. |
@UkoeHB If you dont mind me asking what was the reason for the block? |
Something about it being too complicated or 'we don't really need this'. If you can present a compelling case then it might get enough support from the SMEs. |
Originally, there were three different users pushing for this, each with their own use case:
|
Objective
RenderLayers
andTargetCamera
and provide a single way to configure which camera entities are rendered to #12468.Solution
See changelog. This is Part 1 of a 2-part rework. Part 2 will update UI to remove
TargetCamera
and use affiliated cameras instead.Part 2 planned changelog:
TargetCamera
.PropagateRenderGroups::Camera
by default on UI cameras. This will set the camera affiliation inInheritedRenderGroups
equal to the UI cameras (unless an entity has aRenderGroups
component with a manually-specified camera affiliation, which will take precedence).Part 2 planned migration:
TargetCamera
components that you added manually to cameras withPropagateRenderGroups::Camera
components. If those cameras need to see all 'default render layer' entities, then also add aCameraView::default()
component.Changelog
RenderGroups
component that merges the functionality ofRenderLayers
andTargetCamera
.RenderLayers
(reworked) and an optional 'affiliated camera'.RenderLayers
to store a growable bitmask, instead of being fixed to 32 bits. It can now store up to 64 render layers on the stack (including the default render layer), and then will allocate for more bits.Layer
withRenderLayer
as a user-facing abstraction for interacting withRenderLayers
. ARenderLayer
is a bit index, equivalent to the existingLayer
type.DEFAULT_RENDER_LAYER = RenderLayer(0)
. By default all entities and cameras withoutRenderGroups
orCameraView
components will be in the default layer.CameraView
as a camera-specific component for controlling whichRenderLayers
are visible to the camera. Cameras automatically see any entity with aRenderGroups
component that has a camera affiliation equal to the given camera.PropagateRenderGroups
component for controlling howRenderGroups
are propagated down hierarchies.InheritedRenderGroups
component that mergesRenderGroups
components with propagatedRenderGroups
. This component is managed automatically inPropagateRenderGroupsSet
. It is used internally for rendering, and can be queried by users to e.g. debug visibility issues.PropagateRenderGroupsSet
, which runs in theVisibilityPropagate
set.Migration Guide
Layer
withRenderLayer
.RenderLayers
components withRenderGroups
components on non-camera entities.RenderLayers
components withCameraView
components on camera entities.TODOs
GpuDirectionalLight
andViewUniform
andDirectionalLight
andView
). Now that it is a growable bitset, this doesn't work unless we set an upper cap on bits and pass in an array of ints for the flags.prepare_lights
inbevy_pbr
is cloningExtractedDirectionalLight
, which contains aRenderGroups
. This makes it potentially-allocating due to the growable bitset. There is also a clone when extracting from the world (also forViewUniform
).pixel_grid_snap
,render_to_texture
examples.Follow-up
TargetCamera
and use affiliated cameras in for UI instead.GizmoConfig
to support displaying different gizmos in different cameras. This may require a deeper rethink of how gizmos are designed, since they are currently special-cased.