Skip to content

Make light extraction retained, and clean up lights that became newly invisible.#22857

Merged
superdump merged 7 commits intobevyengine:mainfrom
pcwalton:retained-lights
Feb 9, 2026
Merged

Make light extraction retained, and clean up lights that became newly invisible.#22857
superdump merged 7 commits intobevyengine:mainfrom
pcwalton:retained-lights

Conversation

@pcwalton
Copy link
Copy Markdown
Contributor

@pcwalton pcwalton commented Feb 7, 2026

The lighting code is designed such that lights that aren't visible in any view shouldn't exist in the render world. Unfortunately, when the render world was made retained, the light extraction code was never fully updated to clean up components corresponding to lights that became invisible. So Bevy is currently not enforcing that invariant. This causes many_lights to become slower over time as more lights enter the view and are extracted to the render world, while lights outside the view aren't removed.

This PR fixes the issue in two ways:

  1. Light extraction now properly accounts for the retained render world, following the patterns that other objects like meshes use. Lights that haven't changed from the previous frame aren't re-extracted and are retained from frame to frame.

  2. Visibility of lights and other clustered objects is now determined by the same system that determines visibility of meshes. The GlobalVisibleClusterableObjects side table is gone. To do this, I made the existing Sphere type into a component that can be added in lieu of Aabb to entities that are culled using spheres instead of AABBs. This was surprisingly straightforward to add to the visibility code, as it was already using spheres for quick rejection.

In addition to being a simplification, this PR increases the FPS of many_lights from around 30 FPS to around 100 FPS on my Ryzen 9 8945HS, moving it from CPU bound to strongly GPU bound. The profile was dominated by extract_lights and prepare_lights before. Retained mode drops extract_lights time from 8.9 ms plus 8.0 ms of commands processing time to 0.4 ms, and prepare_lights time drops to approximately 1.2 ms per frame (which further drops to 0.9 ms with my extra PR #22846 applied).

many_lights on main:
Screenshot 2026-02-07 101916

many_lights in this PR:
Screenshot 2026-02-07 101210

Comparison of prepare_lights between main and this PR for many_lights:
Screenshot 2026-02-07 101234

Comparison of extract_lights between main and this PR for many_lights:
Screenshot 2026-02-07 101248

invisible.

The lighting code is designed such that lights that aren't visible in
any view shouldn't exist in the render world. Unfortunately, when the
render world was made retained, the light extraction code was never
fully updated to clean up components corresponding to lights that became
invisible. So Bevy is currently not enforcing that invariant. This
causes `many_lights` to become slower over time as more lights enter the
view and are extracted to the render world, while lights outside the
view aren't removed.

This PR fixes the issue in two ways:

1. Light extraction now properly accounts for the retained render world,
   following the patterns that other objects like meshes use. Lights
   that haven't changed from the previous frame aren't re-extracted and
   are retained from frame to frame.

2. Visibility of lights and other clustered objects is now determined by
   the same system that determines visibility of meshes. The
   `GlobalVisibleClusterableObjects` side table is gone. To do this, I
   made the existing `Sphere` type into a component that can be added in
   lieu of `Aabb` to entities that are culled using spheres instead of
   AABBs.  This was surprisingly straightforward to add to the
   visibility code, as it was already using spheres for quick rejection.

In addition to being a simplification, this PR increases the FPS of
`many_lights` from around 30 FPS to around 100 FPS on my Ryzen 9 8945HS,
moving it from CPU bound to strongly GPU bound. The profile was
dominated by `extract_lights` and `prepare_lights` before. Retained mode
drops `extract_lights` time from 8.9 ms plus 8.0 ms of commands
processing time to 0.4 ms, and `prepare_lights` time drops to
approximately 1.2 ms per frame (which further drops to 0.9 ms with my
extra PR bevyengine#22846 applied).
@pcwalton pcwalton added the A-Rendering Drawing game state to the screen label Feb 7, 2026
@pcwalton pcwalton added C-Performance A change motivated by improving speed, memory usage or compile times P-Regression Functionality that used to work but no longer does. Add a test for this! C-Bug An unexpected or incorrect behavior labels Feb 7, 2026
@alice-i-cecile alice-i-cecile added this to the 0.18.1 milestone Feb 7, 2026
Comment thread crates/bevy_light/src/cluster/mod.rs
Copy link
Copy Markdown
Contributor

@IceSentry IceSentry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I started a full example run just to be safe but LGTM

bevyengine/bevy-example-runner#208

Comment thread crates/bevy_light/src/cluster/assign.rs Outdated
let mut update_from_object_intersections =
|visible_clusterable_objects: &mut VisibleClusterableObjects| {
for clusterable_object in &clusterable_objects {
for clusterable_object in &mut clusterable_objects {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the mut here needed? I'm not seeing where it actually gets mutated.

} else if let Some(model_sphere) = maybe_model_sphere
&& !frustum.intersects_sphere(model_sphere, false)
{
// Do sphere-based frustum culling in this case
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe this comment should live up above the conditional like the rest of them? feels weird that doing "sphere based frustum culling in this case" is just an empty return

Changed<OcclusionCulling>,
Changed<SunDisk>,
)>,
),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these queries are getting comically long lol

Co-authored-by: atlv <email@atlasdostal.com>
@superdump superdump enabled auto-merge February 9, 2026 06:41
@superdump superdump added this pull request to the merge queue Feb 9, 2026
Merged via the queue into bevyengine:main with commit d1c3557 Feb 9, 2026
38 checks passed
@mockersf
Copy link
Copy Markdown
Member

This PR caused a spam of error log when running the contact_shadows example

ERROR bevy_pbr::cluster: Clustered light or decal 296v0 had no assigned index!

#22904

@alice-i-cecile alice-i-cecile removed this from the 0.18.1 milestone Mar 2, 2026
@alice-i-cecile
Copy link
Copy Markdown
Member

I could not cleanly cherrypick this; it has been removed from 0.18.1.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Rendering Drawing game state to the screen C-Bug An unexpected or incorrect behavior C-Performance A change motivated by improving speed, memory usage or compile times P-Regression Functionality that used to work but no longer does. Add a test for this!

Projects

No open projects
Status: Done

Development

Successfully merging this pull request may close these issues.

6 participants