Commit Graph

91 Commits

Author SHA1 Message Date
Bevy Auto Releaser a74009b22e chore: Release 2026-06-21 16:55:28 +00:00
Luo Zhihao 7517c61ecd Add options to lower precision/compressed vertex buffer (#21926)
# Objective

Resolves #21902.

## Solution

This PR adopts a relatively transparent approach to reduce the GPU
vertex buffer size. On CPU-side mesh can still use uncompressed Float32
data, and users are not required to insert compressed vertex formats.
The vertex data is automatically processed into
lower-precision/octahedral encoded data when uploading to the GPU.

To enable vertex attribute compression, just set the
`attribute_compression` field of Mesh, or set
`mesh_attribute_compression` of GltfLoaderSettings. If enabled, normal
and tangent will be octahedral encoded Unorm16x2, uv0, uv1, joint weight
and color will be corresponding Unorm16 or Float16. I also provide
Unorm8x4 for vertex color if hdr isn't needed.

Update 2026-2-16

Removed previous approach that automatically compresses vertex buffer
according to flags when uploading to GPU. Instead, I added
`compressed_mesh` method to Mesh to construct compressed Mesh ahead of
time. GltfLoader can also opt-in mesh compressing when loading. I also
add an option to convert indices to u16, though I believe blender gltf
exporter already uses u16 indices when possible.


## Testing

Run `many_cubes`, `many_foxes`, `many_morph_targets` with
`--vertex-compression` to test 3d.
Run `bevymark` with `sprite_mesh` to test 2d, because `SpriteMesh` uses
compressed quad mesh now.

---------

Co-authored-by: Greeble <166992735+greeble-dev@users.noreply.github.com>
2026-06-02 03:22:56 +00:00
DavidCrossman 7c77ecd576 Fix documentation typos (#24446)
# Objective

Fix typos and other small issues in the documentation. I can drop the
changes to `bevy_reflect`'s and `bevy_anti_alias`'s crate descriptions
if it's a problem.
2026-06-02 00:52:42 +00:00
Patrick Walton a0baf1a2c8 Reapply GPU-driven HLOD evaluation, using the new HLOD view origin. (#24343)
This PR undoes the revert of #23115 that was done in #24252. As part of
doing so, it makes GPU-driven visibility range evaluation use the same
semantics, introduced in #24289, as CPU-driven visibility range
evaluation.

The first commit in this PR is the un-revert, and the second commit is
the change to use the new machinery #24289. This means that you can
simply review the second commit (which is very short).

To test, run the `visibility_range` example with `--no-cpu-culling`, and
use the WASD keys to move behind the flight helmet and zoom out, while
closely watching the shadow. Note that the shadow now properly reflects
the LOD of the model.
2026-05-19 00:42:06 +00:00
Patrick Walton 74d39c639e Correctly handle visibility ranges in shadow maps. (#24289)
Right now, visibility ranges are always resolved relative to the view.
This is incorrect for shadow maps in two ways:

1. Visibility ranges for meshes in directional light shadow maps should
be resolved relative to the camera that the cascades are associated
with.

2. Visibility ranges for meshes in point and spot light shadow maps
should be resolved relative to *some* camera.

To properly solve (2), this commit introduces the notion of a *shadow
LOD origin*. The shadow LOD origin is the point that visibility ranges
are relative to, when rendering views not associated with any camera.
Point and spot light shadow maps are currently not associated with a
camera, and therefore we need this extra notion in order to properly
evaluate visibility ranges.

(As a follow-up, we should introduce the notion of *own shadow maps*,
which will allow each camera to have separate shadow maps for point and
spot lights. That feature is however out of scope for *this* patch,
which simply seeks to make the existing semantics consistent.)

A new component, `ShadowLodOrigin`, has been added, which allows the
developer to customize the shadow LOD origin. In the absence of this
component, this PR implements a simple heuristic to determine the shadow
LOD origin: to prefer an origin that coincides with cameras that render
to a window. This heuristic should suffice in the vast majority of
cases, so developers will rarely have to manually use the
`ShadowLodOrigin` component.

A new field, `lod_view_world_position`, has been added to `View` to
supply the position of the shadow LOD origin to the GPU. This is much
simpler than introducing a new uniform or using immediates, as #24197
tried to do.

This commit is the proper fix for #23991. PR #24252 attempted to fix
this problem by reverting #23115. However, this didn't actually fix the
issue, because the semantics were still inconsistent. This commit
constitutes the correct fix for the issue. I verified that, after
un-reverting #23115 on top of this patch and modifying it to use the new
`lod_view_world_position`, that the issue reported in #23991 disappears.
2026-05-18 11:52:40 +00:00
Kevin Chen dc3a6cbb9c Revert "Move visibility range checking from the CPU to the GPU if NoCpuCulling is specified. (#23115) (#24252)
This reverts commit ebfbc3f5c8.

# Objective

- A third PR that fixes #23991 

## Solution

- Reverts the commit that causes the issue.
- This can be considered if the other solutions (#24197 & #24133) I’ve
put up are not satisfactory, and we need more time to come up with a
good solution (i.e. post 0.19 rc) that:
- Implements PointLight and SpotLight shadow view behavior for gpu vis
range culling that is agreed upon and looks good
- Finalizes an appropriate way of sending this camera view world
position data to the gpu (Immediates vs ViewUniformBuffer or other
Uniform)
- Note: Meshes that are tagged with `NoCpuCulling` will **not** undergo
cpu visibility range culling. That was implemented in a separate PR:
https://github.com/bevyengine/bevy/pull/23107/changes#diff-fec33d34072b7b4be336571ceecf2c10fc9bafd8366c1b29531089b7e3ef621cL745-L748.
If you want to use `VisibilityRange` culling, you will still need to
have CPU culling enabled for the mesh.

## Testing

- `cargo run --example visibility_range` works normal now.
- Tested some other 3d examples make sure the pipeline is ok —
atmosphere, pccm
2026-05-13 08:42:41 +00:00
Justace Clutter 15b23b4e4a Add a warning for the use of Viewport with Window targets (#21830)
# Objective

This PR adds a warning in the Viewport portion of the Camera class to
highlight the need to scale to physical pixels. This PR fixes #21828

## Solution

Adds a warning block in the class documentation

## Testing

I build the documentation and it renders to the following:

<img width="827" height="496" alt="Camrea_Viewport_Warning"
src="https://github.com/user-attachments/assets/d81b82c6-565b-450c-89ed-f6643f22ea23"
/>

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: ickshonpe <david.curthoys@googlemail.com>
Co-authored-by: François Mockers <francois.mockers@vleue.com>
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2026-05-06 03:35:37 +00:00
Carter Anderson abc6c9049b Feathers bsn! style improvements (#24063)
Use our "shorthand" conventions for `Val` everywhere in feathers code /
usage

This also:
- adds a `VariantDefaults` derive to `Visibility` to make it usable
without `template_value`
- adds `From<Val>` to BorderRadius to support `px(10)` assignments
instead of `BorderRadius::all(px(10))`

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: ickshonpe <david.curthoys@googlemail.com>
2026-05-04 03:11:02 +00:00
Kevin Chen d6fbec5b5f Deps: bump up wgpu and related deps to 29.0.3 (#24064)
# Objective

- Should fix #23573 - I don’t have DX-12 so I can’t verify this, just
following orders. Someone can verify this is fixed!
## Solution

- Search and replace “29.0.1” with “29.0.3” if it is related to wgpu
(and checking that the version is available on crates)

- Fixes #23220.

## Testing

- Leaning on CI for this one personally
2026-05-03 23:55:33 +00:00
Rostyslav Toch 8edbb7791c Optimize and fix sphere-OBB intersection logic (#23865)
# Objective

- Optimize the Sphere and OOB intersection logic.
- Fix a bug when the sphere and OBB shared the exact same center.

## Solution

- The previous implementation calculated `v / d` (a normalized vector).
If the distance `d` was zero (in a situation when centers are
identical), this resulted in a division by zero. The new approach avoids
normalization by refactoring the comparison.
- Original: $d < r_1 + r_2$ where $r_2 =$
`relative_radius`$\Bigl(\frac{\mathbf{v}}{d}\Bigr)$
- New: $d^2 \leq r \cdot d + r_{\rm unscaled}$ where $r_{\rm unscaled}
=$ `relative_radius`$(\mathbf{v})$

## Testing

- Added new test cases covering identical centers, edge contacts, and
zero-extent volumes. These tests fail on main but pass with this PR.
- Verified performance via the newly added camera benchmarks in
https://github.com/bevyengine/bevy/pull/23863

```
intersects_obb/sphere_intersects_obb
                        time:   [5.3677 ns 5.3932 ns 5.4227 ns]
                        change: [−19.462% −18.945% −18.426%] (p = 0.00 < 0.05)
                        Performance has improved.
```
2026-04-28 05:11:23 +00:00
Luo Zhihao dca740eed7 Fix clear color in CompositingSpace::Srgb (#23963)
# Objective

Split off from #23803, Fix `ClearColor` with `CompositingSpace::Srgb`.

## Solution

Convert clear color to Srgb

## Testing

Code from
https://github.com/bevyengine/bevy/issues/9213#issuecomment-2487277956:
```rs
//!

use bevy::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .insert_resource(ClearColor(Color::srgba(0., 0.5, 0., 1.)))
        .add_systems(Startup, |mut commands: Commands| {
            commands.spawn((Camera2d, CompositingSpace::Srgb));

            commands.spawn((
                Sprite::from_color(Color::WHITE.with_alpha(0.5), Vec2::new(512., 512.)),
                Transform::from_xyz(-256., 0., 0.),
            ));

            commands.spawn((
                Sprite::from_color(Color::BLACK.with_alpha(0.5), Vec2::new(512., 512.)),
                Transform::from_xyz(256., 0., 0.),
            ));
        })
        .run();
}

```

Before:
<img width="1410" height="878" alt="屏幕截图_20260414_120259"
src="https://github.com/user-attachments/assets/199e2832-d0ba-46fe-835a-8775c2a4adc6"
/>

After:
<img width="1410" height="878" alt="屏幕截图_20260414_115624"
src="https://github.com/user-attachments/assets/f4b26170-3a78-44f1-917d-8f061da83ad5"
/>
2026-04-24 04:35:53 +00:00
atlv f74702a430 misc cleanups (#23736)
# Objective

- ManualTextureView has no reason for being a Component

## Solution

- Make it not be a component

## Testing

- it compiles
2026-04-12 20:37:35 +00:00
Carter Anderson d2af4a565d Add FromTemplate derives to Components using Handle (#23696)
# Objective

A lot of Bevy's built in types don't derive FromTemplate, which is what
makes things like asset handles "templatable" in BSN:

```rust
bsn! {
  ImageNode { image: "path_to_image.png" }
}
```

## Solution

Derive `FromTemplate` for components that have handles.

In cases of fields with `Option<SOME_TEMPLATED_TYPE>`, I've added the
`#[template(OptionTemplate<XTemplate>)]` attribute, as these cases can't
use the blanket `Default + Clone` template implementation (which doesn't
do templating on the internal type). Later I'm likely to propose
something like `#[template(built_in)]` for standard "collection types"
to make this a little less boilerplatey.
2026-04-07 01:45:51 +00:00
Máté Homolya 5794b22aba Non-linear color spaces for compositing 2d sprites (#23049)
# Objective

Currently Bevy's alpha compositing uses Linear color space during
rendering. This PR introduces a new component on the camera that allows
users to customize what color space the application should use for
compositing 2d sprites.

In a future PR, we could introduce the same idea for 3d render pipeline.
While linear space is most commmonly used for physical correctness,
Oklab would improve blending of transparent 3D objects (glass,
particles) in the same way it does for sprites. that is: perceptual
uniformity for partially transparent , saturated colors.

Addresses #9213 when using `CompositingSpace::Srgb`

## Solution

- The solution involves adding a new `CompositingSpace` component on the
`Camera`, that allows users to specify which space to use for
alpha-compositing. This defaults to Linear (same as before!) avoiding
breaking changes.
- in the Blit pipeline, added flags to convert from oklab/srgb back to
linear, this is because typical swapchain formats (for example
`Bgra8UnormSrgb`) expect linear RGB.
- `bevy_ui_render` now uses `bevy_render::color_operations` instead of a
duplicate `color_space.wgsl`.
- The main view texture clear color is now converted to match the
compositing space before being passed to the renderer. Previously it was
always treated as linear RGB.

## Testing

- tested `sprite` example compared to a manually composited reference
image using the `coloraide` python library.
- Ran `gradients`, `feathers` and `stacked_gradients` after refactor

---

## Showcase

Component usage
```rs
// sRGB compositing: matches many image editors
commands.spawn((Camera2d, CompositingSpace::Srgb));

// Linear compositing
commands.spawn((Camera2d, Hdr, CompositingSpace::Linear));

// Oklab compositing: perceptually uniform blending
commands.spawn((Camera2d, Hdr, CompositingSpace::Oklab));
```

Example screenshots show reference image "ground truth" generated using
python / `coloraide` (left) and the image produced by bevy's renderer.

Currently the broken behavior reported by #9213 evidenced by this image
where the reference was composited in srgb space - similar to what DCCs
would produce while the right image is composited in linear space by
Bevy:

<img width="752" height="420" alt="Screenshot 2026-02-18 at 5 30 55 PM"
src="https://github.com/user-attachments/assets/2ac00e26-6b49-41c9-9ea1-df49cc70f9f4"
/>

After switching to `CompositingSpace::Srgb`

<img width="739" height="410" alt="Screenshot 2026-02-18 at 5 30 29 PM"
src="https://github.com/user-attachments/assets/9fe6b67f-bff7-41c9-bb0a-45943c68a137"
/>

Further, to solidify the use-case of the `CompositingSpace` enum this
next showcase if for compositing in a perceptually uniform color space,
OkLAB. The first image shows the default/baseline Srgb composite
produced by most DCCs. (generally incorrect! ) left:okLAB reference from
coloraide, right: srgb compositing in Bevy.
<img width="663" height="361" alt="Screenshot 2026-02-18 at 5 36 29 PM"
src="https://github.com/user-attachments/assets/ffb5ecf4-2401-4b12-a4aa-32a720b3bfb6"
/>

by using the `CompositingSpace::Oklab` we can achieve the same result in
Bevy's renderer.
<img width="699" height="398" alt="Screenshot 2026-02-18 at 5 36 05 PM"
src="https://github.com/user-attachments/assets/d4fdb9cd-9ed5-412e-b705-2121c48a07ba"
/>
2026-03-30 21:03:15 +00:00
Chris Biscardi 422228cf00 bump wgpu dependency to 29.0.1 (#23544)
# Objective

Communicate the fact that we care about not using v29.0.0, since 29.0.1
contains the limits fix discussed in #23277

## Testing

```
cargo run --example occlusion_culling
```

results in no warnings on macos
2026-03-27 22:47:58 +00:00
Chris Biscardi 87e716fcb5 Wgpu 29 (#23277)
wgpu update for v29.

I have tested on macos m1, m5, and windows. Linux testing is
appreciated.

- [x] before merge, naga_oil and dlss_wgpu need to be published, and the
patches referencing their respective PRs removed from the workspace
Cargo.toml

##### other PRs

- naga_oil: https://github.com/bevyengine/naga_oil/pull/134
- dlss_wgpu: https://github.com/bevyengine/dlss_wgpu/pull/27

##### Source of relevant changes

- `Dx12Compiler::DynamicDxc` no longer has `max_shader_model`
    - https://github.com/gfx-rs/wgpu/pull/8607
- `Dx12BackendOptions::force_shader_model` comes from:
    - https://github.com/gfx-rs/wgpu/pull/8984
- Allow optional `RawDisplayHandle` in `InstanceDescriptor`
    - https://github.com/gfx-rs/wgpu/pull/8012
- Add `GlDebugFns` option to disable OpenGL debug functions
    - https://github.com/gfx-rs/wgpu/pull/8931
- Add a DX12 backend option to force a certain shader model
    - https://github.com/gfx-rs/wgpu/pull/8984
- Migrate validation from maxInterStageShaderComponents to
maxInterStageShaderVariables
    - https://github.com/gfx-rs/wgpu/pull/8652
- gaps are now supported in bind group layouts
    - https://github.com/gfx-rs/wgpu/pull/9034
- depth validation changed to option to match spec
    - https://github.com/gfx-rs/wgpu/pull/8840
- SHADER_PRIMITIVE_INDEX is now PRIMITIVE_INDEX
  - https://github.com/gfx-rs/wgpu/pull/9101
- Support for binding arrays of RT acceleration structures
  - https://github.com/gfx-rs/wgpu/pull/8923
- Make HasDisplayHandle optional in WindowHandle
  - https://github.com/gfx-rs/wgpu/pull/8782
- `QueueWriteBufferView` can no longer be dereferenced to `&mut [u8]`,
so use `WriteOnly`.
  - https://github.com/gfx-rs/wgpu/pull/9042
- ~bevy_mesh currently has an added dependency on `wgpu`, can we move
`WriteOnly` to wgpu-types?~ (it is in wgpu-types now)
- Change max_*_buffer_binding_size type to match WebGPU spec (u32 ->
u64)
  - https://github.com/gfx-rs/wgpu/pull/9146
- raw vulkan init `open_with_callback` takes Limits as argument now
  - https://github.com/gfx-rs/wgpu/pull/8756

## Known Issues

There is currently one known issue with occlusion culling on macos,
which we've decided to disable on macos by checking the limits we
actually require. This makes it so that if wgpu releases a patch fix,
bevy 0.19 users will benefit from occlusion culling re-enabling for
them.

<details><summary>More details</summary>

On macos, the wpgu limits were changed to align with the spec and now
put the early and late GPU occlusion culling `StorageBuffer` limit at 8,
but we currently use 9. [Filed in wgpu
repo](https://github.com/gfx-rs/wgpu/issues/9287)

```
2026-03-19T01:37:10.771117Z ERROR bevy_render::error_handler: Caught rendering error: Validation Error

Caused by:
  In Device::create_bind_group_layout, label = 'build mesh uniforms GPU late occlusion culling bind group layout'
    Too many bindings of type StorageBuffers in Stage ShaderStages(COMPUTE), limit is 8, count was 9. Check the limit `max_storage_buffers_per_shader_stage` passed to `Adapter::request_device`
```

</details>

solari working on wgpu 29:

<img width="1282" height="752" alt="image"
src="https://github.com/user-attachments/assets/4744faec-65c0-4a72-93e1-34a721fc26d8"
/>

---------

Co-authored-by: atlv <email@atlasdostal.com>
2026-03-24 21:54:08 +00:00
IWonderWhatThisAPIDoes dfbb379520 Explain difference between visibility components (#22850)
# Objective

New users might not understand the differences between the three
visibility components, and the current documentation does not direct
them to the component they want to use.

## Solution

Explicitly called out the existence of all three components in each
one's documentation, and the differences in their purpose. Inspired by
how `Transform`/`GlobalTransform` are documented.

## Pending points

`ViewVisibility` mentions render extraction, I considered making it a
link to `bevy_render::Extract`. Not sure if that's possible, or a good
idea.
2026-03-24 19:16:56 +00:00
Patrick Walton f6ee281a3b Make CPU visibility systems ignore meshes tagged with NoCpuCulling entirely. (#23107)
Currently, Bevy handles meshes tagged with `NoCpuCulling` by simply
always adding them to `VisibleEntities`. This is, however, inefficient,
because `VisibleEntities` has to be repopulated with the entity IDs of
such meshes every frame and copied to the render world. When scaling to
millions of entities, this becomes a significant bottleneck.

This PR changes the visibility systems to ignore meshes with
`NoCpuCulling` entirely. Instead of being added to `VisibleEntities`,
the mesh extraction systems instead use standard ECS queries to iterate
over meshes with `NoCpuCulling` directly, in addition to any entities in
`VisibleEntities` that use CPU culling. For efficiency,
`RenderVisibleMeshEntities` now tracks mesh instances that are subject
to CPU culling and those that opted out of CPU culling in two separate
data structures. Note that this required changing the signatures of
`DirtySpecializations` methods to return a tuple of references instead
of a reference to a tuple. Although that change looks complicated, it's
actually just reshuffling to accommodate this slight type change, not a
change in logic.

On `bevy_city`, `check_visibility` takes a median of 1.19 ms, and
`check_dir_light_mesh_visibility` takes a median of 4.33 ms. With this
patch, these systems entirely disappear if `NoCpuCulling` is added to
every mesh.

<img width="2756" height="1800" alt="Screenshot 2026-02-22 020929"
src="https://github.com/user-attachments/assets/18048399-bcfd-4165-8491-8d126d73534e"
/>
2026-03-21 20:46:32 +00:00
Rob Parrett 48d9d13bde Fix a few typos (#23412)
# Objective

Poke around in some recent changes, find a typo, go on typo fixing
rampage.

## Solution

Fix em
2026-03-18 22:42:23 +00:00
leomeinel 6d109083e5 docs: Remove misleading ViewVisibility docs (#23385)
# Objective

Fixes #23383.

## Solution

This removes a part of the `ViewVisibility` docs comment that suggests
it to be unreliable in change-detection even though bevy_pbr and some
examples use it for exactly that and it works just fine in general.

#22466 has made it possible to use reliable change-detection with
`ViewVisibility`.

## Testing

This does not change any code or logic. It only changes a docs comment.
Therefore no testing is required.
2026-03-16 20:22:55 +00:00
HugoPeters1024 c7a8d34949 bugfix: recompute inherited visibility when ChildOf component is removed (#23100)
# Objective

Whenever an entity that was inheriting its invisibility was orphaned,
its inherited visibility would not be recomputed, consequently remaining
invisible. It is my opinion - which I feel is supported by existing
comments in the code - that this should result in the entity becoming
visible again.

## Solution

Checking for removed components is a bit awkward (ideally one would have
use an additional `Removed<ChildOf>` predicate in the disjunction of
`changed`. Instead, we consider a secondary loop over
`RemovedComponents<ChildOf>`

## Testing

I included an additional unit test, having checked it did not pass prior
to my changes.

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Josiah Nelson <76791009+AlephCubed@users.noreply.github.com>
2026-03-16 20:09:38 +00:00
Patrick Walton ebfbc3f5c8 Move visibility range checking from the CPU to the GPU if NoCpuCulling is specified. (#23115)
People generally expect GPU-driven renderers to perform LOD selection on
the GPU. Visibility ranges constitute our LOD feature, but they're
currently checked on the CPU. This commit moves that to the GPU and
avoids checking on CPU if `NoCpuCulling` is present.

Note that, even with this patch, LOD buffers (i.e. the on-GPU buffers
that store the visibility ranges) are still built afresh on the CPU
every frame. This will probably be a bottleneck eventually, but that's
for a follow-up.

A `--no-cpu-culling` feature has been added to the `visibility_range`
example, for testing.
2026-03-15 20:40:02 +00:00
Aevyrie a22ef4279b Fix: custom near planes (#23279)
# Objective

- Setting the near plane in bevy 0.18 is ignored due to newly added
oblique projections.

## Solution

- Fix it.

## Testing

- Added a test. Tested with and without the fix.

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Patrick Walton <pcwalton@mimiga.net>
2026-03-10 04:21:30 +00:00
Guillaume Gomez 28fd2cb3c0 Enable the rustdoc "--generate-macro-expansion" feature (#23075)
You can see this feature in action in the compiler docs like
[here](https://doc.rust-lang.org/nightly/nightly-rustc/src/rustc_ast_lowering/errors.rs.html#323)
or
[here](https://doc.rust-lang.org/nightly/nightly-rustc/src/rustc_ast_lowering/format.rs.html#89).
2026-02-21 00:24:25 +00:00
Patrick Walton 6e3a94da85 Use change lists instead of ticks for detecting when meshes need to be re-specialized and/or re-queued. (#22966)
Right now, every frame, all specialization and queuing systems iterate
over all entities visible from a view and check to see whether they need
to be updated by consulting a set of change ticks and comparing them to
the current change ticks. To handle cases in which a mesh needs to be
removed from the bins, a separate final *sweep* pass then finds entities
that no longer exist and removes them manually from the bins. This
process is complex, error-prone, and slow, as it involves visiting all
visible entities multiple times every frame.

This PR changes the setup so that, instead of examining change ticks,
the visibility logic pushes the set of added and removed entities to
each view explicitly. The visibility system determines which meshes need
to be added and removed by first sorting the list of visible entities,
then performing an O(n) diff process on the last frame's visible
entities and this frame's visible entity list. The end result is that
the specialization and queuing systems only process the entities that
they need to every frame. If a mesh was visible last frame, remained
visible this frame, and didn't change its mesh or material, then it's
generally not examined at all. Not only is this significantly faster for
virtually all realistic scenes, but it's also much simpler.

In order to achieve the benefits of not examining every visible mesh
every frame, I made sorted render passes retained via an `IndexMap`.
This allows entities to be removed and added via random access while
still allowing the list to be sorted by distance. Note that I had to
remove the radix sort because `IndexMap` doesn't currently support that;
I believe the enormous speed benefits of this patch outweigh any minor
sorting regressions from this.

I tested this PR by running `scene_viewer` on a test scene with many
meshes and materials and implementing a material shuffler that randomly
switches the materials around. I tested the following cases:

* Moving the camera so that meshes become visible and invisible.

* Switching opaque materials on meshes.

* Moving meshes from opaque to alpha masked and vice versa.

* Moving meshes from binned render passes to sorted render passes (i.e.
transparent).

* All of the above while the meshes were off screen, then moving them on
screen to ensure that the changes took effect.

This PR brings the `specialize_shadows` time on the `bevy_city` demo
from 12.87 ms per frame to 0.1261 ms per frame, a 102x speedup. It
brings the `queue_shadows` time on the same demo from 12.34 ms per frame
to 0.1102 ms, a 111x speedup. Mean frame time goes from 50.16 ms to
23.26 ms, a 2.16x speedup.

`specialize_shadows` in `bevy_city` before and after:
<img width="2756" height="1800" alt="Screenshot 2026-02-14 180313"
src="https://github.com/user-attachments/assets/dbc3c68b-e0ec-424f-8085-87c0f5f41d3f"
/>

`queue_shadows` in `bevy_city` before and after:
<img width="2756" height="1800" alt="Screenshot 2026-02-14 180500"
src="https://github.com/user-attachments/assets/08f8e1bb-6ab4-47da-ae68-a80156d59caa"
/>

Frame graph of `bevy_city` before:
<img width="2756" height="1800" alt="Screenshot 2026-02-12 203324"
src="https://github.com/user-attachments/assets/d0807cee-23a2-4e14-be1a-7466b795ebfa"
/>

Frame graph of `bevy_city` after:
<img width="2756" height="1800" alt="Screenshot 2026-02-14 180506"
src="https://github.com/user-attachments/assets/b22acf0f-a6f9-432b-93d7-f8057c815b05"
/>
2026-02-20 00:45:38 +00:00
Patrick Walton d1c3557d11 Make light extraction retained, and clean up lights that became newly invisible. (#22857)
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`:
<img width="2756" height="1800" alt="Screenshot 2026-02-07 101916"
src="https://github.com/user-attachments/assets/18ec7190-cb5b-41c8-8c6f-75ef13d683fb"
/>

`many_lights` in this PR:
<img width="2756" height="1800" alt="Screenshot 2026-02-07 101210"
src="https://github.com/user-attachments/assets/daf3d93f-efcc-4a2e-b728-da1f32e6ad85"
/>

Comparison of `prepare_lights` between `main` and this PR for
`many_lights`:
<img width="2756" height="1800" alt="Screenshot 2026-02-07 101234"
src="https://github.com/user-attachments/assets/c8c63685-a3df-4351-a7f3-d74fc40d505a"
/>

Comparison of `extract_lights` between `main` and this PR for
`many_lights`:
<img width="2756" height="1800" alt="Screenshot 2026-02-07 101248"
src="https://github.com/user-attachments/assets/68eb1618-a856-40eb-94bc-91f215d1dc4e"
/>

---------

Co-authored-by: Robert Swain <robert.swain@gmail.com>
Co-authored-by: atlv <email@atlasdostal.com>
2026-02-09 07:09:13 +00:00
Erik W 7418544281 Add per-entity NoCpuCulling (#22767)
# Objective

- I use a compute shader to update the transforms and write them to
MeshInput directly for some entities and doesn't readback to the CPU.
This has the unfortunate side-effect of making the main world transform
positions always being (0,0) and then being frustum culled on the CPU
side. There is no way to disable CPU culling per entity, hence this PR.

## Solution

- Use the same `Has<NoCpuCulling>` for visible_aabb_query as view_query

## Testing

I checked in RenderDoc for VkDrawIndexedIndirectCommand's instanceCount,
which results in the same number as without NoCpuCulling, and lower
number than with NoFrustumCulling. It also works when looking away from
the center.

Note that this is my first real contribution and I have no real
rendering experience.
2026-02-04 16:34:56 +00:00
Kevin Chen 5f1ca6cff1 Move HalfSpace and some of Frustum (renamed to ViewFrustum) from bevy_camera to bevy_math (#22684)
# Objective

- Does the latter half of #13945 
- Does the first two thirds of #13878 
- Finish some of what #13882 started

## Solution

- Moves `bevy_camera::primitives::HalfSpace` to
`bevy_math::primitives::HalfSpace` (first commit)
- Moves some of `bevy_camera::primitives::Frustum` to
`bevy_math::primitives::ViewFrustum` (second commit)
- I basically followed Jondolf’s directions on the two refactorings.
aabb, obb, and sphere stuff stays in bevy_camera (for now? If sphere is
refactored, it has been stressed to be done as a follow up )

## Testing

I ran the `lighting`, `2d_gizmos`, and `3d_gizmos` example just to make
sure the lights… and camera… (and action!) were still working.
Everything seems to be fine there compared to main. If there are any
other examples I should run to make sure things look good, let me know.
2026-01-27 06:13:09 +00:00
andriyDev 78805e936e Replace validate_parent_has_component with ValidateParentHasComponentPlugin. (#22675)
# Objective

- Fixes #21666.
- Fixes #19776

## Solution

Instead of warning in the hook, we instead send a message for the entity
if its parent is missing the component. A system later reads these
messages and checks again if the parent is missing the component, and
only then logs. This is done by:

- Change the hook into an observer.
- Create a message to indicate a check is needed.
- Create a system to do this check.
- Create a plugin in `bevy_app` to add all these things to the app.
- I couldn't think of a better place to put this, other than like
`bevy_util`, but I didn't want to add to the "kitchen sink".
- Switch `GlobalTransform`/`InheritedVisibility` to use this plugin
instead.
- One thing to note is I only perform this check in the `Last` schedule.
We could move this check if necessary, but I doubt many users will spawn
a child, and then only add the parent's GlobalTransform **after**
`PostUpdate` (not sure if this will even affect transform propagation
though?)

Note: the memory usage is proportional to how many of these bad entities
you spawn in a single frame.

## Testing

- I ran the following example:

```rust
use bevy::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, setup)
        .run();
}

fn setup(mut commands: Commands) {
    let parent = commands.spawn_empty().id();
    let child = commands.spawn_empty().id();

    // Initialize the child first, add the Visibility+Transform component, which implicitly adds the
    // InheritedVisibility+GlobalTransform component as well.
    commands
        .entity(child)
        .insert((ChildOf(parent), Visibility::Inherited, Transform::default()));

    // Also make the parent add the Visibility+Transform component so it is valid.
    commands
        .entity(parent)
        .insert((Visibility::Inherited, Transform::default()));
}
``` 

On main, this logs two warnings. With this PR, there are no logs! I also
verified that omitting attaching the components to the parent still logs
the warning.

---------

Co-authored-by: Kevin Chen <chen.kevin.f@gmail.com>
2026-01-27 06:10:50 +00:00
Greeble 9d2db8838f Improve frustum culling of skinned meshes through per-joint bounds (#21837)
## Objective

Mostly fix #4971 by adding a new option for updating skinned mesh `Aabb`
components from joint transforms.


https://github.com/user-attachments/assets/c25b31fa-142d-462b-9a1d-012ea928f839

This fixes cases where vertex positions are only modified through
skinning. It doesn't fix other cases like morph targets and vertex
shaders.

The PR kind of upstreams
[`bevy_mod_skinned_aabb`](https://github.com/greeble-dev/bevy_mod_skinned_aabb),
but with some changes to make it simpler and more reliable.

### Dependencies

- (MERGED) #21732 (or something similar) is desirable to make the new
option work with `RenderAssetUsages::RENDER_WORLD`-only meshes.
- This PR is authored as if 21732 has landed. But if that doesn't happen
then I can adjust this PR to note the limitation.
- (Optional) #21845 adds an option related to skinned mesh bounds.
  - Either PR can land first - the second will need to be updated.

## Background

If a main world entity has a `Mesh3d` component then it's automatically
assigned an `Aabb` component. This is done by `bevy_camera` or
`bevy_gltf`. The `Aabb` is used by `bevy_camera` for frustum culling. It
can also be used by `bevy_picking` as an optimization, and by third
party crates.

But there's a problem - the `Aabb` can be wrong if something changes the
mesh's vertex positions after the `Aabb` is calculated. This can be done
by vertex shaders - notably skinning and morph targets - or by mutating
the `Mesh` asset (#4294).

For the skinning case, the most common solution has been to disable
frustum culling via the `NoFrustumCulling` component. This is simple,
and might even be the most efficient approach for apps where meshes tend
to stay on-screen. But it's annoying to implement, bad for apps where
meshes are often off-screen, and it only fixes frustum culling - it
doesn't help other systems that use the `Aabb`.

## Solution

This PR adds a reliable and reasonably efficient method of updating the
`Aabb` of a skinned mesh from its animated joint transforms. See the
"How does it work" section for more detail.

The glTF loader can add skinned bounds automatically if a new
`GltfSkinnedMeshBoundsPolicy` option is enabled in `GltfPlugin` or
`GltfLoaderSettings`:

```rust
app.add_plugins(DefaultPlugins.set(GltfPlugin {
    skinned_mesh_bounds_policy: GltfSkinnedMeshBoundsPolicy::Dynamic,
    ..default()
}))
```

_The new glTF loader option is enabled by default_. I think this is the
right choice for several reasons:

- Bugs caused by skinned mesh culling have been a regular pain for both
new and experienced users. Now the most common case Just Works(tm).
- The CPU cost is modest (see later section), and sophisticated users
can opt-out.
- GPU limited apps might see a performance increase if the user was
previously disabling culling.

Non-glTF cases require some manual steps. The user must ask `Mesh` to
generate the skinned bounds, and then add the `DynamicSkinnedMeshBounds`
marker component to their mesh entity.

```rust
mesh.generate_skinned_mesh_bounds()?;
let mesh_asset = mesh_assets.add(mesh);
entity.insert((Mesh3d(mesh_asset), DynamicSkinnedMeshBounds));
```

See the `custom_skinned_mesh` example for real code.

## Bonus Features

### `GltfSkinnedMeshBoundsPolicy::NoFrustumCulling`

This is a convenience for users who prefer the `NoFrustumCulling`
workaround, but want to avoid the hassle of adding it after a glTF scene
has been spawned.

```rust
app.add_plugins(DefaultPlugins.set(GltfPlugin {
    skinned_mesh_bounds_policy: GltfSkinnedMeshBoundsPolicy::NoFrustumCulling,
    ..default()
}))
```

PR #21845 is also adding an option related to skinned mesh bounds. I'm
fine if that PR lands first - I'll update this PR to include the option.

### Gizmos

`bevy_gizmos::SkinnedMeshBoundsGizmoPlugin` can draw the per-joint
AABBs.

```rust
fn toggle_skinned_mesh_bounds(mut config: ResMut<GizmoConfigStore>) {
    config.config_mut::<SkinnedMeshBoundsGizmoConfigGroup>().1.draw_all ^= true;
}
```

The name is debatable. It's not technically drawing the bounds of the
skinned mesh - it's drawing the per-joint bounds that contribute to the
bounds of the skinned mesh.

## Testing

```sh
cargo run --example test_skinned_mesh_bounds

# Press `B` to show mesh bounds, 'J' to show joint bounds.
cargo run --example scene_viewer --features "free_camera" -- "assets/models/animated/Fox.glb"
cargo run --example scene_viewer --features "free_camera" -- "assets/models/SimpleSkin/SimpleSkin.gltf"

# More complicated mesh downloaded from https://github.com/KhronosGroup/glTF-Sample-Assets/tree/main/Models/RecursiveSkeletons
cargo run --example scene_viewer --features "free_camera" -- "RecursiveSkeletons.glb"

cargo run --example custom_skinned_mesh
```

I also hacked `custom_skinned_mesh` to simulate awkward cases like
rotated and off-screen entities.

## How Does It Work?

<details><summary>Click to expand</summary>

### Summary

`Mesh::generated_skinned_mesh_bounds` calculates an AABB for each joint
in the mesh - the AABB encloses all the vertices skinned to that joint.
Then every frame, `bevy_camera::update_skinned_mesh_bounds` uses the
current joint transforms to calculate an `Aabb` that encloses all the
joint AABBs.

This approach is reliable, in that the final `Aabb` will always enclose
the skinned vertices. But it can be larger than necessary. In practice
it's tight enough to be useful, and rarely more than 50% bigger.

This approach works even with non-rigid transforms and soft skinning. If
there's any doubt then I can add more detail.

### Awkward Bits

The solution is not as simple and efficient as it could be.

#### Problem 1: Joint transforms are world-space, `Aabb` is
entity-space.

- Ideally we'd use the world-space joint transforms to calculate a
world-space `Aabb`, but that's not possible.
- The obvious solution is to transform the joints to entity-space, so
the `Aabb` is directly calculated in entity-space.
  - But that means an extra matrix multiply per joint.
- This PR calculates the `Aabb` in world-space and then transforms it to
entity-space.
- That avoids a matrix multiply per-joint, but can increase the size of
the `Aabb`.

#### Problem 2: Joint AABBs are in a surprising(?) space.

- When creating joint AABBs from a mesh, the intuitive solution would be
to calculate them in joint-space.
- Then the update just has to transform them by the world-space joint
transform.
- But to calculate them in joint-space we need both the bind pose vertex
positions and the bind pose joint transforms.
- These two parts are in separate assets - `Mesh` and
`SkinnedMeshInverseBindposes` - and those assets can be mixed and
matched.
- So we'd need to calculate a `SkinnedMeshBoundsAsset` for each
combination of `Mesh` and `SkinnedMeshInverseBindposes`.
- (`bevy_mod_skinned_aabb` uses this approach - it's slow and fragile.)
- This PR calculates joint AABBs in *mesh-space* (or more strictly
speaking: bind pose space).
  - That can be done with just the `Mesh` asset.
- One downside is that the update needs an extra matrix multiply so we
can go from mesh-space to world-space.
- However, this might become a performance advantage if frustum culling
changes - see the "Future Options" section.
- Another minor downside is that mesh-space AABBs (red in the screenshot
below) tend to be bigger than joint-space AABBs (green), since joints
with one long axis might be at an awkward angle in mesh-space.

<img width="1085" height="759" alt="image"
src="https://github.com/user-attachments/assets/a02a28c3-8882-412c-9be1-64109b767da7"
/>

### Future Options

For frustum culling there's a cheeky way to optimize and simplify
skinned bounds - put frustum culling in the renderer and calculate a
world-space AABB during `extract_skins`. The joint transform will be
already loaded and in the right space, so we can avoid an entity lookup
and matrix multiply. I estimate this would make skinned bounds 3x
faster.

Another option is to change main world frustum culling to use a
world-space AABB. So there would be a new `GlobalAabb` component that
gets updated each frame from `Aabb` and the entity transform (which is
basically the same as transform propagation and the relationship between
`Transform` and `GlobalTransform`). This has some advantages and
disadvantages but I won't get into them here - I think putting frustum
culling into the renderer is a better option.

(Note that putting frustum culling into the renderer doesn't mean
removing the current main world visibility system - it just means the
main world system would be separate opt-in system)

</details>

## Performance

<details><summary>Click to expand</summary>

### Initialization

Creating the skinned bounds asset for `Fox.glb` (576 verts, 22 skinned
joints) takes **0.03ms**. Loading the whole glTF takes 8.7ms, so this is
a **<1% increase**.

### Per-Frame

The `many_foxes` example has 1000 skinned meshes, each with 22 skinned
joints. Updating the skinned bounds takes **0.086ms**. This is a
throughput of roughly 250,000 joints per millisecond, using two threads.

<img width="2404" height="861" alt="image"
src="https://github.com/user-attachments/assets/c27165ae-dc6c-4f6b-bbfb-4e211ab0263c"
/>

The whole animation update takes 3.67ms (where "animation update" =
advancing players + graph evaluation + transform propagation). So we can
kinda sorta claim that this PR increases the cost of skinned animation
by roughly **3%**. But that's very hand-wavey and situation dependent.

This was tested on an AMD Ryzen 7900 but with
`TaskPoolOptions::with_num_threads(6)` to simulate a lower spec CPU.
Comparing against a few other threading options:

- Non-parallel: **0.141ms**.
- 6 threads (2 compute threads): **0.086ms**.
- 24 threads (15 compute threads): **0.051ms**.

So the parallel iterator is better but quickly hits diminishing returns
as the number of threads increases.

### Future Options

The "How Does It Work" section mentions moving skinned mesh bounds into
the renderer's skin extraction. Based on some microbenchmarks, I
estimate this would reduce non-parallel `many_foxes` from 0.141ms to
0.049ms, so roughly 3x faster. Requiring AVX2 (to enable broadcast
loads) or pre-splatting (to fake broadcast loads for SSE) would knock
off another 25%. And fancier SIMD approaches could do better again.

There's also approaches that trade reliability for performance. For
character rigs, an effective optimization is to fold face and finger
joints into a single bound on the head and hand joints. This can reduce
the number of joints required by 50-80%.

</details>

## FAQ

<details><summary>Click to expand</summary>

#### Why can't it be automatically added to any mesh? Then the glTF
importer and custom mesh generators wouldn't need special logic.

`bevy_mod_skinned_aabb` took the automatic approach, and I don't think
the outcome was good. It needs some surprisingly fiddly and fragile
logic to decide when an entity has the right combination of assets in
the right loaded state. And it can never work with
`RenderAssetUsages::RENDER_WORLD`.

So this PR takes a more modest and manual approach. I think there's
plenty of scope to generalise and automate as the asset pipeline
matures. If the glTF importer becomes a purer glTF -> BSN transform,
then adding skinned bounds could be a general scene/asset transform
that's shared with other importers and custom mesh generators.

#### Why is the data in `Mesh`? Shouldn't it go in `SkinnedMesh` or
`SkinnedMeshInverseBindposes`?

That might seem intuitive, but it wouldn't work in practice - the data
is derived from `Mesh` alone. `SkinnedMesh` doesn't work because it's
per mesh instance, so the data would be duplicated.
`SkinnedMeshInverseBindposes` doesn't work because it can be shared
between multiple meshes.

The names are a bit misleading - `Mesh` does contain some skinning data,
while `SkinnedMesh` and `SkinnedMeshInverseBindposes` are more like
joint bindings one step removed from the vertex data.

#### Why not put the bounds on the joint entities?

This is surprisingly tricky in practice because multiple meshes can be
bound to the same joint entity. So there would need to be logic that
tracks the bindings and updates the bounds as meshes are added and
removed.

#### Why is the `DynamicSkinnedMeshBounds` component required?

It's an optimisation for users who want to opt out. It might also be
useful for future expansion, like adding options to approximate the
bounds with an AABB attached to a single joint.

#### Why are the update system and `DynamicSkinnedMeshBounds` component
in `bevy_camera`? Shouldn't they be in `bevy_mesh`?

`bevy_camera` is the owner and main user of `Aabb`, and already has some
mesh related logic (`calculate_bounds` automatically adds an `Aabb` to
mesh entities). So putting it in `bevy_camera` is consistent with the
current structure. I'd agree that it's a little awkward though and could
change in future.

</details>

## What Do Other Engines Do?

<details><summary>Click to expand</summary>

- **Unreal**: Automatically uses [collision
shapes](https://dev.epicgames.com/documentation/en-us/unreal-engine/physics-asset-editor-in-unreal-engine)
attached to joints, which is similar to this PR in practice but fragile
and inefficient. Also supports various fixed bounds options.
- **Unity**: Fixed bounds attached to the root bone. Automatically
calculated from animation poses or specified manually
([documentation](https://docs.unity3d.com/6000.4/Documentation/Manual/troubleshooting-skinned-mesh-renderer-visibility.html)).
- **Godot**: Appears to use roughly the same method as this PR, although
I didn't 100% confirm. See
[`MeshStorage::mesh_get_aabb`](https://github.com/godotengine/godot/blob/fafc07335bdecacd96b548c4119fbe1f47ee5866/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp#L650)
and
[`RendererSceneCull::_update_instance_aabb`](https://github.com/godotengine/godot/blob/235a32ad11f40ecba26d6d9ceea8ab245c13adb0/servers/rendering/renderer_scene_cull.cpp#L1991).
- **O3DE**: Fixed bounds attached to root bone, plus option to
approximate the AABB from joint origins and a fudge factor.
- **Northlight** (Remedy, Alan Wake 2): Specifically for vegetation,
calculates bounds from joint extents on GPU
([source](https://gdcvault.com/play/1034310/Large-Scale-GPU-Based-Skinning),
slide 48)

An approach that's been proposed several times for Bevy is copying
Unity's "fixed AABB from animation poses". I think this is more
complicated and less reliable than many people expect. More complicated
because linking animations to meshes can often be difficult. Less
reliable because it doesn't account for ragdolls and procedural
animation. But it could still be viable for for simple cases like a
single self-contained glTF with basic animation.

</details>

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2026-01-26 04:25:59 +00:00
atlv bb1bcdaf80 reflect ComputedCameraValues (#22691)
# Objective

- Don't know why it wasnt reflected

## Solution

- reflect it

## Testing

- ci
2026-01-25 20:54:53 +00:00
atlv 0bd6409f11 reflect Frustum and HalfSpace (#22693)
# Objective

- reflect it

## Solution

- Reflect derive
2026-01-25 20:54:16 +00:00
atlv 3ae32d500b move transmission stuff to bevy_pbr (#22687)
# Objective

- It makes no sense for bevy_camera::Camera3d to talk about transmission
quality settings
- transmission is not a core thing its a purely pbr thing, why is in
bevy_core_pipelines
- the implementation is generally scattered all over the place
- ScreenSpaceTransmissionQuality is a resource for no reason at all

## Solution

- split out a struct for transmission stuff
- consolidate stuff in bevy_pbr
- make ScreenSpaceTransmissionQuality not a resource

## Testing

transmission example looks good
2026-01-25 20:36:42 +00:00
atlv badca1c48a move Hdr to bevy_camera (#22683)
# Objective

- whether a Camera needs to draw Hdr content or not is an aspect of
scene description
- as such, it should live in a non-rendering crate

## Solution

- move it to bevy_camera
- dont extract it to the render world anymore. audited all usages of it
and none are in the render world queries, instead `ExtractedCamera::hdr`
is used.

## Testing

- manual spot check of a few examples
- example runner regression test
2026-01-24 21:32:24 +00:00
Chris Biscardi 543b305adc Upgrade to wgpu 28 (#22265)
Upgrade to wgpu 28

> [!important]
> This can't merge until https://github.com/bevyengine/naga_oil/pull/132
does, and the dependency is updated from my fork to the release.
> 
> Also requires wgpu 28 in dlss_wgpu:
https://github.com/bevyengine/dlss_wgpu/pull/17

> [!note]
> This does not enable mesh shaders, and neither does the naga_oil PR. I
chose to do an upgrade first, then go back and see about mesh shaders.

Here's a general list of changes and what I did. Commits are grouped by
feature except for the last one, which enabled solari when I ran the
solari example.

## MipmapFilterMode is split from FilterMode

- Split MipmapFilterMode from FilterMode #8314:
https://github.com/gfx-rs/wgpu/pull/8314

solution: implement From for `MipmapFilterMode`/`ImageFilterMode` since
the values are the same. The split was because the spec indicates they
are different types, even though they have the same values.

## Push Constants are now Immediates

- https://github.com/gfx-rs/wgpu/pull/8724

immediate_size is [a
u32](https://docs.rs/wgpu/28.0.0/wgpu/struct.PipelineLayoutDescriptor.html#structfield.immediate_size)
so use that instead of `PushConstantRange`

## Capabilities name changes

- https://github.com/gfx-rs/wgpu/pull/8671

Got new list from
https://github.com/gfx-rs/wgpu/blob/trunk/wgpu-core/src/device/mod.rs#L449
and copied it in.

## subgroup_{min,max}_size moved from Limits to AdapterInfo

- https://github.com/gfx-rs/wgpu/pull/8609

Update the limits to have the fields they have now, mirror the logic
from the other limits calls.

## multiview_mask

- https://github.com/gfx-rs/wgpu/pull/8206

set to None because we don't use it currently. Its vaguely for VR.

## error scope is now a guard

- https://github.com/gfx-rs/wgpu/pull/8685

retain guard and then pop() it later


---

I made one mistake during the PR, thinking set_immediates was going to
be the size of the immediates and not the offset. I'd like reviewers to
take a look at immediates offset and size sites specifically just in
case I missed something.

Here's a bunch of examples running

<img width="1470" height="1040" alt="screenshot-2025-12-24-at-16 47
22@2x"
src="https://github.com/user-attachments/assets/83dcf4c8-69f5-480a-b724-86598530f25a"
/>
<img width="1470" height="1040" alt="screenshot-2025-12-24-at-16 48
44@2x"
src="https://github.com/user-attachments/assets/46d897fa-1ab2-44ef-8055-fe2fce740dbc"
/>
<img width="1470" height="1040" alt="screenshot-2025-12-24-at-16 49
10@2x"
src="https://github.com/user-attachments/assets/6ae7a9bf-0473-4800-8dfc-233a6a41d6df"
/>
<img width="1470" height="1040" alt="screenshot-2025-12-24-at-16 49
31@2x"
src="https://github.com/user-attachments/assets/89f84a26-cfbd-4196-bca8-111c3d20ba7b"
/>


## Known Issues

> [!NOTE]
> 
> There are no current known issues. Everything previous has been
solved.

- [x] enable extensions can not be written in naga oil in a way that
allows them to be used in composed modules.

Update: this was fixed by introducing a flag in naga-oil to force
shaders to allow ray queries in composed modules when using bevy_solari.
This is temporary and will be different in WESL-future.

<details><summary>old explanation</summary>
This is blocking solari from working on nvidia (solari runs successfully
on macos m1) because it needs `enable wgpu_ray_query;`. Putting the
declaration before `#define_import_path` means it gets stripped out
(afaict), and putting it after results in

```
error: expected global declaration, but found a global directive
  ┌─ embedded://bevy_solari/scene/raytracing_scene_bindings.wgsl:3:1
  │
3 │ enable wgpu_ray_query;
  │ ^^^^^^ written after first global declaration
  │
  = expected global declaration, but found a global directive
```

</details>

- [x] dlss_wgpu mixes apis which [causes panics
now](https://github.com/bevyengine/dlss_wgpu/pull/17#issuecomment-3690847524).

<details><summary>Previous notes as dlss_wgpu was being fixed
here</summary>

The wgpu release notes don't mention which PR this was introduced in,
only saying:

> Using both the wgpu command encoding APIs and
CommandEncoder::as_hal_mut on the same encoder will now result in a
panic.

It was caused by https://github.com/gfx-rs/wgpu/pull/8373 which claimed
to not know of any use cases

> With record on finish, the actual ordering on the command buffer is
deeply counter intuitive (all as_hal would come first) and I think it
additionally was just flat out broken in some ways
> -
https://discord.com/channels/691052431525675048/743663924229963868/1453786307099758683

Possible path forward is using multiple command buffers:
https://discord.com/channels/691052431525675048/743663924229963868/1453795633503670415

</summary>

---------

Co-authored-by: robtfm <50659922+robtfm@users.noreply.github.com>
2026-01-22 18:35:29 +00:00
fogi 4d7749026e add Clone and PartialEq traits to NoFrustumCulling marker component (#22615)
# Objective

fixes not being able to use the HierarchyPropagatePlugin with the
NoFrustumCulling component by satisfying the trait bounds for Clone and
PartialEq

## Solution

- add Clone and PartialEq trait to NoFrustumCulling struct

## Testing

i did not test this
2026-01-21 00:14:25 +00:00
github-actions[bot] d772c32071 Bump Version after Release (#22498)
Bump version after release
This PR has been auto-generated

---------

Co-authored-by: Bevy Auto Releaser <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: François Mockers <francois.mockers@vleue.com>
2026-01-14 18:21:56 +00:00
Aevyrie c1d31bba96 Fix the fix to the optimization and fix (#22466)
# Objective

- Fix change detection being triggered every frame in an untested case.

## Solution

- Fix the issue and add another test to catch this case.

## Testing

- Yes

Main vs this PR
<img width="1228" height="746" alt="image"
src="https://github.com/user-attachments/assets/29e1d900-1ccb-4414-8a68-ce31219dec4e"
/>
2026-01-10 06:42:53 +00:00
Aevyrie e1ac08e82c Fix Visibility Latching Bug (#22425)
# Objective

- Fix when an entity becomes visible, it latches and is impossible to
hide.
- Introduced in #22226 

## Solution

- Ensure we clear the current state bit when shifting
- Add regression test
2026-01-08 16:54:24 +00:00
Aevyrie e4eb916464 Optimize Visibility Systems (#22226)
# Objective

- Make visibility systems less slow.
- Saves as much as 1.2ms of CPU time on a (GPU bound) 6.7ms frame,
rendering caldera hotel.
- Fixes #22256

## Solution

- Replace the `EntityHashSet` with a component added directly to
entities. This still allows for correct change detection triggers on
visibility, but avoids hashing. This also enables parallel updates.

## Summary

- This PR lead to a rabbit hole that uncovered #22256 
- This PR resolves the regression introduced since 0.17 released
- A more fair comparison is not to main (which has a regression), but to
0.17
- Compared to 0.17.3, this is a 27% speedup to the `PostUpdate` schedule
for caldera hotel on an M4 Max

<img width="558" height="251" alt="image"
src="https://github.com/user-attachments/assets/2d711e3c-b65e-4c3a-9d2a-a587f8f36aae"
/>

## Testing

- Many cubes, many foxes, caldera
- Yellow is new, red is old.
- Note the bimodality in the old traces, this was consistently
repeatable, and seems to have something to do with `EntityHashSet` and
`EntityHashMap`. Worth investigating that further, as I've seen that
bimodal behavior before, and blamed it on pcores vs ecores, but I
verified that is not happening.
- Summary: caldera hotel no longer exhibits bimodal performance with a
pathological mode that runs very slowly. Roughly comparing the ~90th
percentile performance, the optimized code is about 1.2ms faster. This
is particularly significant when you consider this is running on a
device that is already hitting 150fps (GPU bound), so it only has a
frame budget of 6.7ms.
- Notably, visibility checking was the last egregiously performing set
of systems in the common hot path of most bevy apps, and no longer stick
out as obviously slow in a frame profile:

A high level comparison of the change in CPU time by looking at just
PostUpdate between old(red) and new(yellow)

<img width="1738" height="512" alt="image"
src="https://github.com/user-attachments/assets/5252ff0f-d9f3-49d1-a3ce-309bd84d5485"
/>

### caldera hotel - all entities in view

For this test, I didn't touch the camera, so all entities were in view
at all times.

`check_visibility`

<img width="1262" height="397" alt="image"
src="https://github.com/user-attachments/assets/c80c470a-1548-42d2-838c-4e74525a6cb8"
/>

`reset_view_visibility`

<img width="1262" height="407" alt="image"
src="https://github.com/user-attachments/assets/7b550823-f832-41f1-9e42-20cab342b0f2"
/>

`mark_newly_hidden_entities_invisible`

This is 23us slower than the "fast mode" of the existing code on main,
but 180-250us *faster* than the weird "slow mode" on main. The new code
is significantly more stable, and does not exhibit the super slow mode.

<img width="1256" height="418" alt="image"
src="https://github.com/user-attachments/assets/b7cbf57d-a221-468a-9af8-3381981874b2"
/>

### caldera hotel - no entities in view

For this test, I immediately rotated the camera so the hotel was not in
view.

`check_visibility`

This is largely a wash. The old code is 2us faster, but this is likely
in the realm of noise.

<img width="1255" height="372" alt="image"
src="https://github.com/user-attachments/assets/2c795f72-ebea-4b35-ba19-a8eccd4c56fc"
/>

`reset_view_visibility`

This is a big win. The main peak is about 30us faster now, but the major
win is the worst case, which is nearly 500us faster.

<img width="1252" height="398" alt="image"
src="https://github.com/user-attachments/assets/f8a9d99f-a6e1-48db-8639-d52276dba9ab"
/>


`mark_newly_hidden_entities_invisible`

Same story as the other caldera comparison with everything in view, this
is a bit slower than the fast mode, but way faster than the slow mode on
main.

<img width="1248" height="400" alt="image"
src="https://github.com/user-attachments/assets/4564d94e-911a-4b3f-a584-c6e102ef2dd0"
/>
2026-01-01 22:00:16 +00:00
breakdown_dog 119d6fbeca Deduplicate world_to_view logic (#21715)
# Objective

- The functions logical_viewport_size and physical_viewport_size in
camera.rs share a very similar pattern.
- Refactor code to reduce duplication.

## Solution

- extract to a shared function.

## Testing

- Ran `cargo test` and all the tests pass.
2025-12-16 05:03:08 +00:00
Chris Biscardi ec072d8f11 reflect rendertarget (#22113)
RenderTarget was a Component that derived Reflect but did not Reflect
Component

as of this merge today:
https://github.com/bevyengine/bevy/commit/67633b34da677a825ea3c6900b884d012d56999b

Without this fix, spawning a scene with a camera (such as from a Blender
export) causes a panic:

> scene contains the unregistered component
`bevy_camera::camera::RenderTarget`. consider adding
`#[reflect(Component)]` to your type
2025-12-15 02:03:18 +00:00
robtfm 5f7e43c951 Retain asset without data for RENDER_WORLD-only assets (#21732)
# Objective

when `RenderAssets` with `RenderAssetUsages::RENDER_WORLD` and without
`RenderAssetUsages::MAIN_WORLD` are extracted, the asset is removed from
the assets collection. this causes some issues:
- systems which rely on the asset, like picking with meshes, fail with
"asset not found" errors which are unintuitive.
- loading the asset by path a second time results in the asset being
reloaded from storage, re-extracted and re-transferred to gpu, replacing
the existing asset
- knowledge about the asset state is lost, we cannot tell if an asset is
already loaded with `AssetServer::get_handle`
- metadata (image size, e.g.) is no longer available for the asset

## Solution

### extraction:
- add `take_gpu_data` to the `RenderAsset` trait. use it to pull the
data out of the asset for transfer, and leave the empty asset in the
collection. default implementation just `clone`s the asset.
- if the data has already been taken, ~~panic. this follows from
modifying an asset after extraction, which is always a code error, so i
think panic here makes sense~~ _log an error_

### Mesh/RenderMesh:
- make `Mesh::attributes` and `Mesh::indices` options
- take them on extraction
- `expect` operations which access or modify the vertex data or indices
if it has been extracted. accessing the vertex data after extraction is
always a code error. fixes #19737 by resulting in the error `Mesh has
been extracted to RenderWorld. To access vertex attributes, the mesh
must have RenderAssetUsages::MAIN_WORLD`
- provide `try_xxx` operations which allow users to handle the access
error gracefully if required (no usages as part of this pr, but provided
for future)
- compute the mesh `Aabb` when gpu data is taken and store the result.
this allows extracted meshes to still use frustum culling (otherwise
using multiple copies of an extracted mesh now panics as `compute_aabb`
relied on vertex positions). there's a bit of a tradeoff here: users may
not need the Aabb and we needlessly compute it. but i think users almost
always do want them, and computing once (for extracted meshes) is
cheaper than the alternative, keeping position data and computing a
fresh `Aabb` every time the mesh is used on a new entity.

### Image/GpuImage:
images are a little more complex because the data can be deliberately
`None` for render-targets / GPU-written textures where we only want an
uninitialized gpu-side texture.
- take `Image::data` on extraction
- record on the resulting `GpuImage` whether any data was found
initially
- on subsequent modifications with no data, panic if there was data
previously

corner case / issue: when used with `RenderAssetBytesPerFrameLimiter`
there may be no previous gpu asset if it is still queued pending upload
due to the bandwidth limit. this can result in a modified image with
initial data skipping the `had_data` check, resulting in a blank
texture. i think this is sufficiently rare that it's not a real problem,
users would still hit the panic if the asset is transferred in time and
the problem/solution should be clear when they do hit it.

### ShaderStorageBuffer/GpuShaderStorageBuffer
follows the same pattern as Image/GpuImage:
- take `ShaderStorageBuffer::data` on extraction
- record on the resulting `GpuShaderStorageBuffer` whether any data was
found initially
- on modifications with no data, panic if there was data previously

we don't have the queue issue here because `GpuShaderStorageBuffer`
doesn't implement `byte_len` so we can't end up queueing them.

#### other RenderAssets
i didn't modify the other `RenderAsset` types
(`GpuAutoExposureCompensationCurve`, `GpuLineGizmo`,
`RenderWireframeMaterial`, `PreparedMaterial`, `PreparedMaterial2d`,
`PreparedUiMaterial`) on the assumption that ~~cloning these is cheap
enough anyway~~ _the asset usages are not exposed so we should never
call `take_gpu_data`. the default implementation panics with a message
directing users to implement the method if required_

## Testing

only really tested within my work project. i can add some explicit tests
if required.

---------

Co-authored-by: Jasmine S <jasmine.schweitzer@nominal.io>
Co-authored-by: Greeble <166992735+greeble-dev@users.noreply.github.com>
2025-12-14 22:03:54 +00:00
Aevyrie e3ebf3a8a9 Recompute AABBs (#18742)
# Objective

- Recompute AABBs when meshes change. 
- Optimize sprite AABB re-computation to avoid component insertion where
mutation is possible.
- Fixes #4294 and closes #7971

## Solution

- Implement the things.

## Testing

- CI
2025-12-14 21:35:27 +00:00
charlotte 🌸 67633b34da Convert RenderTarget to Component (#20917)
# Objective

#20830 created the possibility that we may want to have render targets
that produce a number of outputs, e.g. depth and normals. This is a
first step towards something like that (e.g. a `RendersTo` relation) by
converting `RenderTarget` to be a component. This is also useful for
out-of-tree render targets that may want to do something like
`#[require(RenderTarget::Image)]` once BSN lands.

## Solution

Make it a component.
2025-12-14 21:33:48 +00:00
JMS55 0c815db70e Upgrade to wgpu 27 and associated crates (#21746)
Supersedes https://github.com/bevyengine/bevy/pull/21725.

---------

Co-authored-by: Jasmine Schweitzer <jasmine.schweitzer@nominal.io>
Co-authored-by: Ben Cochrane <ben.cochrane2112@gmail.com>
Co-authored-by: François Mockers <francois.mockers@vleue.com>
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: François Mockers <mockersf@gmail.com>
2025-12-10 07:26:40 +00:00
Patrick Walton 00f6eb7a1c Implement the infrastructure needed to support portals and mirrors. (#13797)
Implement the infrastructure needed to support portals and mirrors.

Bevy currently supports multiple cameras and rendering to off-screen
render targets, so one might naïvely think that the engine has support
for portals and mirrors already. However, Bevy is missing two key
features that enable portals and mirrors at present:

1. Bevy has support for neither custom clip planes nor oblique clip
planes. This prevents the construction of proper portals or mirrors, as
meshes that intersect the portal plane must be clipped to render
properly.

2. Bevy has no support for cameras that invert the culling mode, so
meshes that are reflected across a plane will render inside-out.

This PR addresses the two issues above:

1. This commit introduces a new field on `PerspectiveProjection`,
`near_plane`, which allows the application to specify a custom near
plane. That feature fully enables [Lengyel oblique clipping], which is
the most optimal way to achieve a custom near clipping plane. It allows
us to avoid having to support custom clip planes, which are often
implemented inefficiently in hardware.

2. This patch adds a new field on the `Camera` component,
`invert_culling`. This field causes the Bevy renderer to invert the
front face setting when rendering the objects visible from that camera.
When coupled with an appropriately-set [Householder matrix] on the
camera, this allows correct rendering of objects reflected across a
plane.

Additionally, this PR adds a new function to `bevy_math::mat3`,
`reflection_matrix`. This generates the matrix that reflects objects
across a plane, suitable for encoding into a `Transform`. It's fully
documented for ease of use.

Finally, a new example, `mirror`, has been added. This example is a
complete instance of a working mirror, combining a camera with a
Householder matrix, oblique projection, and inverted culling with a
custom material to render an animated mesh and its planar reflection.
The camera and mesh may be moved with the mouse, and the off-screen
render target that stores the rendered contents of the mirror world is
properly resized when the user resizes the window.

[Lengyel oblique clipping]:
https://terathon.com/lengyel/Lengyel-Oblique.pdf

[Householder matrix]:
https://en.wikipedia.org/wiki/Householder_transformation

<img width="2564" height="1500" alt="Screenshot 2025-12-05 212155"
src="https://github.com/user-attachments/assets/35652b58-a9a5-415a-bdff-367889a23b9f"
/>
2025-12-09 23:08:15 +00:00
charlotte 🌸 8ca07c4727 Add flag to force MSAA writeback (#22066)
For Processing, we sometimes need to write out of band to the main view
target, which will get clobbered if it's the first camera and writeback
doesn't occur.
2025-12-08 23:57:25 +00:00
Saratii 80e17cce42 Add optimized is_in_half_space_identity, contains_aabb_identity, and intersects_obb_identity (#21249)
# Objective

This adds an optimized version of is_in_half_space,
`Frustum::contains_aabb_identity`, `Aabb::is_in_half_space_identity`,
and `Frustum::intersects_obb_identity` that takes advantage of how
calling with IDENTITY (common) reduces the amount of matrix
multiplications.

## Solution

Add a specialized function without touching the original that assumes
Identity was passed to simplify math.

## Testing

I use this function extensively in my own project. I asserted the old
function called with identity always returns the same as my new
function.
I bench marked and profiled the usage in my real application and noticed
a 16% speed up on Linux and 20% on windows for contains_aabb.
I bench marked intersects_obb_identity and noticed a 39% increase on
linux and 38% on windows.
Both functions have unit tests that assert calling with identity yields
the same result in both versions.

---------

Co-authored-by: François Mockers <francois.mockers@vleue.com>
Co-authored-by: IQuick 143 <IQuick143cz@gmail.com>
2025-12-08 22:51:33 +00:00
re0312 2219069c72 remove deprecated APIs in 0.17 (#21723)
# Objective

- removes all APIs that were marked as deprecated in 0.17,
2025-11-03 18:51:23 +00:00