Commit Graph

251 Commits

Author SHA1 Message Date
Luo Zhihao 0d00c9109f Fix nightly clippy and clippy::result_large_err again (#24624)
# Objective

#24206 boxes the large error in `GltfError` but it's not sufficient
after https://github.com/rust-lang/rust-clippy/issues/17070 is fixed.

## Solution

Box `AssetLoadError::RequestedHandleTypeMismatch` so that it is reduced
to 120 bytes which is below 128 bytes.

Also fix some other trivial clippy warnings.

## Testing

Use the latest nightly clippy which has
https://github.com/rust-lang/rust-clippy/issues/17070 fixed:
```
cargo +nightly clippy --workspace --all-features --all-targets -- -D warnings
```
2026-06-17 04:40:59 +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
Rostyslav Toch 98639670f5 Add many_meshlet_materials stress test example (#23792)
# Objective

- Introduce a dedicated example to measure performance overhead when
dealing with a high number of meshlet instances and unique materials.

## Solution

- Added a new example `many_meshlet_materials` that spawns a
configurable grid of meshlet bunnies.

## Testing

- `SystemInfo { os: "Linux (CachyOS Linux rolling)", kernel:
"6.19.10-1-cachyos", cpu: "AMD Ryzen 7 5800H with Radeon Graphics",
core_count: "8", memory: "15.0 GiB" }`
- `AdapterInfo { name: "AMD Radeon Graphics (RADV RENOIR)", vendor:
4098, device: 5688, device_type: IntegratedGpu, device_pci_bus_id:
"0000:03:00.0", driver: "radv", driver_info: "Mesa 26.0.3-arch2.2",
backend: Vulkan, subgroup_min_size: 64, subgroup_max_size: 64,
transient_saves_memory: false }`
- `cargo run --features=meshlet,https --release --example
many_meshlet_materials`

---

## Showcase

Example works perfectly with 5x5 grid.

<img width="1024" height="599" alt="image"
src="https://github.com/user-attachments/assets/948b42b7-e88e-4a91-9fb7-f1c1b4d27e32"
/>

The issue begins when grid count increases to 10x10. Meshes start
flickering.

<img width="1024" height="598" alt="image"
src="https://github.com/user-attachments/assets/055df47f-39dc-41bc-be05-1978ec352e3a"
/>

With 50x50 meshes, the whole screen flickers.

<img width="1024" height="595" alt="image"
src="https://github.com/user-attachments/assets/7d685a2b-7389-43fe-b04e-a5987136f459"
/>

When running with unique materials, performance significantly drops on
my hardware.

<img width="2224" height="1298" alt="image"
src="https://github.com/user-attachments/assets/8d5b4677-7851-4234-9467-992f8db62a9d"
/>

---------

Co-authored-by: Carter Anderson <mcanders1@gmail.com>
Co-authored-by: François Mockers <francois.mockers@vleue.com>
2026-05-22 12:13:26 +00:00
ickshonpe 649586a47d Add an overflow-flip argument to many_buttons (#24244)
# Objective

Add an option enabling clipping on the button nodes in `many_buttons`.

To help identify any performance regressions due to UI clipping changes.

## Solution

Add an option that sets overflow to clip for all the button nodes in
`testbed_ui`.
2026-05-21 10:23:42 +00:00
ickshonpe b00ff93c87 Remove new_with_ prefix from the TextLayout constuctor functions (#24049)
# Objective

Remove the `new_with_` prefixes from the `TextLayout` constuctor
functions. Generally, the "new" part is redundant and "with" is used by
fluent APIs.

## Solution

Just delete the prefixes, shorten the names (all on `TextLayout`).
* `new_with_justify` -> `justify`
* `new_with_linebreak` -> `linebreak`
* `new_with_no_wrap` -> `no_wrap`
2026-05-01 21:08:18 +00:00
Alice Cecile 85212f763e Fix CI errors for Rust 1.95 (#23829)
# Objective

CI is
[broken](https://github.com/bevyengine/bevy/actions/runs/24511176705/job/71642514168?pr=23785)

## Solution

Look at the CI errors. Fix them.

## Testing

Let's see if CI is green!
2026-04-16 17:46:57 +00:00
Carter Anderson 535cf401cc Reframe old "scene" terminology as "world serialization" (#23630)
Part 2 of #23619 

In **Bevy 0.19** we are landing a subset of Bevy's Next Generation Scene
system (often known as BSN), which now lives in the `bevy_scene` /
`bevy::scene` crate. However the old `bevy_scene` system still needs to
stick around for a bit longer, as it provides some features that Bevy's
Next Generation Scene system doesn't (yet!):

1. It is not _yet_ possible to write a World _to_ BSN, so the old system
is still necessary for "round trip World serialization".
2. The GLTF scene loader has not yet been ported to BSN, so the old
system is still necessary to spawn GLTF scenes in Bevy.

For this reason, we have renamed the old `bevy_scene` crate to
`bevy_world_serialization`. If you were referencing `bevy_scene::*` or
`bevy::scene::*` types, rename those paths to
`bevy_world_serialization::*` and `bevy::world_serialization::*`
respectively.

Additionally, to avoid confusion / conflicts with the new scene system,
all "scene" terminology / types have been reframed as "world
serialization":

- `Scene` -> `WorldAsset` (as this was always just a World wrapper)
- `SceneRoot` -> `WorldAssetRoot`
- `DynamicScene` -> `DynamicWorld`
    - `DynamicScene::from_scene` -> `DynamicWorld::from_world_asset`
- `DynamicSceneBuilder` -> `DynamicWorldBuilder`
- `DynamicSceneRoot` -> `DynamicWorldRoot`
- `SceneInstanceReady` -> `WorldInstanceReady`
- `SceneLoader` -> `WorldAssetLoader`
- `ScenePlugin` -> `WorldSerializationPlugin`
- `SceneRootTemplate` -> `WorldAssetRootTemplate`
- `SceneSpawner` -> `WorldInstanceSpawner`
- `SceneFilter` -> `WorldFilter`
- `SceneLoaderError` -> `WorldAssetLoaderError`
- `SceneSpawnError` -> `WorldInstanceSpawnError`

Note that I went with `bevy_world_serialization` over
`bevy_ecs_serialization`, as that is what all of the internal features
described themselves as. I think it is both more specific and does a
better job of making itself decoupled from `bevy_ecs` proper.
2026-04-04 00:31:47 +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
andristarr a9192fa63f refactor: remove threshold configuration from StaticTransformOptimiza… (#23193)
# Objective

- Fixes #23192 by removing the threshold from the associated functions

## Solution

- We are removing the threshold all together, so the optimization is
either enabled or disabled now.

## Migration guide
- Don't rely on from_threshold calls, either have the optimizations
enabled or disabled.

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: François Mockers <francois.mockers@vleue.com>
2026-03-04 23:43:45 +00:00
Patrick Walton 107cc2539f Batch meshes with morph targets. (#23023)
Right now, Bevy can't batch meshes with morph targets together, because
the morph targets are packed into a morph texture, which is
non-bindless. To fix this, this PR adds support for batching morph
targets together on platforms with storage buffers. Morph displacements
are allocated using the mesh allocator, just like vertex and index
buffers are.

This PR also improves the API for supplying morph targets to a mesh.
Today, the application must create a `MorphTargetImage` explicitly to
store the morph targets, which is cumbersome. This patch changes the
`Mesh` API to instead take morph targets as a flat vector. Internally,
if the platform doesn't support storage buffers, the morph targets are
converted to a morph target image; if the platform does support storage
buffers, however, the morph targets are packed in the mesh allocator.

This patch is a prerequisite for skin caching, because skin caching also
applies to morph targets, and skin caching wants to skin many meshes at
a time. Using a morph target image would either require batch breaking
logic or bindless, neither of which are desirable for a feature that be
simple and work on WebGPU, so I opted to make morph targets batchable
instead.

On the `many_morph_targets` example, I went from 5.55 ms/frame to 2.80
ms/frame, a 1.98x speedup.

---------

Co-authored-by: Greeble <166992735+greeble-dev@users.noreply.github.com>
2026-02-28 21:14:10 +00:00
Greeble b8153cbce4 Add motion blur option to various stress tests (#23060)
## Objective

Add a motion blur option to various stress tests. This is useful for
benchmarking, but also for bug reproduction - not many examples use
motion blur. In particular, I want this to repro a suspected bug with
motion blur and morph targets.

## Solution

Add an opt-in `--motion-blur` argument to `many_foxes`,
`many_morph_targets` and `many_cubes`.

I'm a bit unhappy about the cut and paste. Maybe there should be some
shared utilities for the stress tests.

## Testing

Tested on Native/Win10/Nvidia/Vulkan.

```sh
cargo run --example many_foxes -- --motion-blur
cargo run --example many_morph_targets -- --motion-blur
cargo run --example many_many_cubes -- --motion-blur
```
2026-02-24 18:31:01 +00:00
Patrick Walton b6816816cc Add the ability to set the number of cubes in many_cubes. (#23101)
Surprisingly, despite the number of settings in `many_cubes`, it was
missing the most obvious one: setting the number of cubes. This commit
adds this feature. It's a little more complicated than one might expect,
because of the way the number of cubes is never actually set directly,
as well as the fact that the demo supports three different patterns.
2026-02-24 01:25:14 +00:00
Gonçalo Rica Pais da Silva f255b8e57a Upgrade glam, hexasphere, rand & uuid to latest versions (#22928)
# Objective

- `glam`, `hexasphere` & `rand` have released their latest versions,
update Bevy to support them.

## Solution

- The above have been updated to their compatible versions. `rand_distr`
updated as well to match `rand` v0.10 support.
- `rand_chacha` is soft deprecated and no longer used by `rand`, so its
usage has been changed to `chacha20` to match `rand` dep tree.
- `uuid` is in the process of updating to `getrandom` v0.4, which `rand`
v0.10 supports. This PR remains in draft until a new `uuid` release hits
crates.io.
- `RngCore` is now `Rng`, and `Rng` is now `RngExt`, so this required
updating across many files.
- `choose_multiple` method is deprecated, changed to `sample`.

## Testing

- Chase all compiler errors, since this should not regress any already
existing behaviour.
- This must pass CI without regressions.

## Additional Notes

`getrandom` v0.4 doesn't add anything new for Web WASM support, so the
same `wasm_js` feature is used.
2026-02-19 22:17:25 +00:00
Greeble 0339de9571 Add many_morph_targets stress test (#18536)
## Objective

I wanted to benchmark the morph target changes in #18465. I also wanted
to test morph targets on multiple meshes, which is not covered by
existing examples.

## Solution

Add a stress test for morph targets, similar to `many_cubes` and
`many_foxes`. Spawns a ton of meshes (defaults to 1024) and animates
their morph target weights.


![425571366-b043c16c-6e6a-491e-a0bd-5ece630d7bf8](https://github.com/user-attachments/assets/86ef26a4-ad00-46fa-9e5a-0aa4238023e3)

## Testing

```sh
cargo run --example many_morph_targets

# Test different mesh counts.
cargo run --example many_morph_targets -- --count 42
```

Tested on Win10/Vulkan/Nvidia, Wasm/WebGL/Chrome/Win10/Nvidia.
2026-02-16 22:55:00 +00:00
Rostyslav Lesovyi 9fd2637846 Add tools to avoid unnecessary AssetEvent::Modified events that lead to rendering performance costs (#16751) (#22460)
# Objective

- Fixes #16751

## Solution

- `Assets::get_mut` now returns a wrapper `AssetMut` type instead of
`&mut impl Asset`.
- `AssetMut` implements `Deref` and `DerefMut`.
- `DerefMut` marks assets as changed.
- when dropped `AssetMut` will add `AssetEvent::Modified` event to a
queue only in case asset was marked as changed.

## Testing

- Did you test these changes? If so, how?
  - No unit tests were added, change is pretty straightforward.
  - Test project: https://github.com/MatrixDev/bevy-feature-16751-test.
    - With change: ~100 fps.
    - Without change: ~15 fps.
- Are there any parts that need more testing?
- I don't really see how this can break anything or add a measurable
overhead.
- `AssetEvent::Modified` will now be sent after the asset was modified
instead of before. It should not affect anything but still worth noting.
- How can other people (reviewers) test your changes? Is there anything
specific they need to know?
- Have a big amount of entities that constantly update their materials.
- Properties of those materials should be animated in a stepped maned
(like changing color every 0.1 seconds).
  - Update material only if value has actually changed:
```rust
if material.base_color != new_color {
    material.base_color = new_color;
}
```
- If relevant, what platforms did you test these changes on, and are
there any important ones you can't test?
  - tested on macos (Mackbook M1)
  - not a platform-specific issue

PS: This is my first PR, so please don’t judge.
2026-02-10 18:39:37 +00:00
ickshonpe 6ca4769128 Minimal responsive FontSize support (#22614)
# Objective

Add responsive font sizes supporting rem and viewport units to
`bevy_text` with minimal changes to the APIs and systems.

## Solution

Introduce a new `FontSize` enum:

```rust
pub enum FontSize {
    /// Font Size in logical pixels.
    Px(f32),
    /// Font size as a percentage of the viewport width.
    Vw(f32),
    /// Font size as a percentage of the viewport height.
    Vh(f32),
    /// Font size as a percentage of the smaller of the viewport width and height.
    VMin(f32),
    /// Font size as a percentage of the larger of the viewport width and height.
    VMax(f32),
    /// Font Size relative to the value of the `RemSize` resource.
    Rem(f32),
}
```

This replaces the `f32` value of `TextFont`'s `font_size` field.

The viewport variants work the same way as their respective `Val`
counterparts.

`Rem` values are multiplied by the value of the `RemSize` resource
(which newtypes an `f32`).

`FontSize` provides an `eval` method that takes a logical viewport size
and rem base size and returns an `f32` logical font size. The resolved
logical font size is then written into the `Attributes` passed to Cosmic
Text by `TextPipeline::update_buffer`.

Any text implementation using `bevy_text` must now provide viewport and
rem base values when calling `TextPipeline::update_buffer` or
`create_measure`.

`Text2d` uses the size of the primary window to resolve viewport values
(or `Vec2::splat(1000)` if no primary window is found). This is a
deliberate compromise, a single `Text2d` can be rendered to multiple
viewports using `RenderLayers`, so it's difficult to find a rule for
which viewport size should be chosen.

### Change detection 

`ComputedTextBlock` has two new fields: `uses_viewport_sizes` and
`uses_rem_sizes`, which are set to true in `TextPipeline::update_buffer`
iff any text section in the block uses viewport or rem font sizes,
respectively.

The `ComputedTextBlock::needs_rerender` method has been modified to take
take two bool parameters:
```rust
    pub fn needs_rerender(
        &self,
        is_viewport_size_changed: bool,
        is_rem_size_changed: bool,
    ) -> bool {
        self.needs_rerender
            || (is_viewport_size_changed && self.uses_viewport_sizes)
            || (is_rem_size_changed && self.uses_rem_sizes)
    }
 ```
This ensures that text reupdates will also be scheduled if one of the text section's uses a viewport font size and the local viewport size changed, or if one of the text section's uses a rem font size and the rem size changed.

#### Limitations

There are some limitations because we don't have any sort of font style inheritance yet:

* "rem" units aren't proper rem units, and just based on the value of a resource. 
* "em" units are resolved based on inherited font size, so can't be implemented without inheritance support.

#### Notes

* This PR is quite small and not very technical. Reviewers don't need to be especially familiar with `bevy_text`. Most of the changes are to the examples.

* We could consider using `Val` instead of `FontSize`, then we could use `Val`'s constructor functions which would be much nicer, but some variants might not have sensible interpretations in both UI and Text2d contexts. Also we'd have to make `Val` accessible to `bevy_text`.

## Testing

The changes to the text systems are relatively trivial and easy to understand.  I already added a minor change to the `text` example to use `Vh` font size for the "hello bevy" text in the bottom right corner. If you change the size of the window, you should see the text change size in response. The text initially flickers before it updates because of some unrelated asset/image changes that mean that font textures aren't ready until the frame after the text update that changes the font size.

Most of the example migrations were automated using regular expressions, and there are bound to be mistakes in those changes. It's infeasible to check every single example thoroughly, but it's early enough in the release cycle that I don't think we should be too worried if a few bugs slip in.

---------

Co-authored-by: Kevin Chen <chen.kevin.f@gmail.com>
2026-02-02 22:52:33 +00:00
ickshonpe f7be67e820 WinitSettings cleanup (#22650)
# Objective

#### Some cleanup
1. `many_gradients` inserts `WinitSettings` twice.
2. Replace manual configuration where `WinitSettings::continuous()`
could be used.
2026-01-22 18:13:36 +00:00
Patrick Walton 842032d1f7 Assign indices to light probes in the clustered objects list, and refactor the clustering code. (#22621)
At the moment, clustering is a three-step process:

1. `assign_objects_to_clusters` runs on all clusterable objects during
the `PostUpdate` schedule, creating lists of all clusterable objects in
each view.

2. During the extraction phase, `extract_clusters` runs on all views
that had clusters created for them, linearizing the clusters into a list
of `ExtractedClusterableObjectElement::ClusterHeader` commands followed
by other `ExtractedClusterableObjectElement`s, one for each object in
the cluster. Each `ExtractedClusterableObjectElement` specifies the
render world entity for the clusterable object.

3. In the render world, `prepare_clusters` processes all
`ExtractedClusterableObjectElement` commands to create the GPU buffers,
looking up each clustered object in the `GlobalClusterableObjectMeta`
table in order to translate from entity to index.

Unfortunately, there are two main problems with this:

a. Light probes don't have render world entities at all and are instead
tracked in `RenderViewLightProbes` components in the render world. Thus
step (2) silently fails for them.

b. The `GlobalClusterableObjectMeta` table only contains clustered
lights and decals, so even if light probes had render-world entities,
step (3) would still fail.

The end result is that the GPU ends up consulting bogus out-of-bounds
indices that may or may not actually refer to the light probe when
traversing clusters.

This PR fixes the issues:

* I extended `extract_clusters` to support light probes, by adding
`ExtractedClusterableObjectElement::ReflectionProbe` and
`ExtractedClusterableObjectElement::IrradianceVolume` variants. These
variants reference the *main* world entities for light probes, since no
render-world entities exist for them.

* When processing the new `ExtractedClusterableObjectElement` commands,
`prepare_clusters` uses the `RenderViewLightProbes` to find the index in
the reflection probe or irradiance volume table as appropriate and
supply it to the GPU. Note that this step might fail if a texture that
the light probe needs hasn't been loaded yet. In this case, an index of
-1 is stored, and the shader skips it. This isn't the optimum behavior;
ideally we wouldn't cluster such objects at all. However, it was a
minimally-invasive change.

* I renamed types that referenced clusterable objects to refer to
clusterable *lights* specifically if the types only dealt with lights,
to reduce confusion in the future.

* The `VisibleClusterableObjects` type is currently overloaded to both
serve as a component, in which case it contains *all* clusterable
objects associated with a view, and to serve as a container for the
objects associated with a cluster. Not only is this confusing, but it's
also wasteful, as there's bookkeeping that the type does that's not
needed when it's serving as a component. I split the type into the
component `VisibleClusterableObjects` and the helper structure
`ObjectsInCluster`, and encapsulated logic within each type in order to
make `assign_objects_to_clusters` easier to understand.

* The `gather_light_probes` system performed its own frustum culling on
light probes separately from the frustum culling that
`assign_objects_to_clusters` also does. This was wasteful and confusing,
especially since the frustum culling algorithms differed between the two
systems, so I simplified the logic so that `assign_objects_to_clusters`
fills out a table for `gather_light_probes` to use.

* `compute_radiances` in `environment_map.wgsl` was broken, as it
neglected to set `light_from_world` to the identity matrix when falling
back to the view environment map when a reflection probe wasn't found.
This would cause specular to vanish in some cases (e.g. in the
`reflection_probes` example). I fixed the problem.

This commit is a prerequisite for #22610, as multiple light probes are
too broken without it.
2026-01-22 17:28:02 +00:00
ickshonpe 78febd262d FontAtlasSet total font data size helper function (#22598)
# Objective

Add a function that returns the sum of the sizes of all the image data
for all the fonts.

Needed for an LRU buffer implementation that would remove the text
atlases for the least recently used font face when the total font image
data exceeds a set limit.

## Solution

Add a `total_bytes` method on `FontAtlasSet` that returns the sum of the
lengths of the image data for all fonts.

Implementation might be too naive, I'm not sure, but it seems sufficient
to me.

## Testing

I changed the `many_text2d` example's `info!` output to include the
total font image bytes.
2026-01-21 05:09:59 +00:00
Damon 777098ebc7 SpriteMesh + SpriteMaterial (#22484)
# Objective

The objective is reworking `Sprites` to use the `Mesh` backend instead
of the current, severely outdated, one.

The `2D Ecosystem` would grately benefit from sprites having access to
all the mesh utilities, such as `ExtensionMaterials`, without having to
be updated and maintained separately.

We could allow things such as attaching a shader to a Sprite to give it
an outline, which is a very accessible feature in other engines that
Bevy currently lacks any streamlined support for (#16170).

This PR notably contributes to #13265, while also completely fixing
#15021 and possibly other sprite-related issues.

## Solution

I've introduced a new `SpriteMesh` component. It is an exact replica of
`Sprite`, having the same API, except for an extra `alpha_mode` field.

The purpose of this is to eventually remove `Sprite` and all its
backend, and rename `SpriteMesh` to `Sprite`, which should hopefully be
a seamless transition for our end-users.

Internally, `SpriteMesh` is just an abstraction over adding a `Mesh2d`
and the new `SpriteMaterial` to an entity, and a user should be able to
just do the latter if they prefer, essentially merging the ease-of-use
of `Sprites` and flexibility of `Meshes`.

In its current state, `SpriteMesh` is completely usable and produces the
same behavior as `Sprite`. There is however a notable exception:

- The `max_corner_scale` parameter when slicing now produces different
results. Before, in the
[`sprite_slice`](https://bevy.org/examples/2d-rendering/sprite-slice/)
example, it was set to `0.2`, causing the corners to be half of their
scale. I am fairly confident this was a bug since I haven't found any
correlation between `0.2` and what was being displayed. Instead, the
`SpriteMesh` now requires `0.5` to produce the same result, `0.2`
resulting in a much smaller corner (a fifth of its size).

| Sprite, `max_corner_scale=0.2` | SpriteMesh, `max_corner_scale=0.2` |
SpriteMesh, `max_corner_scale=0.5` |
| ------------- | ------------- | ------------- |
| <img width="347" height="262" alt="image"
src="https://github.com/user-attachments/assets/76cc6ffe-155b-4ba5-88ff-494248948efa"
/> | <img width="337" height="256" alt="image"
src="https://github.com/user-attachments/assets/4a28739a-b058-4286-908c-8d6e83295b86"
/> | <img width="329" height="248" alt="image"
src="https://github.com/user-attachments/assets/7c758e33-1379-4cff-8f53-13f678283264"
/>|

Otherwise, replacing a `Sprite` with a `SpriteMesh` should have no
visual difference.

There were also a few bugs that I encountered with the `Sprites`,
related to scaling and flipping them a certain way producing
weird behavior such as the sprite completely disappearing. These are not
present in the new `SpriteMesh`.

While the API and visual behavior remain the same, the underlying logic
has been completely rewritten:
- No custom `RenderApp` logic is used anymore,
`SpriteMesh`/`SpriteMaterial` exclusively uses the API provided by
`Material2d`.

- `Slicing` and `Tiling` are now done purely inside the shader through
UV mapping and manipulation (all done in constant time). Before, they
generated multiple `SpriteInstances` for each tile / slice, being
extremely unmaintainable and adding significant overhead. This fixes
#15021.

## Testing

- I mostly ran tests by overlapping `Sprites` with `SpriteMeshes` having
the exact same configuration and making sure there are no differences in
behavior. However, I did essentially have to rework the whole `Sprite`
API to work with `Meshes` and I can't guarantee that I haven't missed
anything.

- I tested the new `SpriteMeshes` in
[`bevymark`](https://bevy.org/examples/stress-tests/bevymark/). I'd say
there's an ~2-2.5x increase in performance over `Sprites` when alpha
masking them, and a ~0.5x decrease when alpha blending. However, alpha
masking is definitely the more common choice for Sprites, and the alpha
blending performance should be improved significantly in the near future
as part of #13265.

## Problems 

These are a few notable problems that should ideally be resolved before
fully replacing `Sprite` with the new `SpriteMesh`:

- The `TextureAtlasLayout` is an asset that the `SpriteMaterial` depends
on to generate its `BindGroup`. It is currently just read when the
`SpriteMesh` is added (or changed) and baked into the `SpriteMaterial`,
which means there's no hot reloading for it (changing a
`TextureAtlasLayout` won't update the material). I believe this is best
fixed by reading the `TextureAtlasLayout` asset inside
`as_bind_group_shader_type()`, same as the `Sprite's` `Image`.

- Creating a new `SpriteMaterial` for each new `SpriteMesh` and its
changes is really bad for performance, which is why I've added a
rudimentary material caching solution through a `HashMap<SpriteMesh,
Handle<SpriteMaterial>`. However, I do believe there's significant
potential for further optimizations in this area.

- Currently, a significant chunk of `SpriteMaterialUniform` (about `40
bytes`) is taken by data used for slicing. This data is useless for
`Sprites` which don't use slicing, and so it adds overhead. Ideally,
this data would be placed in a separate binding that is only written and
read on the GPU if a certain `ShaderDef` is set.

---------

Co-authored-by: IceSentry <IceSentry@users.noreply.github.com>
2026-01-20 19:58:27 +00:00
Aevyrie a88af65738 Contact Shadows (#22382)
# Objective

- Implement contact shadows to add fine shadow detail where shadow
cascades cannot.

## Solution

- Extend our existing pbr implementation using our existing raymarching
functions.

---

## Showcase

<img width="1824" height="1180" alt="image"
src="https://github.com/user-attachments/assets/e93b79c5-c596-4a9e-b94d-20bdde1d863b"
/>

<img width="1824" height="1180" alt="image"
src="https://github.com/user-attachments/assets/0fd7dffa-60b8-4b92-8fad-7f993d4d89dd"
/>


https://github.com/user-attachments/assets/e74b190d-9ae3-4aaf-97f0-b520930a0667


https://github.com/user-attachments/assets/e80ccb26-bbaa-4d25-a823-8ea12354c5b9


https://github.com/user-attachments/assets/b04f4b00-92bd-4a2f-b7dd-5157d8fbe0ab

<img width="1073" height="685" alt="image"
src="https://github.com/user-attachments/assets/b7629908-dd32-48db-8ee7-a4d2dd8f66c2"
/>

<img width="1073" height="685" alt="image"
src="https://github.com/user-attachments/assets/3de0258e-9191-4180-ac57-41b32e1205bd"
/>

<img width="1073" height="685" alt="image"
src="https://github.com/user-attachments/assets/951477f9-e9a9-426f-ae8d-18ae50cc7b85"
/>

<img width="1073" height="685" alt="image"
src="https://github.com/user-attachments/assets/2291453c-da57-4fcc-a6b0-f60f6eac6cbb"
/>

<img width="1073" height="685" alt="image"
src="https://github.com/user-attachments/assets/5820cdff-ea54-4294-b520-2a8d8dc24996"
/>

<img width="1073" height="685" alt="image"
src="https://github.com/user-attachments/assets/3ea16481-7689-4e99-87e2-1589f1532e4c"
/>

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: charlotte 🌸 <charlotte.c.mcelwain@gmail.com>
2026-01-13 21:51:39 +00:00
ickshonpe e5d4dfd7a6 Minimal Font Families, Font Queries, Collections, System Fonts, Stretch, and Slant support (#22156)
# Objective

Implement support for the remaining missing text features with minimal
changes.

## Solution

`TextFont` has been expanded to include new fields:
```rust
pub struct TextFont {
    pub font: FontSource,
    pub font_size: f32,
    pub weight: FontWeight,
    pub width: FontWidth,
    pub style: FontStyle,
    pub font_smoothing: FontSmoothing,
    pub font_features: FontFeatures,
}
```

FontSource has two variants: Handle, which identifies a font by asset
handle, and Family, which selects a font by its family name.

`FontWidth` is a newtype struct representing OpenType font stretch
classifications ranging from ULTRA_CONDENSED (50%) to ULTRA_EXPANDED
(200%).

`FontStyle` is an enum used to set the slant style of a font, either
`Normal`, `Italic`, or `Oblique`.

The system font support is very barebones. You load them using the
`CosmicFontSystem` resource:
```rust
font_system.db_mut().load_system_fonts()
```
Then they are available to be selected by family name using
`FontSource::Family`.

### Other changes

* `TextPipelines`'s `glyph_info` field has been removed. There is no
need to collect the section infos or perform any querys during text
layout updates, so that code has been removed as well.

* `update_text_layout_info` used some `try_for_each` with some nested
closures which was unnecessarily complicated again. They've been
replaced with a regular for loop.

* After font assets are loaded there's a new system
`load_font_assets_into_fontdb_system` that automatically adds them to
cosmic text's font database. Then they are available to be looked up by
family name as well as by asset handle.

* There aren't are performance motivated changes but layout updates seem
to be overall significantly more efficient now, with a slight regression
for very large numbers of short, single section text entities.

* Font texture atlases are no longer automatically cleared when the font
asset they were generated from is removed. There is no way to remove
individual fonts from cosmic text's `FontSystem`, so the font is still
accessible using the family name with `FontSource::family` and removing
the text atlases naively could cause a panic since rendering expects
them to be present.

## Testing

```
cargo run --example font_query
```
---

## Showcase

<img width="1229" height="591" alt="font-query"
src="https://github.com/user-attachments/assets/23f5aaa2-fdb8-4448-9b4e-9d65d6431107"
/>

---------

Co-authored-by: Thierry Berger <contact@thierryberger.com>
2026-01-03 22:38:53 +00:00
Aevyrie a912a48401 Bevymark 3D (#22298)
# Objective

- Add a stress test that exercises the 3d mesh pipeline for dynamic
scenes.
- Large static scenes like caldera hotel don't expose performance issues
when many meshes are moving.
- Give us a way to benchmark PRs like
   - https://github.com/bevyengine/bevy/pull/22297
   - https://github.com/bevyengine/bevy/pull/22281
   - https://github.com/bevyengine/bevy/pull/22226

## Solution

- Make a 3d version of `bevymark`, sticking to the existing patterns as
closely as possible.

## Testing

<img width="1072" height="684" alt="image"
src="https://github.com/user-attachments/assets/41214ba9-ffad-471d-a320-1f007490dead"
/>

---------

Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2025-12-30 01:11:12 +00:00
François Mockers b0acc82097 Fix many_foxes animation controls (#22292)
# Objective

- Fix #22285

## Solution

- Example is relying on component `AnimationTransitions` which wasn't
added. Add it
2025-12-30 01:04:30 +00:00
Aevyrie 83e57e0175 Optimize transform propagation for dynamic scenes (#22281)
# Objective

- Follow up from previous transform optimization (#18589), make the
`mark_dirty_trees` system more intelligent - don't run this expensive
static scene optimization for dynamic scenes.
- Using a threshold was mentioned as a follow up in that PR, and we also
want this threshold to be user-configurable.
- This was not implemented previously because the optimizations were
still large improvements even in dynamic scenes thanks to the improved
parallelism #17840

## Solution

- Don't run static scene optimization (dirty tree tracking) for very
dynamic scenes - defined here as scenes where more than 30% of objects
have their `Transform` updated.
- This is configurable with a percentage threshold, or it can be
unconditionally enabled or disabled when setting to `0.0` or `1.0` to
avoid the cost of computing the threshold.
- For dynamic scenes, this makes transform prop much faster, twice as
fast in the stress tests shown here.

## Testing

transform_hierarchy stress tests, all of these cases spawn about a
quarter million entities:

- humanoids_active - dynamic scene that should be faster than `main`:
<img width="609" height="395" alt="image"
src="https://github.com/user-attachments/assets/bf3d6b93-aa09-4440-b8ac-18af7e46a00f"
/>

- humanoids_inactive - static scene that should be unchanged from
`main`:
<img width="631" height="377" alt="image"
src="https://github.com/user-attachments/assets/a0306109-600b-4cdd-a217-5cc15e269bca"
/>

- humanoids_mixed - half dynamic scene that should be faster than `main`
<img width="604" height="372" alt="image"
src="https://github.com/user-attachments/assets/2751ece2-d4b9-4daa-af24-fe379eaf75b2"
/>

- large_tree - dynamic scene (50% of entities are moved) we expect to
see improvements
<img width="665" height="371" alt="image"
src="https://github.com/user-attachments/assets/c6b08abe-eb1d-44fb-be36-457f9d5ba78e"
/>
2025-12-30 00:59:44 +00:00
ickshonpe 5436f6a952 Name the fields of FontAtlasKey (#22161)
# Objective

Name the fields of the `FontAtlasKey` struct.

## Solution

Named the fields and added doc comments.
2025-12-17 18:41:57 +00:00
Antun Matanović 7d84e5bed1 Update rotate_cubes system to use par_iter_mut() to reduce its impact on performance (#21755)
Quick follow-up to #21745.

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2025-11-05 21:20:36 +00:00
Antun Matanović ea953aace0 Add a dense layout to the many_cubes stress test (#21745)
# Objective

- Test the impact that many overlapping cubes, all within the camera
frustum, have on visibility/occlusion calculations, especially with
continuous rotation and shadows enabled.

## Solution

- Add a dense cube layout using a static camera, and a toggle for
individual cube rotation.

## Testing

- I ran the test in several stages, each causing a considerable
performance drop.
  - Stage 1: using just the dense layout.
  - Stage 2: using the dense layout with rotation or shadows enabled.
- Stage 3: using the dense layout with both rotation and shadows
enabled.

```
cargo run --release --example many_cubes -- --layout dense --rotate-cubes --shadows
```

---

## Showcase
<img width="1904" height="940" alt="image"
src="https://github.com/user-attachments/assets/6fc811aa-81fa-4130-a6e0-0a017bc99f19"
/>
2025-11-05 18:12:48 +00:00
Eagster 279a8678d4 Improved Entity Lifecycle: remove flushing, support manual spawning and despawning (#19451)
# Objective

This is the next step for #19430 and is also convinient for #18670.

For context, the way entities work on main is as a "allocate and use"
system. Entity ids are allocated, and given a location. The location can
then be changed, etc. Entities that are free have an invalid location.
To allocate an entity, one must also set its location. This introduced
the need for pending entities, where an entity would be reserved,
pending, and at some point flushed. Pending and free entities have an
invalid location, and others are assumed to have a valid one.

This paradigm has a number of downsides: First, the entities metadata
table is inseparable from the allocator, which makes remote reservation
challenging. Second, the `World` must be flushed, even to do simple
things, like allocate a temporary entity id. Third, users have little
control over entity ids, only interacting with conceptual entities. This
made things like `Entities::alloc_at` clunky and slow, leading to its
removal, despite some users still having valid need of it.

So the goal of this PR is to:
- Decouple `Entities` from entity allocation to make room for other
allocators and resolve `alloc_at` issues.
- Decouple entity allocation from spawning to make reservation a moot
point.
- Introduce constructing and destructing entities, in addition to
spawn/despawn.
- Change `reserve` and `flush` patterns to `alloc` and `construct`
patterns.

It is possible to break this up into multiple prs, as I originally
intended, but doing so would require lots of temporary scaffolding that
would both hurt performance and make things harder to review.

## Solution

This solution builds on #19433, which changed the representation of
invalid entity locations from a constant to `None`.

There's quite a few steps to this, each somewhat controversial:

### Entities with no location

This pr introduces the idea of entity rows both with and without
locations. This corresponds to entities that are constructed (the row
has a location) and not constructed (the row has no location). When a
row is free or pending, it is not constructed. When a row is outside the
range of the meta list, it still exists; it's just not constructed.

This extends to conceptual entities; conceptual entities may now be in
one of 3 states: empty (constructed; no components), normal
(constructed; 1 or more components), or null (not constructed). This
extends to entity pointers (`EntityWorldMut`, etc): These now can point
to "null"/not constructed entities. Depending on the privilege of the
pointer, these can also construct or destruct the entity.

This also changes how `Entity` ids relate to conceptual entities. An
`Entity` now exists if its generation matches that of its row. An
`Entity` that has the right generation for its row will claim to exist,
even if it is not constructed. This means, for example, an `Entity`
manually constructed with a large index and generation of 0 *will* exist
if it has not been allocated yet.

### `Entities` is separate from the allocator

This pr separates entity allocation from `Entities`. `Entities` is now
only focused on tracking entity metadata, etc. The new
`EntitiesAllocator` on `World` manages all allocations. This forces
`Entities` to not rely on allocator state to determine if entities
exist, etc, which is convinient for remote reservation and needed for
custom allocators. It also paves the way for allocators not housed
within the `World`, makes some unsafe code easier since the allocator
and metadata live under different pointers, etc.

This separation requires thinking about interactions with `Entities` in
a new way. Previously, the `Entities` set the rules for what entities
are valid and what entities are not. Now, it has no way of knowing.
Instead, interaction with `Entities` are more like declaring some
information for it to track than changing some information it was
already tracking. To reflect this, `set` has been split up into
`declare` and `update`.

### Constructing and destructing

As mentioned, entities that have no location (not constructed) can be
constructed at any time. This takes on exactly the same meaning as the
previous `spawn_non_existent`. It creates/declares a location instead of
updating an old one. As an example, this makes spawning an entity now
literately just allocate a new id and construct it immediately.

Conversely, entities that are constructed may be destructed. This
removes all components and despawns related entities, just like
`despawn`. The only difference is that destructing does not free the
entity id for reuse. Between constructing and destructing, all needs for
`alloc_at` are resolved. If you want to keep the id for custom reuse,
just destruct instead of despawn! Despawn, now just destructs the entity
and frees it.

Destructing a not constructed entity will do nothing. Constructing an
already constructed entity will panic. This is to guard against users
constructing a manually formed `Entity` that the allocator could later
hand out. However, public construction methods have proper error
handling for this. Despawning a not constructed entity just frees its
id.

### No more flushing

All places that once needed to reserve and flush entity ids now allocate
and construct them instead. This improves performance and simplifies
things.

### Flow chart

![entity row
lifecycle](https://github.com/user-attachments/assets/3e5397ab-4ec6-4477-91de-3d002d0cf92e)

(Thanks @ItsDoot)

## Testing

- CI
- Some new tests
- A few deleted (no longer applicable) tests
- If you see something you think should have a test case, I'll gladly
add it.

## Showcase

Here's an example of constructing and destructing

```rust
let e4 = world.spawn_null();
world
    .entity_mut(e4)
    .construct((TableStored("junk"), A(0)))
    .unwrap()
    .destruct()
    .construct((TableStored("def"), A(456)))
    .unwrap();
```

## Future Work

- [x] More expansive docs. This should definitely should be done, but
I'd rather do that in a future pr to separate writing review from code
review. If you have more ideas for how to introduce users to these
concepts, I'd like to see them. As it is, we don't do a very good job of
explaining entities to users. Ex: `Entity` doesn't always correspond to
a conceptual entity.
- [ ] Try to remove panics from `EntityWorldMut`. There is (and was) a
lot of assuming the entity is constructed there (was assuming it was not
despawned).
- [ ] A lot of names are still centered around spawn/despawn, which is
more user-friendly than construct/destruct but less precise. Might be
worth changing these over.
- [ ] Making a centralized bundle despawner would make sense now.
- [ ] Of course, build on this for remote reservation and, potentially,
for paged entities.

## Performance

<details>

<summary>Benchmarks</summary>

```txt
critcmp main pr19451 -t 1
group                                                                                                     main                                     pr19451
-----                                                                                                     ----                                     -------
add_remove/sparse_set                                                                                     1.13    594.7±6.80µs        ? ?/sec      1.00    527.4±8.01µs        ? ?/sec
add_remove/table                                                                                          1.08   799.6±15.53µs        ? ?/sec      1.00   739.7±15.10µs        ? ?/sec
add_remove_big/sparse_set                                                                                 1.10    614.6±6.50µs        ? ?/sec      1.00   557.0±19.04µs        ? ?/sec
add_remove_big/table                                                                                      1.03      2.8±0.01ms        ? ?/sec      1.00      2.7±0.02ms        ? ?/sec
added_archetypes/archetype_count/100                                                                      1.01     30.9±0.50µs        ? ?/sec      1.00     30.5±0.44µs        ? ?/sec
added_archetypes/archetype_count/1000                                                                     1.00   638.0±19.77µs        ? ?/sec      1.03   657.0±73.61µs        ? ?/sec
added_archetypes/archetype_count/10000                                                                    1.02      5.5±0.14ms        ? ?/sec      1.00      5.4±0.09ms        ? ?/sec
all_added_detection/50000_entities_ecs::change_detection::Sparse                                          1.02     47.9±1.22µs        ? ?/sec      1.00     46.8±0.40µs        ? ?/sec
all_added_detection/50000_entities_ecs::change_detection::Table                                           1.02     45.4±1.89µs        ? ?/sec      1.00     44.6±0.78µs        ? ?/sec
build_schedule/1000_schedule                                                                              1.02   942.6±11.53ms        ? ?/sec      1.00   925.2±10.35ms        ? ?/sec
build_schedule/100_schedule                                                                               1.01      5.8±0.12ms        ? ?/sec      1.00      5.7±0.12ms        ? ?/sec
build_schedule/100_schedule_no_constraints                                                                1.03   803.1±28.93µs        ? ?/sec      1.00   781.1±50.11µs        ? ?/sec
build_schedule/500_schedule_no_constraints                                                                1.00      5.6±0.31ms        ? ?/sec      1.08      6.0±0.27ms        ? ?/sec
busy_systems/01x_entities_03_systems                                                                      1.00     24.4±1.35µs        ? ?/sec      1.01     24.7±1.35µs        ? ?/sec
busy_systems/03x_entities_03_systems                                                                      1.00     38.1±1.70µs        ? ?/sec      1.04     39.7±1.49µs        ? ?/sec
busy_systems/03x_entities_09_systems                                                                      1.01    111.4±2.27µs        ? ?/sec      1.00    109.9±2.46µs        ? ?/sec
busy_systems/03x_entities_15_systems                                                                      1.00    174.8±2.56µs        ? ?/sec      1.01    176.6±4.22µs        ? ?/sec
contrived/03x_entities_09_systems                                                                         1.00     59.0±2.92µs        ? ?/sec      1.01     59.8±3.03µs        ? ?/sec
contrived/03x_entities_15_systems                                                                         1.00     97.5±4.87µs        ? ?/sec      1.01     98.8±4.69µs        ? ?/sec
contrived/05x_entities_09_systems                                                                         1.00     75.3±3.76µs        ? ?/sec      1.01     76.4±4.11µs        ? ?/sec
despawn_world/10000_entities                                                                              1.32    344.8±4.47µs        ? ?/sec      1.00    261.4±4.91µs        ? ?/sec
despawn_world/100_entities                                                                                1.22      4.3±0.04µs        ? ?/sec      1.00      3.5±0.54µs        ? ?/sec
despawn_world/1_entities                                                                                  1.01    169.6±7.88ns        ? ?/sec      1.00   167.8±11.45ns        ? ?/sec
despawn_world_recursive/10000_entities                                                                    1.20  1723.0±53.82µs        ? ?/sec      1.00  1437.0±26.11µs        ? ?/sec
despawn_world_recursive/100_entities                                                                      1.16     17.9±0.10µs        ? ?/sec      1.00     15.5±0.16µs        ? ?/sec
despawn_world_recursive/1_entities                                                                        1.01   372.8±15.68ns        ? ?/sec      1.00   367.7±16.90ns        ? ?/sec
ecs::entity_cloning::hierarchy_many/clone                                                                 1.03   227.9±24.67µs 1559.9 KElem/sec    1.00   221.1±29.74µs 1607.8 KElem/sec
ecs::entity_cloning::hierarchy_many/reflect                                                               1.00   406.2±23.46µs 875.2 KElem/sec     1.02   413.9±22.45µs 858.9 KElem/sec
ecs::entity_cloning::hierarchy_tall/clone                                                                 1.01     12.2±0.34µs  4.0 MElem/sec      1.00     12.0±1.41µs  4.1 MElem/sec
ecs::entity_cloning::hierarchy_tall/reflect                                                               1.02     15.3±0.39µs  3.2 MElem/sec      1.00     15.0±2.14µs  3.2 MElem/sec
ecs::entity_cloning::single/clone                                                                         1.02  659.0±100.01ns 1481.8 KElem/sec    1.00  643.3±101.49ns 1517.9 KElem/sec
ecs::entity_cloning::single/reflect                                                                       1.03  1135.2±72.17ns 860.2 KElem/sec     1.00  1098.3±65.99ns 889.1 KElem/sec
empty_archetypes/for_each/10                                                                              1.02      8.1±0.57µs        ? ?/sec      1.00      8.0±0.37µs        ? ?/sec
empty_archetypes/for_each/100                                                                             1.01      8.1±0.34µs        ? ?/sec      1.00      8.1±0.28µs        ? ?/sec
empty_archetypes/for_each/1000                                                                            1.03      8.4±0.25µs        ? ?/sec      1.00      8.2±0.29µs        ? ?/sec
empty_archetypes/iter/100                                                                                 1.01      8.1±0.29µs        ? ?/sec      1.00      8.0±0.34µs        ? ?/sec
empty_archetypes/iter/1000                                                                                1.02      8.5±0.31µs        ? ?/sec      1.00      8.4±0.62µs        ? ?/sec
empty_archetypes/iter/10000                                                                               1.01     10.6±1.22µs        ? ?/sec      1.00     10.5±0.49µs        ? ?/sec
empty_archetypes/par_for_each/10                                                                          1.01      8.8±0.49µs        ? ?/sec      1.00      8.7±0.31µs        ? ?/sec
empty_archetypes/par_for_each/100                                                                         1.00      8.7±0.48µs        ? ?/sec      1.04      9.0±0.34µs        ? ?/sec
empty_archetypes/par_for_each/10000                                                                       1.01     21.2±0.41µs        ? ?/sec      1.00     20.9±0.44µs        ? ?/sec
empty_commands/0_entities                                                                                 1.72      3.7±0.01ns        ? ?/sec      1.00      2.1±0.02ns        ? ?/sec
empty_systems/100_systems                                                                                 1.00     82.9±3.29µs        ? ?/sec      1.07     88.3±3.77µs        ? ?/sec
empty_systems/2_systems                                                                                   1.01      8.2±0.71µs        ? ?/sec      1.00      8.2±0.38µs        ? ?/sec
empty_systems/4_systems                                                                                   1.00      8.2±0.72µs        ? ?/sec      1.03      8.4±0.71µs        ? ?/sec
entity_hash/entity_set_build/10000                                                                        1.10     45.9±1.60µs 207.7 MElem/sec     1.00     41.6±0.39µs 229.0 MElem/sec
entity_hash/entity_set_build/3162                                                                         1.06     12.7±0.77µs 236.7 MElem/sec     1.00     12.0±0.75µs 250.6 MElem/sec
entity_hash/entity_set_lookup_hit/10000                                                                   1.02     14.5±0.30µs 658.3 MElem/sec     1.00     14.2±0.07µs 672.6 MElem/sec
entity_hash/entity_set_lookup_hit/3162                                                                    1.01      4.4±0.03µs 682.7 MElem/sec     1.00      4.4±0.01µs 691.3 MElem/sec
entity_hash/entity_set_lookup_miss_gen/10000                                                              1.01     61.3±4.12µs 155.6 MElem/sec     1.00     60.6±1.47µs 157.3 MElem/sec
entity_hash/entity_set_lookup_miss_gen/3162                                                               1.00      9.5±0.02µs 316.3 MElem/sec     1.01      9.7±0.88µs 311.7 MElem/sec
entity_hash/entity_set_lookup_miss_id/100                                                                 1.00    145.5±1.49ns 655.4 MElem/sec     1.03    149.8±1.59ns 636.7 MElem/sec
entity_hash/entity_set_lookup_miss_id/10000                                                               1.85     63.9±3.57µs 149.3 MElem/sec     1.00     34.6±3.81µs 275.8 MElem/sec
entity_hash/entity_set_lookup_miss_id/316                                                                 1.00    562.0±9.58ns 536.2 MElem/sec     1.02    573.9±1.27ns 525.1 MElem/sec
entity_hash/entity_set_lookup_miss_id/3162                                                                1.03      9.1±0.10µs 330.7 MElem/sec     1.00      8.9±0.24µs 339.0 MElem/sec
event_propagation/four_event_types                                                                        1.12    541.5±3.84µs        ? ?/sec      1.00    482.7±4.64µs        ? ?/sec
event_propagation/single_event_type                                                                       1.07   769.5±10.21µs        ? ?/sec      1.00   715.9±15.16µs        ? ?/sec
event_propagation/single_event_type_no_listeners                                                          1.56    393.4±2.89µs        ? ?/sec      1.00    251.4±3.68µs        ? ?/sec
events_iter/size_16_events_100                                                                            1.01     64.0±0.18ns        ? ?/sec      1.00     63.4±0.23ns        ? ?/sec
events_iter/size_4_events_100                                                                             1.02     64.8±0.90ns        ? ?/sec      1.00     63.4±0.24ns        ? ?/sec
events_iter/size_4_events_1000                                                                            1.01    586.5±8.00ns        ? ?/sec      1.00    579.1±4.93ns        ? ?/sec
events_send/size_16_events_100                                                                            1.00   142.7±24.34ns        ? ?/sec      1.03   147.1±28.36ns        ? ?/sec
events_send/size_16_events_10000                                                                          1.01     12.2±0.13µs        ? ?/sec      1.00     12.1±0.12µs        ? ?/sec
fake_commands/10000_commands                                                                              1.43     63.3±8.21µs        ? ?/sec      1.00     44.1±0.16µs        ? ?/sec
fake_commands/1000_commands                                                                               1.40      6.2±0.01µs        ? ?/sec      1.00      4.4±0.02µs        ? ?/sec
fake_commands/100_commands                                                                                1.38    629.4±1.69ns        ? ?/sec      1.00    457.1±0.84ns        ? ?/sec
few_changed_detection/50000_entities_ecs::change_detection::Table                                         1.00     57.7±0.86µs        ? ?/sec      1.07     61.6±1.19µs        ? ?/sec
few_changed_detection/5000_entities_ecs::change_detection::Sparse                                         1.05      5.4±0.53µs        ? ?/sec      1.00      5.1±0.56µs        ? ?/sec
few_changed_detection/5000_entities_ecs::change_detection::Table                                          1.00      4.3±0.30µs        ? ?/sec      1.18      5.1±0.35µs        ? ?/sec
insert_commands/insert                                                                                    1.11   402.5±10.75µs        ? ?/sec      1.00    363.6±8.07µs        ? ?/sec
insert_commands/insert_batch                                                                              1.00    174.9±3.03µs        ? ?/sec      1.02    177.9±5.74µs        ? ?/sec
insert_simple/base                                                                                        1.04   564.1±23.01µs        ? ?/sec      1.00   544.3±60.70µs        ? ?/sec
insert_simple/unbatched                                                                                   1.32  929.3±180.10µs        ? ?/sec      1.00  704.1±132.88µs        ? ?/sec
iter_fragmented/base                                                                                      1.02    280.0±2.86ns        ? ?/sec      1.00    274.0±4.85ns        ? ?/sec
iter_fragmented/foreach                                                                                   1.00     97.3±0.42ns        ? ?/sec      1.03    100.6±3.44ns        ? ?/sec
iter_fragmented/foreach_wide                                                                              1.04      2.7±0.04µs        ? ?/sec      1.00      2.6±0.03µs        ? ?/sec
iter_fragmented_sparse/base                                                                               1.00      5.6±0.05ns        ? ?/sec      1.04      5.8±0.06ns        ? ?/sec
multiple_archetypes_none_changed_detection/100_archetypes_10000_entities_ecs::change_detection::Sparse    1.00   737.7±27.38µs        ? ?/sec      1.01   747.5±30.01µs        ? ?/sec
multiple_archetypes_none_changed_detection/100_archetypes_10000_entities_ecs::change_detection::Table     1.02   678.3±25.13µs        ? ?/sec      1.00   662.1±19.63µs        ? ?/sec
multiple_archetypes_none_changed_detection/100_archetypes_1000_entities_ecs::change_detection::Sparse     1.09     76.0±9.35µs        ? ?/sec      1.00     70.0±3.29µs        ? ?/sec
multiple_archetypes_none_changed_detection/100_archetypes_1000_entities_ecs::change_detection::Table      1.03     64.7±3.40µs        ? ?/sec      1.00     62.8±1.80µs        ? ?/sec
multiple_archetypes_none_changed_detection/100_archetypes_100_entities_ecs::change_detection::Table       1.02      7.6±0.12µs        ? ?/sec      1.00      7.5±0.16µs        ? ?/sec
multiple_archetypes_none_changed_detection/100_archetypes_10_entities_ecs::change_detection::Sparse       1.00  1003.5±12.38ns        ? ?/sec      1.01  1013.7±32.64ns        ? ?/sec
multiple_archetypes_none_changed_detection/20_archetypes_10_entities_ecs::change_detection::Sparse        1.03   187.1±21.18ns        ? ?/sec      1.00   181.9±22.86ns        ? ?/sec
multiple_archetypes_none_changed_detection/5_archetypes_10_entities_ecs::change_detection::Sparse         1.00     52.8±8.19ns        ? ?/sec      1.03     54.3±8.06ns        ? ?/sec
multiple_archetypes_none_changed_detection/5_archetypes_10_entities_ecs::change_detection::Table          1.00     46.8±2.23ns        ? ?/sec      1.03     48.0±2.48ns        ? ?/sec
no_archetypes/system_count/0                                                                              1.00     16.3±0.17ns        ? ?/sec      1.02     16.6±0.16ns        ? ?/sec
no_archetypes/system_count/100                                                                            1.02    851.5±9.32ns        ? ?/sec      1.00    832.9±7.93ns        ? ?/sec
none_changed_detection/5000_entities_ecs::change_detection::Sparse                                        1.00      3.4±0.04µs        ? ?/sec      1.02      3.5±0.05µs        ? ?/sec
nonempty_spawn_commands/10000_entities                                                                    1.89    261.1±6.99µs        ? ?/sec      1.00    137.8±8.47µs        ? ?/sec
nonempty_spawn_commands/1000_entities                                                                     1.90     26.4±3.18µs        ? ?/sec      1.00     13.9±2.38µs        ? ?/sec
nonempty_spawn_commands/100_entities                                                                      1.87      2.6±0.07µs        ? ?/sec      1.00  1388.8±97.31ns        ? ?/sec
observe/trigger_simple                                                                                    1.09    347.5±1.51µs        ? ?/sec      1.00    317.7±2.62µs        ? ?/sec
observe/trigger_targets_simple/10000_entity                                                               1.04   696.5±15.50µs        ? ?/sec      1.00   672.0±13.88µs        ? ?/sec
par_iter_simple/with_0_fragment                                                                           1.01     34.4±0.51µs        ? ?/sec      1.00     33.9±0.53µs        ? ?/sec
par_iter_simple/with_1000_fragment                                                                        1.04     45.5±0.93µs        ? ?/sec      1.00     43.9±1.85µs        ? ?/sec
par_iter_simple/with_100_fragment                                                                         1.03     36.2±0.50µs        ? ?/sec      1.00     35.1±0.44µs        ? ?/sec
par_iter_simple/with_10_fragment                                                                          1.03     37.5±0.97µs        ? ?/sec      1.00     36.5±0.74µs        ? ?/sec
param/combinator_system/8_dyn_params_system                                                               1.00     10.4±0.73µs        ? ?/sec      1.01     10.5±0.79µs        ? ?/sec
param/combinator_system/8_piped_systems                                                                   1.05      8.0±0.65µs        ? ?/sec      1.00      7.6±0.57µs        ? ?/sec
query_get/50000_entities_sparse                                                                           1.06    136.7±0.35µs        ? ?/sec      1.00    128.6±0.44µs        ? ?/sec
query_get_many_10/50000_calls_sparse                                                                      1.02  1649.4±77.80µs        ? ?/sec      1.00  1614.4±78.91µs        ? ?/sec
query_get_many_2/50000_calls_sparse                                                                       1.00    191.3±3.66µs        ? ?/sec      1.01    193.3±0.75µs        ? ?/sec
query_get_many_2/50000_calls_table                                                                        1.00    243.9±0.55µs        ? ?/sec      1.05    257.2±8.62µs        ? ?/sec
query_get_many_5/50000_calls_sparse                                                                       1.00    585.9±7.70µs        ? ?/sec      1.03    600.6±5.99µs        ? ?/sec
query_get_many_5/50000_calls_table                                                                        1.00    673.7±7.44µs        ? ?/sec      1.07   722.3±10.77µs        ? ?/sec
run_condition/no/1000_systems                                                                             1.00     23.7±0.06µs        ? ?/sec      1.06     25.1±0.07µs        ? ?/sec
run_condition/no/100_systems                                                                              1.00   1460.5±4.28ns        ? ?/sec      1.03   1510.1±3.69ns        ? ?/sec
run_condition/no/10_systems                                                                               1.00    201.5±0.53ns        ? ?/sec      1.04    209.1±2.37ns        ? ?/sec
run_condition/yes/1000_systems                                                                            1.00  1225.7±22.58µs        ? ?/sec      1.02  1253.7±24.90µs        ? ?/sec
run_condition/yes/100_systems                                                                             1.02     89.4±3.43µs        ? ?/sec      1.00     88.0±3.96µs        ? ?/sec
run_condition/yes_using_query/1000_systems                                                                1.00  1288.3±26.57µs        ? ?/sec      1.03  1323.0±24.73µs        ? ?/sec
run_condition/yes_using_query/100_systems                                                                 1.00    108.8±2.51µs        ? ?/sec      1.03    112.3±3.09µs        ? ?/sec
run_condition/yes_using_resource/100_systems                                                              1.03     99.0±3.37µs        ? ?/sec      1.00     96.2±4.80µs        ? ?/sec
run_empty_schedule/MultiThreaded                                                                          1.03     15.3±0.10ns        ? ?/sec      1.00     14.9±0.03ns        ? ?/sec
run_empty_schedule/Simple                                                                                 1.01     15.2±0.15ns        ? ?/sec      1.00     15.0±0.25ns        ? ?/sec
sized_commands_0_bytes/10000_commands                                                                     1.57     52.6±0.41µs        ? ?/sec      1.00     33.5±0.10µs        ? ?/sec
sized_commands_0_bytes/1000_commands                                                                      1.57      5.3±0.01µs        ? ?/sec      1.00      3.4±0.00µs        ? ?/sec
sized_commands_0_bytes/100_commands                                                                       1.56    536.5±4.83ns        ? ?/sec      1.00    343.6±1.12ns        ? ?/sec
sized_commands_12_bytes/10000_commands                                                                    1.22     63.0±0.53µs        ? ?/sec      1.00     51.5±6.06µs        ? ?/sec
sized_commands_12_bytes/1000_commands                                                                     1.25      5.7±0.01µs        ? ?/sec      1.00      4.6±0.05µs        ? ?/sec
sized_commands_12_bytes/100_commands                                                                      1.27    579.3±1.28ns        ? ?/sec      1.00    455.4±0.85ns        ? ?/sec
sized_commands_512_bytes/10000_commands                                                                   1.11   248.4±85.81µs        ? ?/sec      1.00   224.3±52.11µs        ? ?/sec
sized_commands_512_bytes/1000_commands                                                                    1.09     22.8±0.18µs        ? ?/sec      1.00     21.0±0.17µs        ? ?/sec
sized_commands_512_bytes/100_commands                                                                     1.13  1852.2±11.21ns        ? ?/sec      1.00   1635.3±4.91ns        ? ?/sec
spawn_commands/10000_entities                                                                             1.04   844.2±11.96µs        ? ?/sec      1.00   811.5±13.25µs        ? ?/sec
spawn_commands/1000_entities                                                                              1.05     84.9±3.66µs        ? ?/sec      1.00     80.5±4.13µs        ? ?/sec
spawn_commands/100_entities                                                                               1.06      8.6±0.12µs        ? ?/sec      1.00      8.1±0.12µs        ? ?/sec
spawn_world/10000_entities                                                                                1.03   413.2±25.20µs        ? ?/sec      1.00   400.9±49.97µs        ? ?/sec
spawn_world/100_entities                                                                                  1.02      4.1±0.62µs        ? ?/sec      1.00      4.1±0.69µs        ? ?/sec
spawn_world/1_entities                                                                                    1.04     42.2±3.23ns        ? ?/sec      1.00     40.6±6.81ns        ? ?/sec
world_entity/50000_entities                                                                               1.18     88.3±0.42µs        ? ?/sec      1.00     74.7±0.16µs        ? ?/sec
world_get/50000_entities_sparse                                                                           1.02    182.2±0.32µs        ? ?/sec      1.00    179.5±0.84µs        ? ?/sec
world_get/50000_entities_table                                                                            1.01    198.3±0.46µs        ? ?/sec      1.00    196.2±0.63µs        ? ?/sec
world_query_for_each/50000_entities_sparse                                                                1.00     32.7±0.12µs        ? ?/sec      1.01     33.1±0.46µs        ? ?/sec
```
</details>

This roughly doubles command spawning speed! Despawning also sees a
20-30% improvement. Dummy commands improve by 10-50% (due to not needing
an entity flush). Other benchmarks seem to be noise and are negligible.
It looks to me like a massive performance win!

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Christian Hughes <9044780+ItsDoot@users.noreply.github.com>
Co-authored-by: urben1680 <55257931+urben1680@users.noreply.github.com>
Co-authored-by: Chris Russell <8494645+chescock@users.noreply.github.com>
Co-authored-by: Trashtalk217 <24552941+Trashtalk217@users.noreply.github.com>
Co-authored-by: James Liu <contact@jamessliu.com>
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2025-11-03 23:06:02 +00:00
eugineerd a264dd3c85 API for traversing Relationships and RelationshipTargets in dynamic contexts (#21601)
# Objective
Currently there is no way to traverse relationships in type-erased
contexts or to define dynamic relationship components, which is a hole
in the current relationships api.

## Solution
Introduce `RelationshipAccessor` to describe a way to get `Entity`
values from any registered relationships in dynamic contexts and store
it on `ComponentDescriptor`. This allows to traverse relationships
without knowing their type, which is useful for working with entity
hierarchies using non-default components.

## Testing
Added a simple test/example of how to use this api to traverse
hierarchies in a type-erased context.
2025-10-20 21:52:31 +00:00
ickshonpe 1610aa9d91 Remove FontAtlasSets (#21345)
# Objective

The `FontAtlasSets` resource contains a map from `AssetId<Font>`s to
`FontAtlasSet`s.
`FontAtlasSet` has another map from a `FontAtlasKey` (tuple of the font
size bit cast into a `u32` and `FontSmoothing`) to a vec that contains
the actual font atlases for the font. The redirection through the
additional map doesn't serve much purpose, only individual fonts are
looked up (except when freeing unused fonts), not the set of all atlases
for a particular font face.

## Solution
* Remove `FontAtlasSet`. 
* Rename `FontAtlasSets` to `FontAtlasSet`.
* Add `AssetId<Font>` to `FontAtlasKey`.
* Change the font atlas map to a `HashMap<FontAtlasKey, Vec<FontAtlas>>`
* Move the `FontAtlasSet` methods `add_glyph_to_atlas`,
`get_glyph_atlas_info`, and `get_outlined_glyph_texture` into the
`font_atlas` module and rework them into free functions.
* Change `FontAtlasSet` into a newtype around the map and remove the
manual method implementations in favour of deriving `Deref` and
`DerefMut`.

We could consider renaming `FontAlasSet` to `FontAtlasMap`, but it
doesn't seem necessary, and mathematically a map is a set so it's not
incorrect.

## Testing

The output from all of the text examples should be unchanged.

Naive benchmarks suggest this is a modest improvement in performance at
a very high load (5% ish), but this is more about reducing complexity to
make more significant optimisations possible. Freeing the unused atlases
when a font asset is removed will be slower, it has to filter all the
font atlas vecs now instead of just removing the entry for the font
asset, but this isn't done very often and the number of entries should
be relatively low.

---------

Co-authored-by: Dimitrios Loukadakis <dloukadakis@users.noreply.github.com>
2025-10-06 22:10:49 +00:00
ickshonpe acae6a5c16 Add a WinitSettings::continuous constructor (#20754)
# Objective

* Add a constructor to `WinitSettings` for the continuous update mode
used by the stress tests.

## Solution

* Add the constructor.

---------

Co-authored-by: James Liu <contact@jamessliu.com>
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2025-09-11 19:43:19 +00:00
Carter Anderson eda118d033 Event Rearchitecture (#20731)
There is general consensus that our terminology for Events, "entity
events", Observers, and BufferedEvents needs clarity. Additionally, many
of us also agree that the current Observer system would benefit from
additional static-ness: currently it is assumed that you can use events
in pretty much any context, and they all go through the exact same code
path.

Alice put forth a proposal to [Overhaul
Observers](https://hackmd.io/@bevy/rk4S92hmlg), and we have already
partially implemented it for 0.17. I think it does a great job of
outlining many of the issues at play, and it solves them reasonably
well. But I _also_ think the proposed solution isn't yet ideal. Given
that it is already partially implemented for 0.17, it is a breaking
change, _and_ given that we have already broken the Observer API a
number of times, I think we need to sort this out before the next
release.

This is a big changeset, but it is _largely_ just a reframing of what is
already there. I haven't fundamentally changed the behaviors. I've just
refined and constrained in a way that allows us to do what we are
currently doing in a clearer, simpler, and more performant way.

First, I'll give some quick notes on Alice's proposal (which you all
should read if you haven't yet!):
### Notes on Alice's Proposal
- I like the move toward a more static API
- I think we've gone too far down the "separate terminology" path. The
proposal introduces a zoo of apis, terms, and "subterms". I think we
need to simplify our concepts and names to make this all easier to talk
about and use in practice.
- BroadcastEvent feels like the wrong name. EntityEvent is also
"broadcast" in the exact same way
- BufferedEvent is a completely different system than EntityEvent and
BroadcastEvent. This muddles concepts too much. It needs its own
standalone, single-word concept name.
- "Universal observers": I think this should be fully context driven,
rather than needing encoding in the API.
- I agree we can't get rid of buffered events, and that merging them
with "broadcast events" isn't helpful
- I'm not quite sure how we'd make the proposed PropagateEvent subtrait
work transparently. This can't be "layered on top" as a trait. It needs
to be baked in at more fundamental level.
* I don't like `app.add_broadcast_observers()`,
`app.add_universal_observers()`, `Observer::entity_observer`,
`Observer::broadcast`, etc. The `On` event should statically determine
whether an observer is an "entity observer" or a "broadcast" Observer.
This would already be encoded in the type system and is therefore
something we can do on the developer's behalf. Likewise, any observer
being registered at a top level is inherently _not_ a specific entity
observer. All of these variants serve to make users guess and poke
around in a way that is unnecessary. I want simple one word concept
names, single constructors, etc.
### Proposed Principals
- Static-ness:
- Events should only be usable in the context they were defined to be
used.
- When triggered, Observers should *only* have access to fields and
behaviors that are relevant:
- Dont return Option or PLACEHOLDER: the field or function shouldn't
exist
- Entity events that don't support propagation shouldn't expose that
functionality
- Don't do unnecessary work at runtime
- Event triggers shouldn't branch through every potential event code
path
- Don't clone potentially large lists of event context unnecessarily
(Ex: we currently clone the component list for every observer
invocation)
- Minimize codegen
	- Don't recompile things redundantly.
	- Don't compile unnecessary code paths.
- Clear and Simple
- Minimize the number of concept names floating around, and lock each
concept down heavily to a specific context
- I'm convinced at this point that "buffered events" and "observer
events" sharing concept names is wrong. We need two clean and clear
terms, and I'm willing to give "buffered events" a slightly worse name
if it means "observer events" can be nicer.
- Don't throw the concept name "Event" out ... it is a very good name.
Instead, constrain it to one specific thing.
	- Minimize our API surface
- Events contain all context, including what used to be the "target".
This lets people define the "target" name that makes the most sense for
the context, and lets the documentation fully describe the context of
that "target".
### Concepts
- **Event** (the thing you "observe")
- Rationale: "Event" is the clear choice for this concept. An "event"
feels like something that happens in real time. "Event observers" are
things that observe events when they occur (are triggered).
Additionally, this is the concept that "propagates", and "event
propagation" is a term people understand.
- **Trigger**: (the verb that "causes" events to happen for targets).
Events are Triggered. This can include additional context/ data that is
passed to observers / informs the trigger behavior. Events have
_exactly_ one Trigger. If you want a different trigger behavior, define
a new event. This makes the system more static, more predictable, and
easier to understand and document. `world.trigger_ref_with` makes it
possible to pass in mutable reference to your own Trigger data, making
it possible to customize the input trigger data and read out the final
trigger data.
- **Observer** (the thing that "observes" events): An event's `Trigger`
determines which observers will run.
- **Event Types**: You can build any "type" of event. The concept of a
"target" has been removed. Instead, define a `Trigger` that expects a
specific kind of event (ex: `E: EntityEvent`).
- **EntityEvent** We add a new `EntityEvent` trait, which defines an
`event.entity()` accessor. This is used by the `Trigger` impls :
`EntityTrigger`, `PropagateEntityTrigger`, and
`EntityComponentsTrigger`.
- **Message** (the buffered thing you "read" and "write")
- `Message` is a solid metaphor for what this is ... it is data that is
written and then at some later point read by someone / something else. I
expect existing consumers of "buffered events" to lament this name
change, as "event" feels nicer. But having a separate name is within
everyone's best interest.
	- **MessageReader** (the thing that reads messages)
	- **MessageWriter** (the thing that writes messages)
### The Changes
- `Event` trait changes
	- Event is now used exclusively by Observers
- Added `Event::Trigger`, which defines what trigger implementation this
event will use
- Added the `Trigger` trait
- All of the shared / hard-coded observer trigger logic has been broken
out into individual context-specific Trigger traits.
- "Trigger Targets" have been removed.
- Instead, Events, in combination with their Trigger impl, decide how
they will be triggered. In general, this means that Events now include
their "targets" as fields on the event.
- APIs like `trigger_targets` have been replaced by `trigger`, which can
now be used for any `Event`
- `EntityEvent` trait changes
- Propagation config has been removed from the `EntityEvent` trait. It
now lives on the `Trigger` trait (specifically the
`PropagateEntityTrigger` trait).
- `EntityEvent` now provides `entity / entity_mut` accessors for the
Event it is implemented for
- `EntityEvent` defaults to having no propagation (uses the simpler
`EntityTrigger`)
- `#[entity_event(propagate)]` enables the "default" propagation logic
(uses ChildOf). The existing `#[entity_event(traversal = X)]` has been
renamed to `#[entity_event(propagate = X)`
- Deriving `EntityEvent` requires either a single `MyEvent(Entity)`, the
`entity` field name (`MyEvent { entity: Entity}`), or `MyEvent {
#[event_entity] custom: Entity }`
- Animation event changes
- Animation events now have their own `AnimationEvent` trait, which sets
the `AnimationEventTrigger`. This allows developers to pass in events
that _dont_ include the Entity field (as this is set by the system). The
custom trigger also opens the doors to cheaply passing in additional
animation system context, accessible through `On`
- `EntityComponentsTrigger`
- The built in Add/Remove/etc lifecycle events now use the
`EntityComponentsTrigger`, which passes in the components as additional
state. This _significantly_ cuts down on clones, as it does a borrow
rather than cloning the list into _each_ observer execution.
	- Each event now has an `entity` field.
- Style changes
- Prefer the event name for variables: `explode: On<Explode>` not
`event: On<Explode>`
- Prefer using the direct field name for the entity on entity events,
rather than `event.entity()`. This allows us to use more specific names
where appropriate, provides better / more contextual docs, and coaches
developers to think of `On<MyEvent>` _as_ the event itself.

Take a look at the changes to the examples and the built-in events to
see what this looks like in practice.

### Downsides
- Moving the "target" into the event adds some new constraints:
- Triggering the same event for multiple entities requires multiple
trigger calls. For "expensive" events (ex: lots of data attached to the
event), this will be more awkward. Your options become:
		-  Create multiple instances of the event, cloning the expensive data
- Use `trigger_ref`, and mutate the event on each call to change the
target.
- Move the "expensive" shared data into the Trigger, and use
`trigger_ref_with``
- We could build a new EntityEvent method that abstracts over the "event
mutation" behavior and provides something like the old `trigger_target`
behavior.
- Use a different `EntityTargetTrigger` (not currently provided by bevy,
but we could), which brings back the old behavior. This would be used
with `trigger_with` to replicate the old pattern:
`world.trigger_with(MyEvent, [e1, e2].into())` (or we could make the
`into()` implicit)
- Bubbling the event involves mutating the event to set the entity. This
means that `trigger_ref` will result in the event's
`EntityEvent::entity()` being the final bubbled entity instead of the
initial entity.
- Some APIs (trivially) benefit from the "target entity" being separate
from the event. Specifically, this new API requires changes to the
"Animation Event" system in AnimationPlayer. I think this is actually a
good change set, as it allows us to:
- Cheaply expose more animation state as part of a new
AnimationEventTrigger impl
- Move that "implict" entity target provided by the AnimationPlayer into
the AnimationEventTrigger
- Encode the "animation event trigger-ness" of the event into the type
itself (by requiring `#[event(trigger = AnimationEventTrigger)]`)
- By not implementing Default for AnimationEventTrigger, we can block
animation events from being fired manually by the user.

### Draft TODO
- [x] Fill in documentation and update existing docs
- [ ] Benchmark: I expect this impl to be significantly faster. There
might also be tangible binary size improvements, as I've removed a lot
of redundant codegen.
- [x] Update release notes and migration guides

### Next Steps
- The `BufferedEvent -> Message` rename was not included to keep the
size down.

Fixes #19648

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Jan Hohenheim <jan@hohenheim.ch>
2025-09-09 23:48:55 +00:00
TheBlckbird 13877fa84d Add a new trait to accept more types in the Val-helper functions (#20551)
# Objective

- Allow the `Val`-helper functions to accept more types besides just
`f32`

Fixes #20549

## Solution

- Adds a new trait that can be implemented for numbers
- That trait has a method that converts `self` to `f32`

## Testing

- I tested it using Rust's testing framework (although I didn't leave
the tests in, as I don't deem them important enough)

<details>
  <summary>Rust test</summary>

```rust
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_val_helpers_work() {
        let p = px(10_u8);
        assert_eq!(p, Val::Px(10.0));

        let p = px(10_u16);
        assert_eq!(p, Val::Px(10.0));

        let p = px(10_u32);
        assert_eq!(p, Val::Px(10.0));

        let p = px(10_u64);
        assert_eq!(p, Val::Px(10.0));

        let p = px(10_u128);
        assert_eq!(p, Val::Px(10.0));

        let p = px(10_i8);
        assert_eq!(p, Val::Px(10.0));

        let p = px(10_i16);
        assert_eq!(p, Val::Px(10.0));

        let p = px(10_i32);
        assert_eq!(p, Val::Px(10.0));

        let p = px(10_i64);
        assert_eq!(p, Val::Px(10.0));

        let p = px(10_i128);
        assert_eq!(p, Val::Px(10.0));

        let p = px(10.3_f32);
        assert_eq!(p, Val::Px(10.3));

        let p = px(10.6_f64);
        assert_eq!(p, Val::Px(10.6));
    }
}
```
</details>

---

## Showcase

```rust
// Same as Val::Px(10.)
px(10);
px(10_u8);
px(10.0);
```
2025-08-29 20:18:57 +00:00
ickshonpe 950a9de4e6 Disable button labels for the many_buttons stress text by default (#20636)
# Objective

The `many_buttons` benchmark isn't a good measure of UI performance
because it's dominated by the text layout and rendering for the button
labels. The button labels should be disabled by default, we have other
text specific stress tests.

## Solution

1. Rename the `no_text` commandline parameter to `text`. The buttons are
no longer shown by default, to run the example with labels you need to
use:
  ```
  cargo run --example many_buttons --release -- --text
  ```
2. Add a second button icon image, this is to break up the batches
during rendering without text.
3. Spawn every button with an icon.

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2025-08-26 03:01:21 +00:00
François Mockers 99d511afa3 update some stress tests to use the common setup (#20751)
# Objective

- `many_gradients`, `many_materials` and `many_text2d` don't always run
at their full potential

## Solution

- Use the same scale factor as other stress tests.
- Use the same winit settings as other stress tests.
2025-08-25 20:59:13 +00:00
Carter Anderson 5058f8a9e6 Improve On Terminology (#20648)
# Objective

Fixes #19263 (and expands on it)

Within `Observers`, we have started to distance ourselves from the
"trigger" terminology. Specifically, we have renamed `Trigger` to `On`.
I think this was a very good move, but we're currently in an awkward
middle ground state. Users interact with `On` as if it were an event:
`On<E>` exposes the event and derefs directly to it. I think we should
embrace this mindset fully, in the interest of clarity.

## Solution

- Rename all `trigger: On<SomeEvent>` cases to `event: On<SomeEvent>`.
- Rename `On::target` to `On::entity`. This reads _much_ better when
writing `query.get(event.entity())` and pairs more effectively with the
`EntityEvent` terminology.
- Rename `On::original_target` to `On::original_entity`, for the same
reasons.
- Take advantage of the `Deref` behavior where appropriate

```rust
// Before
entity.observe(|trigger: On<Explode>| {
  println!("{} exploded!", trigger.target());
})

// After
entity.observe(|event: On<Explode>| {
  println!("{} exploded!", event.entity());
})
```
2025-08-21 08:54:28 +00:00
BigWingBeat 121981b016 Remove redundant number conversion in window resolution (#20582)
# Objective

`WindowResolution` stores the width and height as `u32`, but the
constructors take `f32` and just convert straight to `u32`.
Additionally, everywhere in Bevy where a `WindowResolution` is
constructed specifies whole numbers, making the use of a float entirely
pointless.

## Solution

Replace the `f32` constructors with `u32` constructors.

I also decided to change the generic `I: Into<f32>` tuple and array
constructors to only take u32 instead of `I: Into<u32>` for UX reasons.
It allows formatting those constructors as `(1920, 1080).into()`, as the
compiler can infer that the numbers are u32. Keeping those impls generic
prevents that inference, and because the default number type (i32)
doesn't impl `Into<u32>`, that would require formatting it explicitly as
`(1920u32, 1080u32).into()` to compile.

In practice, these generic constructors were only used with whole-number
f32 values anyway, like everything else, so them being generic wasn't
actually leveraged anywhere.

## Testing

Chased type errors until they were all gone
2025-08-20 18:02:41 +00:00
Daniel Skates 9ee0aaafc5 Split out sprite rendering (#20587)
# Objective

- Split out `bevy_sprite_render` from `bevy_sprite` .

## Solution

- Do the thing

## Testing

- CI
- `cargo run --example sprite`
2025-08-15 22:41:05 +00:00
Greeble e2a709e06d Update morph_targets and many_foxes examples to use observers (#20531)
## Objective

Change some examples to follow best practices for playing animations,
and as a bonus work around an issue with scenes spawning multiple times.

## Background

Examples that play skeletal animations usually have two steps:

1) Start scene spawning.
2) Play animations after the scene has spawned.

Different examples use different approaches for triggering part 2,
including a scene spawning observer and `Added<AnimationPlayer>`
queries.

The observer approach is arguably best as it's more tightly scoped and
easier for users to extend. The other approaches work in simple examples
but fall down when users want multiple scenes or animations. See #17421
for more detail.

As a bonus, the scene spawning observer works around a current issue
with scenes spawning multiple times - see #20393, #20430. Although
there's an argument that this PR shouldn't land until the issue is
properly fixed, as these examples are a useful test case.

## Solution

Update the `morph_targets` and `many_foxes` examples to use observers.

I also made a few tweaks and fixes to `morph_targets`.

- Fix documentation referring to an `update_weights` feature that isn't
in the example.
- Use the same `AnimationToPlay` component as the `animated_mesh`
example.
- Change the `name_morphs` system to be event driven and print the asset
name.
- This is maybe too complex, but could also be nice for users to c&p
into their app for debugging.

I haven't updated the `animated_mesh_control`, `animated_mesh_events`,
and `animation_masks` examples, which still use
`Added<AnimationPlayer>`.

## Testing

```sh
cargo run --example morph_targets
cargo run --example many_foxes
```
2025-08-14 20:38:41 +00:00
atlv 68b848217f Yeet RenderAssetUsages re-export (#20498)
# Objective

- Forgor to yeet this

## Solution

- rember and yeet

## Testing

cargo check --examples --all-features
2025-08-11 01:42:08 +00:00
atlv ce416c37a2 Use bevy::light in examples instead of bevy::pbr::light re-export (#20487)
# Objective

- Prepare for removing re-exports

## Solution

- title

## Testing

- cargo check --examples --all-features
2025-08-10 05:25:03 +00:00
atlv 03dd839b82 Use bevy::camera in examples instead of bevy::render::camera re-export (#20477)
# Objective

- Prepare for removing re-exports

## Solution

- title

## Testing

- cargo check --examples
2025-08-09 21:09:48 +00:00
Gonçalo Rica Pais da Silva ef845e0cea Update rand, glam and encase to latest versions (#18047)
# Objective

New `rand` version, which means updating `glam` and `encase` to support
the newer ecosystem update. Does mean that this changes how WASM builds
need to be done in order to configure `getrandom` correctly, but this
can be remedied with updated docs.

## Solution

Updating all needed dependencies to their compatible versions. ~~This PR
is currently blocked by `encase`, which is waiting on [this
PR](https://github.com/teoxoy/encase/pull/88) to be merged and then a
new version published.~~ ~~This PR is no longer blocked~~,
~~`hexasphere` is blocking this PR now due to not yet having a new
release with the latest `glam` version support~~, The PR is all good to
go now, everything in order across glam/rand deps.

## Testing

- Must pass CI for all checks, tests, not introduce breaking changes.

---

## Migration Guide

With newer versions of `glam` & `encase`, the updated versions don't
seem to have introduced breakages, though as always, best to consult
their docs [1](https://docs.rs/glam/latest/glam/)
[2](https://docs.rs/encase/0.11.0/encase/) for any changes.

`rand` changes are more extensive, with changes such as `thread_rng()`
-> `rng()`, `from_entropy()` -> `from_os_rng()`, and so forth. `RngCore`
is now split into infallible `RngCore` and fallible `TryRngCore`, and
the `distributions` module has been renamed to `distr`. Most of this
affects only internals, and doesn't directly affect Bevy's APIs. For the
full set of changes, see `rand` [migration
notes](https://rust-random.github.io/book/update-0.9.html).

`getrandom` is also updated, and will require additional configuration
when building Bevy for WASM/Web (if also using `rand`). The full details
of how to do this is in the `getrandom` docs
[1](https://github.com/rust-random/getrandom?tab=readme-ov-file#opt-in-backends)
[2](https://github.com/rust-random/getrandom?tab=readme-ov-file#webassembly-support).

---------

Co-authored-by: François Mockers <francois.mockers@vleue.com>
2025-08-09 02:09:10 +00:00
Tyler Critchlow 3fc49f00c1 Preconvert colors before sending to shader (#20074)
# Objective

- Fixes #20008 - Preconvert colors before sending them to the UI
gradients shader for better performance

  ## Solution

- Modified `prepare_gradient` in `gradient.rs` to convert colors from
`LinearRgba` to `Srgba` on the CPU before sending to the GPU
- Updated the gradient shader to remove per-pixel color space
conversions since colors are now pre-converted
  - Added documentation to clarify that vertex colors are in sRGB space

This optimization reduces the number of power operations per pixel from
3 to 1:
- **Before**: Convert start color to sRGB, convert end color to sRGB,
mix, convert back to linear (3 pow operations per pixel)
- **After**: Colors pre-converted on CPU, mix in sRGB space, convert
back to linear (1 pow operation per pixel)

  ## Testing

- Verified that the UI gradient examples (`cargo run --example
gradients`) compile and render correctly
- The visual output should remain identical while performance improves,
especially for large gradient areas
- Changes maintain the same color interpolation behavior (mixing in sRGB
space)

  To test:
1. Run `cargo run --example gradients` or `cargo run --example
stacked_gradients`
  2. Verify gradients render correctly

---------

Co-authored-by: ickshonpe <david.curthoys@googlemail.com>
2025-07-16 16:06:45 +00:00
atlv b62b14c293 Add UVec to_extents helper method (#19807)
# Objective

- Simplify common usecase

## Solution

- Helper trait
2025-06-26 20:53:49 +00:00
charlotte 🌸 92e65d5eb1 Upgrade to Rust 1.88 (#19825) 2025-06-26 19:38:19 +00:00
Tero Laxström c549b9e9a4 Copy stress test settings for many_camera_lights (#19512)
# Objective

- Fixes #17183

## Solution

- Copied the stress settings from the `many_animated_sprite` example
that were mentioned in the ticket

## Testing

- Run the example

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2025-06-10 01:12:55 +00:00
ickshonpe 02fa833be1 Rename JustifyText to Justify (#19522)
# Objective

Rename `JustifyText`:

* The name `JustifyText` is just ugly.
* It's inconsistent since no other `bevy_text` types have a `Text-`
suffix, only prefix.
* It's inconsistent with the other text layout enum `Linebreak` which
doesn't have a prefix or suffix.

Fixes #19521.

## Solution

Rename `JustifyText` to `Justify`.

Without other context, it's natural to assume the name `Justify` refers
to text justification.

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2025-06-09 19:59:48 +00:00
Chris Russell 571b3ba475 Remove ArchetypeComponentId and archetype_component_access (#19143)
# Objective

Remove `ArchetypeComponentId` and `archetype_component_access`.
Following #16885, they are no longer used by the engine, so we can stop
spending time calculating them or space storing them.

## Solution

Remove `ArchetypeComponentId` and everything that touches it.  

The `System::update_archetype_component_access` method no longer needs
to update `archetype_component_access`. We do still need to update query
caches, but we no longer need to do so *before* running the system. We'd
have to touch every caller anyway if we gave the method a better name,
so just remove `System::update_archetype_component_access` and
`SystemParam::new_archetype` entirely, and update the query cache in
`Query::get_param`.

The `Single` and `Populated` params also need their query caches updated
in `SystemParam::validate_param`, so change `validate_param` to take
`&mut Self::State` instead of `&Self::State`.
2025-05-27 19:04:32 +00:00