This commit allows Bevy to use `multi_draw_indirect_count` for drawing
meshes. The `multi_draw_indirect_count` feature works just like
`multi_draw_indirect`, but it takes the number of indirect parameters
from a GPU buffer rather than specifying it on the CPU.
Currently, the CPU constructs the list of indirect draw parameters with
the instance count for each batch set to zero, uploads the resulting
buffer to the GPU, and dispatches a compute shader that bumps the
instance count for each mesh that survives culling. Unfortunately, this
is inefficient when we support `multi_draw_indirect_count`. Draw
commands corresponding to meshes for which all instances were culled
will remain present in the list when calling
`multi_draw_indirect_count`, causing overhead. Proper use of
`multi_draw_indirect_count` requires eliminating these empty draw
commands.
To address this inefficiency, this PR makes Bevy fully construct the
indirect draw commands on the GPU instead of on the CPU. Instead of
writing instance counts to the draw command buffer, the mesh
preprocessing shader now writes them to a separate *indirect metadata
buffer*. A second compute dispatch known as the *build indirect
parameters* shader runs after mesh preprocessing and converts the
indirect draw metadata into actual indirect draw commands for the GPU.
The build indirect parameters shader operates on a batch at a time,
rather than an instance at a time, and as such each thread writes only 0
or 1 indirect draw parameters, simplifying the current logic in
`mesh_preprocessing`, which currently has to have special cases for the
first mesh in each batch. The build indirect parameters shader emits
draw commands in a tightly packed manner, enabling maximally efficient
use of `multi_draw_indirect_count`.
Along the way, this patch switches mesh preprocessing to dispatch one
compute invocation per render phase per view, instead of dispatching one
compute invocation per view. This is preparation for two-phase occlusion
culling, in which we will have two mesh preprocessing stages. In that
scenario, the first mesh preprocessing stage must only process opaque
and alpha tested objects, so the work items must be separated into those
that are opaque or alpha tested and those that aren't. Thus this PR
splits out the work items into a separate buffer for each phase. As this
patch rewrites so much of the mesh preprocessing infrastructure, it was
simpler to just fold the change into this patch instead of deferring it
to the forthcoming occlusion culling PR.
Finally, this patch changes mesh preprocessing so that it runs
separately for indexed and non-indexed meshes. This is because draw
commands for indexed and non-indexed meshes have different sizes and
layouts. *The existing code is actually broken for non-indexed meshes*,
as it attempts to overlay the indirect parameters for non-indexed meshes
on top of those for indexed meshes. Consequently, right now the
parameters will be read incorrectly when multiple non-indexed meshes are
multi-drawn together. *This is a bug fix* and, as with the change to
dispatch phases separately noted above, was easiest to include in this
patch as opposed to separately.
## Migration Guide
* Systems that add custom phase items now need to populate the indirect
drawing-related buffers. See the `specialized_mesh_pipeline` example for
an example of how this is done.
We won't be able to retain render phases from frame to frame if the keys
are unstable. It's not as simple as simply keying off the main world
entity, however, because some main world entities extract to multiple
render world entities. For example, directional lights extract to
multiple shadow cascades, and point lights extract to one view per
cubemap face. Therefore, we key off a new type, `RetainedViewEntity`,
which contains the main entity plus a *subview ID*.
This is part of the preparation for retained bins.
---------
Co-authored-by: ickshonpe <david.curthoys@googlemail.com>
# Objective
The `camera_entity` field on the extracted uinode structs holds the
render world entity that has the extracted camera components
corresponding to the target camera world entity. It should be renamed so
that it's clear it isn't the target camera world entity itself.
## Solution
Rename the `camera_entity` field on each of the extracted UI item
structs to `extracted_camera_entity`.
# Objective
Many instances of `clippy::too_many_arguments` linting happen to be on
systems - functions which we don't call manually, and thus there's not
much reason to worry about the argument count.
## Solution
Allow `clippy::too_many_arguments` globally, and remove all lint
attributes related to it.
# Objective
`extract_shadows` uses the render world entity corresponding to the
extracted camera when it queries the main world for the camera to get
the viewport size for the responsive viewport coords resolution and
fails. This means that viewport coords get resolved based on a viewport
size of zero.
## Solution
Use the main world camera entity.
# Objective
- https://github.com/bevyengine/bevy/issues/17111
## Solution
Set the `clippy::allow_attributes` and
`clippy::allow_attributes_without_reason` lints to `deny`, and bring
`bevy_ui` in line with the new restrictions.
## Testing
`cargo clippy --tests` and `cargo test --package bevy_ui` were run, and
no errors were encountered.
# Objective
The name `DefaultCameraView` is confusing and misleading:
* It isn't the default UI camera, which is either the camera with the
`IsDefaultUiCamera` marker component or, if no such camera is found, the
camera with the highest order which has the primary window as its render
target.
* It doesn't make sense to call it a "default", every active 2d and 3d
camera is given its own `DefaultCameraView`.
* The name doesn't make it clear that it's UI specific component.
## Solution
Rename `DefaultCameraView` to `UiCameraView`, add a doc comment for it
and rename a few other fields and variables.
## Migration Guide
`DefaultCameraView` has been renamed to `UiCameraView`
# Objective
- Allow other crates to use `TextureAtlas` and friends without needing
to depend on `bevy_sprite`.
- Specifically, this allows adding `TextureAtlas` support to custom
cursors in https://github.com/bevyengine/bevy/pull/17121 by allowing
`bevy_winit` to depend on `bevy_image` instead of `bevy_sprite` which is
a [non-starter].
[non-starter]:
https://github.com/bevyengine/bevy/pull/17121#discussion_r1904955083
## Solution
- Move `TextureAtlas`, `TextureAtlasBuilder`, `TextureAtlasSources`,
`TextureAtlasLayout` and `DynamicTextureAtlasBuilder` into `bevy_image`.
- Add a new plugin to `bevy_image` named `TextureAtlasPlugin` which
allows us to register `TextureAtlas` and `TextureAtlasLayout` which was
previously done in `SpritePlugin`. Since `SpritePlugin` did the
registration previously, we just need to make it add
`TextureAtlasPlugin`.
## Testing
- CI builds it.
- I also ran multiple examples which hopefully covered any issues:
```
$ cargo run --example sprite
$ cargo run --example text
$ cargo run --example ui_texture_atlas
$ cargo run --example sprite_animation
$ cargo run --example sprite_sheet
$ cargo run --example sprite_picking
```
---
## Migration Guide
The following types have been moved from `bevy_sprite` to `bevy_image`:
`TextureAtlas`, `TextureAtlasBuilder`, `TextureAtlasSources`,
`TextureAtlasLayout` and `DynamicTextureAtlasBuilder`.
If you are using the `bevy` crate, and were importing these types
directly (e.g. before `use bevy::sprite::TextureAtlas`), be sure to
update your import paths (e.g. after `use bevy::image::TextureAtlas`)
If you are using the `bevy` prelude to import these types (e.g. `use
bevy::prelude::*`), you don't need to change anything.
If you are using the `bevy_sprite` subcrate, be sure to add `bevy_image`
as a dependency if you do not already have it, and be sure to update
your import paths.
# Objective
In UI extraction the default UI camera is queried for every UI node. It
only needs to be retrieved once.
## Solution
Query for the default UI camera once before iterating the UI nodes.
```
cargo run --example many_buttons --release --features "trace_tracy"
```
<img width="631" alt="default-camera-extract"
src="https://github.com/user-attachments/assets/db712bce-6a0b-49a7-8e20-654baf588390"
/>
`extract_uinode_background_colors` yellow is this PR, red is main.
# Objective
- Contributes to #11478
## Solution
- Made `bevy_utils::tracing` `doc(hidden)`
- Re-exported `tracing` from `bevy_log` for end-users
- Added `tracing` directly to crates that need it.
## Testing
- CI
---
## Migration Guide
If you were importing `tracing` via `bevy::utils::tracing`, instead use
`bevy::log::tracing`. Note that many items within `tracing` are also
directly re-exported from `bevy::log` as well, so you may only need
`bevy::log` for the most common items (e.g., `warn!`, `trace!`, etc.).
This also applies to the `log_once!` family of macros.
## Notes
- While this doesn't reduce the line-count in `bevy_utils`, it further
decouples the internal crates from `bevy_utils`, making its eventual
removal more feasible in the future.
- I have just imported `tracing` as we do for all dependencies. However,
a workspace dependency may be more appropriate for version management.
# Objective
The UI debug overlay draws an outline for every UI node even if it is
invisible or clipped.
Disable debug outlines for hidden and clipped nodes by default and add
options to renable them if needed.
## Solution
* Add `show_hidden` and `show_clipped` fields to `UiDebugOptions`:
```rust
/// Show outlines for non-visible UI nodes
pub show_hidden: bool,
/// Show outlines for clipped sections of UI nodes
pub show_clipped: bool,
```
* Only extract debug outlines for hidden and clipped UI nodes if the
respective field in `UiDebugOptions` is set to `true`.
## Testing
Also added some extra features to the `testbed_ui` example that
demonstrate the new options:
cargo run --example testbed_ui --features "bevy_ui_debug"
<img width="641" alt="show-hidden-and-clipped"
src="https://github.com/user-attachments/assets/16a68600-170c-469e-a3c7-f7dae411dc40"
/>
# Objective
Remove the `atlas_scaling` field from `ExtractedUiItem::Gylphs`.
It's only ever set to `Vec2::ONE`. I don't remember why/if this field
was ever needed, maybe it was useful before the scale factor clean up.
## Migration Guide
The `atlas_scaling` field from `ExtractedUiItem::Gylphs` has been
removed. This shouldn't affect any existing code as it wasn't used for
anything.
# Objective
When preparing `GpuImage`s, we currently discard the
`depth_or_array_layers` of the `Image`'s size by converting it into a
`UVec2`.
Fixes#16715.
## Solution
Change `GpuImage::size` to `Extent3d`, and just pass that through when
creating `GpuImage`s.
Also copy the `aspect_ratio`, and `size` (now `size_2d` for
disambiguation from the field) functions from `Image` to `GpuImage` for
ease of use with 2D textures.
I originally copied all size-related functions (like `width`, and
`height`), but i think they are unnecessary considering how visible the
`size` field on `GpuImage` is compared to `Image`.
## Testing
Tested via `cargo r -p ci` for everything except docs, when generating
docs it keeps spitting out a ton of
```
error[E0554]: `#![feature]` may not be used on the stable release channel
--> crates/bevy_dylib/src/lib.rs:1:21
|
1 | #![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
```
Not sure why this is happening, but it also happens without my changes,
so it's almost certainly some strange issue specific to my machine.
## Migration Guide
- `GpuImage::size` is now an `Extent3d`. To easily get 2D size, use
`size_2d()`.
# Objective
Fixes#16771
## Solution
Fixed typo in code.
## Testing
- Did you test these changes? If so, how?
I tested on my own example, that I included in the issue. It was
behaving as I expected.
Here is the screenshot after fix, the screenshot before the fix can be
found in the issue.

# Objective
The doc comments and function namings for `BorderRect` feel imprecise to
me. Particularly the `square` function which is used to define a uniform
`BorderRect` with equal widths on each edge. But this is potentially
confusing since this "square" border could be around an oblong shape.
Using "padding" to refer to the border extents seems undesirable too
since "padding" is typically used to refer to the area between border
and content, not the border itself.
## Solution
* Rename `square` to `all` (this matches the name of the similar method
on `UiRect`).
* Rename `rectangle` to `axes` (this matches the name of the similar
method on `UiRect`).
* Update doc comments.
## Migration Guide
The `square` and `rectangle` functions belonging to `BorderRect` have
been renamed to `all` and `axes`.
---------
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
# Objective
Draw the UI debug overlay using the UI renderer.
Significantly simpler and easier to use than
`bevy_dev_tools::ui_debug_overlay` which uses `bevy_gizmos`.
* Supports multiple windows and UI rendered to texture.
* Draws rounded debug rects for rounded UI nodes.
Fixes#16666
## Solution
Removed the `ui_debug_overlay` module from `bevy_dev_tools`.
Added a `bevy_ui_debug` feature gate.
Draw the UI debug overlay using the UI renderer.
Adds a new module `bevy_ui::render::debug_overlay`.
The debug overlay extraction function queries for the existing UI layout
and then adds a border around each UI node with `u32::MAX / 2` added to
each stack index so it's drawn on top.
There is a `UiDebugOptions` resource that can be used to enable or
disable the debug overlay and set the line width.
## Testing
The `testbed_ui` example has been changed to use the new debug overlay:
```
cargo run --example testbed_ui --features bevy_ui_debug
```
Press Space to toggle the debug overlay on and off.
---
## Showcase
<img width="961" alt="testbed-ui-new-debug"
src="https://github.com/user-attachments/assets/e9523d18-39ae-46a8-adbe-7d3f3ab8e951">
## Migration Guide
The `ui_debug_overlay` module has been removed from `bevy_dev_tools`.
There is a new debug overlay implemented using the `bevy_ui` renderer.
To use it, enable the `bevy_ui_debug` feature and set the `enable` field
of the `UiDebugOptions` resource to `true`.
This commit adds support for *multidraw*, which is a feature that allows
multiple meshes to be drawn in a single drawcall. `wgpu` currently
implements multidraw on Vulkan, so this feature is only enabled there.
Multiple meshes can be drawn at once if they're in the same vertex and
index buffers and are otherwise placed in the same bin. (Thus, for
example, at present the materials and textures must be identical, but
see #16368.) Multidraw is a significant performance improvement during
the draw phase because it reduces the number of rebindings, as well as
the number of drawcalls.
This feature is currently only enabled when GPU culling is used: i.e.
when `GpuCulling` is present on a camera. Therefore, if you run for
example `scene_viewer`, you will not see any performance improvements,
because `scene_viewer` doesn't add the `GpuCulling` component to its
camera.
Additionally, the multidraw feature is only implemented for opaque 3D
meshes and not for shadows or 2D meshes. I plan to make GPU culling the
default and to extend the feature to shadows in the future. Also, in the
future I suspect that polyfilling multidraw on APIs that don't support
it will be fruitful, as even without driver-level support use of
multidraw allows us to avoid expensive `wgpu` rebindings.
# Objective
The `UiBoxShadowSamples` resource should be renamed to
`BoxShadowSamples` so it matches the `BoxShadow` component.
## Migration Guide
`UiBoxShadowSamples` has been renamed to `BoxShadowSamples`
This patch adds the infrastructure necessary for Bevy to support
*bindless resources*, by adding a new `#[bindless]` attribute to
`AsBindGroup`.
Classically, only a single texture (or sampler, or buffer) can be
attached to each shader binding. This means that switching materials
requires breaking a batch and issuing a new drawcall, even if the mesh
is otherwise identical. This adds significant overhead not only in the
driver but also in `wgpu`, as switching bind groups increases the amount
of validation work that `wgpu` must do.
*Bindless resources* are the typical solution to this problem. Instead
of switching bindings between each texture, the renderer instead
supplies a large *array* of all textures in the scene up front, and the
material contains an index into that array. This pattern is repeated for
buffers and samplers as well. The renderer now no longer needs to switch
binding descriptor sets while drawing the scene.
Unfortunately, as things currently stand, this approach won't quite work
for Bevy. Two aspects of `wgpu` conspire to make this ideal approach
unacceptably slow:
1. In the DX12 backend, all binding arrays (bindless resources) must
have a constant size declared in the shader, and all textures in an
array must be bound to actual textures. Changing the size requires a
recompile.
2. Changing even one texture incurs revalidation of all textures, a
process that takes time that's linear in the total size of the binding
array.
This means that declaring a large array of textures big enough to
encompass the entire scene is presently unacceptably slow. For example,
if you declare 4096 textures, then `wgpu` will have to revalidate all
4096 textures if even a single one changes. This process can take
multiple frames.
To work around this problem, this PR groups bindless resources into
small *slabs* and maintains a free list for each. The size of each slab
for the bindless arrays associated with a material is specified via the
`#[bindless(N)]` attribute. For instance, consider the following
declaration:
```rust
#[derive(AsBindGroup)]
#[bindless(16)]
struct MyMaterial {
#[buffer(0)]
color: Vec4,
#[texture(1)]
#[sampler(2)]
diffuse: Handle<Image>,
}
```
The `#[bindless(N)]` attribute specifies that, if bindless arrays are
supported on the current platform, each resource becomes a binding array
of N instances of that resource. So, for `MyMaterial` above, the `color`
attribute is exposed to the shader as `binding_array<vec4<f32>, 16>`,
the `diffuse` texture is exposed to the shader as
`binding_array<texture_2d<f32>, 16>`, and the `diffuse` sampler is
exposed to the shader as `binding_array<sampler, 16>`. Inside the
material's vertex and fragment shaders, the applicable index is
available via the `material_bind_group_slot` field of the `Mesh`
structure. So, for instance, you can access the current color like so:
```wgsl
// `uniform` binding arrays are a non-sequitur, so `uniform` is automatically promoted
// to `storage` in bindless mode.
@group(2) @binding(0) var<storage> material_color: binding_array<Color, 4>;
...
@fragment
fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
let color = material_color[mesh[in.instance_index].material_bind_group_slot];
...
}
```
Note that portable shader code can't guarantee that the current platform
supports bindless textures. Indeed, bindless mode is only available in
Vulkan and DX12. The `BINDLESS` shader definition is available for your
use to determine whether you're on a bindless platform or not. Thus a
portable version of the shader above would look like:
```wgsl
#ifdef BINDLESS
@group(2) @binding(0) var<storage> material_color: binding_array<Color, 4>;
#else // BINDLESS
@group(2) @binding(0) var<uniform> material_color: Color;
#endif // BINDLESS
...
@fragment
fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
#ifdef BINDLESS
let color = material_color[mesh[in.instance_index].material_bind_group_slot];
#else // BINDLESS
let color = material_color;
#endif // BINDLESS
...
}
```
Importantly, this PR *doesn't* update `StandardMaterial` to be bindless.
So, for example, `scene_viewer` will currently not run any faster. I
intend to update `StandardMaterial` to use bindless mode in a follow-up
patch.
A new example, `shaders/shader_material_bindless`, has been added to
demonstrate how to use this new feature.
Here's a Tracy profile of `submit_graph_commands` of this patch and an
additional patch (not submitted yet) that makes `StandardMaterial` use
bindless. Red is those patches; yellow is `main`. The scene was Bistro
Exterior with a hack that forces all textures to opaque. You can see a
1.47x mean speedup.

## Migration Guide
* `RenderAssets::prepare_asset` now takes an `AssetId` parameter.
* Bin keys now have Bevy-specific material bind group indices instead of
`wgpu` material bind group IDs, as part of the bindless change. Use the
new `MaterialBindGroupAllocator` to map from bind group index to bind
group ID.
# Objective
Add support for multiple box shadows on a single `Node`.
## Solution
* Rename `BoxShadow` to `ShadowStyle` and remove its `Component` derive.
* Create a new `BoxShadow` component that newtypes a `Vec<ShadowStyle>`.
* Add a `new` constructor method to `BoxShadow` for single shadows.
* Change `extract_shadows` to iterate through a list of shadows per
node.
Render order is determined implicitly from the order of the shadows
stored in the `BoxShadow` component, back-to-front.
Might be more efficient to use a `SmallVec<[ShadowStyle; 1]>` for the
list of shadows but not sure if the extra friction is worth it.
## Testing
Added a node with four differently coloured shadows to the `box_shadow`
example.
---
## Showcase
```
cargo run --example box_shadow
```
<img width="460" alt="four-shadow"
src="https://github.com/user-attachments/assets/2f728c47-33b4-42e1-96ba-28a774b94b24">
## Migration Guide
Bevy UI now supports multiple shadows per node. A new struct
`ShadowStyle` is used to set the style for each shadow. And the
`BoxShadow` component is changed to a tuple struct wrapping a vector
containing a list of `ShadowStyle`s. To spawn a node with a single
shadow you can use the `new` constructor function:
```rust
commands.spawn((
Node::default(),
BoxShadow::new(
Color::BLACK.with_alpha(0.8),
Val::Percent(offset.x),
Val::Percent(offset.y),
Val::Percent(spread),
Val::Px(blur),
)
));
```
---------
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
# Objective
We switch back and forwards between logical and physical coordinates all
over the place. Systems have to query for cameras and the UiScale when
they shouldn't need to. It's confusing and fragile and new scale factor
bugs get found constantly.
## Solution
* Use physical coordinates whereever possible in `bevy_ui`.
* Store physical coords in `ComputedNode` and tear out all the unneeded
scale factor calculations and queries.
* Add an `inverse_scale_factor` field to `ComputedNode` and set nodes
changed when their scale factor changes.
## Migration Guide
`ComputedNode`'s fields and methods now use physical coordinates.
`ComputedNode` has a new field `inverse_scale_factor`. Multiplying the
physical coordinates by the `inverse_scale_factor` will give the logical
values.
---------
Co-authored-by: atlv <email@atlasdostal.com>
# Objective
It looks like this file was created based on the `ui_texture_slice`
rendering code and some variable names weren't updated.
## Solution
Rename "texture slice" variable names to "box shadow".
# Objective
UI Anti-aliasing is incorrectly implemented. It always uses an edge
radius of 0.25 logical pixels, and ignores the physical resolution. For
low dpi screens 0.25 is is too low and on higher dpi screens the
physical edge radius is much too large, resulting in visual artifacts.
## Solution
Multiply the distance by the scale factor in the `antialias` function so
that the edge radius stays constant in physical pixels.
## Testing
To see the problem really clearly run the button example with `UiScale`
set really high. With `UiScale(25.)` on main if you examine the button's
border you can see a thick gradient fading away from the edges:
<img width="127" alt="edgg"
src="https://github.com/user-attachments/assets/7c852030-c0e8-4aef-8d3e-768cb2464cab">
With this PR the edges are sharp and smooth at all scale factors:
<img width="127" alt="edge"
src="https://github.com/user-attachments/assets/b3231140-1bbc-4a4f-a1d3-dde21f287988">
# Objective
Fixes#15940
## Solution
Remove the `pub use` and fix the compile errors.
Make `bevy_image` available as `bevy::image`.
## Testing
Feature Frenzy would be good here! Maybe I'll learn how to use it if I
have some time this weekend, or maybe a reviewer can use it.
## Migration Guide
Use `bevy_image` instead of `bevy_render::texture` items.
---------
Co-authored-by: chompaa <antony.m.3012@gmail.com>
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
# Objective
- wgpu 0.20 made workgroup vars stop being zero-init by default. this
broke some applications (cough foresight cough) and now we workaround
it. wgpu exposes a compilation option that zero initializes workgroup
memory by default, but bevy does not expose it.
## Solution
- expose the compilation option wgpu gives us
## Testing
- ran examples: 3d_scene, compute_shader_game_of_life, gpu_readback,
lines, specialized_mesh_pipeline. they all work
- confirmed fix for our own problems
---
</details>
## Migration Guide
- add `zero_initialize_workgroup_memory: false,` to
`ComputePipelineDescriptor` or `RenderPipelineDescriptor` structs to
preserve 0.14 functionality, add `zero_initialize_workgroup_memory:
true,` to restore bevy 0.13 functionality.
# Objective
Use same pattern when creating `TransparentUi` items where the
`sort_key` is the `UiNode` stack index + some offset.
## Solution
Refactored to follow same pattern.
## Testing
Ran few UI examples.
## Doubts
Maybe `stack_z_offsets::BACKGROUND_COLOR` should be renamed. This is
used for `ExtractedUiNode`, which is not only used for "background
color" it's also used to render borders, images and text (I think).
# Objective
1. UI texture slicing chops and scales an image to fit the size of a
node and isn't meant to place any constraints on the size of the node
itself, but because the required components changes required `ImageSize`
and `ContentSize` for nodes with `UiImage`, texture sliced nodes are
laid out using an `ImageMeasure`.
2. In 0.14 users could spawn a `(UiImage, NodeBundle)` which would
display an image stretched to fill the UI node's bounds ignoring the
image's instrinsic size. Now that `UiImage` requires `ContentSize`,
there's no option to display an image without its size placing
constrains on the UI layout (unless you force the `Node` to a fixed
size, but that's not a solution).
3. It's desirable that the `Sprite` and `UiImage` share similar APIs.
Fixes#16109
## Solution
* Remove the `Component` impl from `ImageScaleMode`.
* Add a `Stretch` variant to `ImageScaleMode`.
* Add a field `scale_mode: ImageScaleMode` to `Sprite`.
* Add a field `mode: UiImageMode` to `UiImage`.
* Add an enum `UiImageMode` similar to `ImageScaleMode` but with
additional UI specific variants.
* Remove the queries for `ImageScaleMode` from Sprite and UI extraction,
and refer to the new fields instead.
* Change `ui_layout_system` to update measure funcs on any change to
`ContentSize`s to enable manual clearing without removing the component.
* Don't add a measure unless `UiImageMode::Auto` is set in
`update_image_content_size_system`. Mutably deref the `Mut<ContentSize>`
if the `UiImage` is changed to force removal of any existing measure
func.
## Testing
Remove all the constraints from the ui_texture_slice example:
```rust
//! This example illustrates how to create buttons with their textures sliced
//! and kept in proportion instead of being stretched by the button dimensions
use bevy::{
color::palettes::css::{GOLD, ORANGE},
prelude::*,
winit::WinitSettings,
};
fn main() {
App::new()
.add_plugins(DefaultPlugins)
// Only run the app when there is user input. This will significantly reduce CPU/GPU use.
.insert_resource(WinitSettings::desktop_app())
.add_systems(Startup, setup)
.add_systems(Update, button_system)
.run();
}
fn button_system(
mut interaction_query: Query<
(&Interaction, &Children, &mut UiImage),
(Changed<Interaction>, With<Button>),
>,
mut text_query: Query<&mut Text>,
) {
for (interaction, children, mut image) in &mut interaction_query {
let mut text = text_query.get_mut(children[0]).unwrap();
match *interaction {
Interaction::Pressed => {
**text = "Press".to_string();
image.color = GOLD.into();
}
Interaction::Hovered => {
**text = "Hover".to_string();
image.color = ORANGE.into();
}
Interaction::None => {
**text = "Button".to_string();
image.color = Color::WHITE;
}
}
}
}
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
let image = asset_server.load("textures/fantasy_ui_borders/panel-border-010.png");
let slicer = TextureSlicer {
border: BorderRect::square(22.0),
center_scale_mode: SliceScaleMode::Stretch,
sides_scale_mode: SliceScaleMode::Stretch,
max_corner_scale: 1.0,
};
// ui camera
commands.spawn(Camera2d);
commands
.spawn(Node {
width: Val::Percent(100.0),
height: Val::Percent(100.0),
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
..default()
})
.with_children(|parent| {
for [w, h] in [[150.0, 150.0], [300.0, 150.0], [150.0, 300.0]] {
parent
.spawn((
Button,
Node {
// width: Val::Px(w),
// height: Val::Px(h),
// horizontally center child text
justify_content: JustifyContent::Center,
// vertically center child text
align_items: AlignItems::Center,
margin: UiRect::all(Val::Px(20.0)),
..default()
},
UiImage::new(image.clone()),
ImageScaleMode::Sliced(slicer.clone()),
))
.with_children(|parent| {
// parent.spawn((
// Text::new("Button"),
// TextFont {
// font: asset_server.load("fonts/FiraSans-Bold.ttf"),
// font_size: 33.0,
// ..default()
// },
// TextColor(Color::srgb(0.9, 0.9, 0.9)),
// ));
});
}
});
}
```
This should result in a blank window, since without any constraints the
texture slice image nodes should be zero-sized. But in main the image
nodes are given the size of the underlying unsliced source image
`textures/fantasy_ui_borders/panel-border-010.png`:
<img width="321" alt="slicing"
src="https://github.com/user-attachments/assets/cbd74c9c-14cd-4b4d-93c6-7c0152bb05ee">
For this PR need to change the lines:
```
UiImage::new(image.clone()),
ImageScaleMode::Sliced(slicer.clone()),
```
to
```
UiImage::new(image.clone()).with_mode(UiImageMode::Sliced(slicer.clone()),
```
and then nothing should be rendered, as desired.
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
# Objective
Remove `calculated_` from the name `ComputedNode::calculated_size` as
redundant, It's obvious from context that it's the resolved size value
and it's inconsistant since none of other fields of `ComputedNode` have
a `calculated_` prefix.
## Alternatives
Rename all the fields of `ComputedNode` to `calculated_*`, this seems
worse.
# Objective
`UiImage` isn't just a general image component now, it's the defining
component for the image widget so it belongs in the image widget's
module.
# Objective
- Follow up on #16044
- `extract_uinode_borders` uses `bevy_hierarchy` directly instead of
going through the traversal utilities, meaning it won't handle
`GhostNode`s properly.
## Solution
- Replaced the use of `bevy_hierarchy::Parent` with
`UIChildren::get_parent`
## Testing
- Ran the `overflow` example, clipping looks ok.
---
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
# Objective
1. Nodes with `Display::None` set are removed from the layout and have
no position or size. Outlines should not be drawn for a node with
`Display::None` set.
2. The outline and border colors are checked for transparency together.
If only one of the two is transparent, both will get queued.
3. The `node.is_empty()` check is insufficient to check if a border is
present since a non-zero sized node can have a zero width border.
## Solution
1. Add a check to `extract_uinode_borders` and ignore the node if
`Display::None` is set.
2. Filter the border and outline optional components by
`is_fully_transparent`.
3. Check if all the border widths are zero instead.
## Testing
I added dark cyan outlines around the left and right sections in the
`display_and_visibility` example. If you run the example and set the
outermost node to `Display::None` on the right, then you'll see the that
the outline on the left disappears.
# Objective
fixes#15502
Clipped borders and outlines aren't drawn correctly.
### Borders aren't clipped
Spawn two nodes with the same dimensions and border thickness, but clip
on of the nodes so that only its top left quarter is visible:
<img width="194" alt="clip"
src="https://github.com/user-attachments/assets/2d3f6d28-aa20-44df-967a-677725828294">
You can see that instead of clipping the border, instead the border is
scaled to fit inside of the unclipped section.
```rust
use bevy::color::palettes::css::BLUE;
use bevy::prelude::*;
use bevy::winit::WinitSettings;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.insert_resource(WinitSettings::desktop_app())
.add_systems(Startup, setup)
.run();
}
fn setup(mut commands: Commands) {
commands.spawn(Camera2d);
commands
.spawn(Node {
width: Val::Percent(100.),
height: Val::Percent(100.),
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
..Default::default()
})
.with_children(|commands| {
commands
.spawn(Node {
column_gap: Val::Px(10.),
..Default::default()
})
.with_children(|commands| {
commands
.spawn(Node {
width: Val::Px(100.),
height: Val::Px(100.),
overflow: Overflow::clip(),
..Default::default()
})
.with_child((
Node {
position_type: PositionType::Absolute,
width: Val::Px(100.),
height: Val::Px(100.),
border: UiRect::all(Val::Px(10.)),
..Default::default()
},
BackgroundColor(Color::WHITE),
BorderColor(BLUE.into()),
));
commands
.spawn(Node {
width: Val::Px(50.),
height: Val::Px(50.),
overflow: Overflow::clip(),
..Default::default()
})
.with_child((
Node {
position_type: PositionType::Absolute,
width: Val::Px(100.),
height: Val::Px(100.),
border: UiRect::all(Val::Px(10.)),
..Default::default()
},
BackgroundColor(Color::WHITE),
BorderColor(BLUE.into()),
));
});
});
}
```
You can also see this problem in the `overflow` example. If you hover
over any of the clipped nodes you'll see that the outline only wraps the
visible section of the node
### Outlines are clipped incorrectly
A UI nodes Outline's are drawn outside of its bounds, so applying the
local clipping rect to the outline doesn't make any sense.
Instead an `Outline` should be clipped using its parent's clipping rect.
## Solution
* Pass the `point` value into the vertex shader instead of calculating
it in the shader.
* In `extract_uinode_borders` use the parents clipping rect when
clipping outlines.
The extra parameter isn't a great solution I think, but I wanted to fix
borders for the 0.15 release and this is the most minimal approach I
could think of without replacing the whole shader and prepare function.
## Showcase
<img width="149" alt="clipp"
src="https://github.com/user-attachments/assets/19fbd3cc-e7cd-42e1-a5e0-fd92aad04dcd">
---------
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
# Objective
- Fixes https://github.com/bevyengine/bevy/issues/15871
(Camera is done in #15946)
## Solution
- Do the same as #15904 for other extraction systems
- Added missing `SyncComponentPlugin` for DOF, TAA, and SSAO
(According to the
[documentation](https://dev-docs.bevyengine.org/bevy/render/sync_component/struct.SyncComponentPlugin.html),
this plugin "needs to be added for manual extraction implementations."
We may need to check this is done.)
## Testing
Modified example locally to add toggles if not exist.
- [x] DOF - toggling DOF component and perspective in `depth_of_field`
example
- [x] TAA - toggling `Camera.is_active` and TAA component
- [x] clusters - not entirely sure, toggling `Camera.is_active` in
`many_lights` example (no crash/glitch even without this PR)
- [x] previous_view - toggling `Camera.is_active` in `skybox` (no
crash/glitch even without this PR)
- [x] lights - toggling `Visibility` of `DirectionalLight` in `lighting`
example
- [x] SSAO - toggling `Camera.is_active` and SSAO component in `ssao`
example
- [x] default UI camera view - toggling `Camera.is_active` (nop without
#15946 because UI defaults to some camera even if `DefaultCameraView` is
not there)
- [x] volumetric fog - toggling existence of volumetric light. Looks
like optimization, no change in behavior/visuals
# Objective
Continue improving the user experience of our UI Node API in the
direction specified by [Bevy's Next Generation Scene / UI
System](https://github.com/bevyengine/bevy/discussions/14437)
## Solution
As specified in the document above, merge `Style` fields into `Node`,
and move "computed Node fields" into `ComputedNode` (I chose this name
over something like `ComputedNodeLayout` because it currently contains
more than just layout info. If we want to break this up / rename these
concepts, lets do that in a separate PR). `Style` has been removed.
This accomplishes a number of goals:
## Ergonomics wins
Specifying both `Node` and `Style` is now no longer required for
non-default styles
Before:
```rust
commands.spawn((
Node::default(),
Style {
width: Val::Px(100.),
..default()
},
));
```
After:
```rust
commands.spawn(Node {
width: Val::Px(100.),
..default()
});
```
## Conceptual clarity
`Style` was never a comprehensive "style sheet". It only defined "core"
style properties that all `Nodes` shared. Any "styled property" that
couldn't fit that mold had to be in a separate component. A "real" style
system would style properties _across_ components (`Node`, `Button`,
etc). We have plans to build a true style system (see the doc linked
above).
By moving the `Style` fields to `Node`, we fully embrace `Node` as the
driving concept and remove the "style system" confusion.
## Next Steps
* Consider identifying and splitting out "style properties that aren't
core to Node". This should not happen for Bevy 0.15.
---
## Migration Guide
Move any fields set on `Style` into `Node` and replace all `Style`
component usage with `Node`.
Before:
```rust
commands.spawn((
Node::default(),
Style {
width: Val::Px(100.),
..default()
},
));
```
After:
```rust
commands.spawn(Node {
width: Val::Px(100.),
..default()
});
```
For any usage of the "computed node properties" that used to live on
`Node`, use `ComputedNode` instead:
Before:
```rust
fn system(nodes: Query<&Node>) {
for node in &nodes {
let computed_size = node.size();
}
}
```
After:
```rust
fn system(computed_nodes: Query<&ComputedNode>) {
for computed_node in &computed_nodes {
let computed_size = computed_node.size();
}
}
```
# Objective
- UI materials reserve too much capacity in a vec: for every node in the
transparent phase, it reserves enough memory to store all the nodes
- Update #10437
## Solution
- Only reserve extra memory if there's not enough
- Only reserve the needed memory, not more
# Objective
In `queue_shadows`, the `UiBoxShadows` option is unwrapped incorrectly
which results in the number of shadow samples being set to
`u32::default()` instead of `UiBoxShadows::default()` if the camera
entity doesn't have the component.
## Solution
Just use `unwrap_or_default` directly without `map`.
# Objective
Remove `bevy-ui`'s non-functional "bevy_text" feature.
Fixes#15900
## Solution
Remove all the "bevy_text" cfg gates.
I tried to fix it at first but couldn't figure it out. I'll happily
withdraw this in favour of another PR that gets the feature gate
working.
# Objective
#15320 is a particularly painful breaking change, and the new
`RenderEntity` in particular is very noisy, with a lot of `let entity =
entity.id()` spam.
## Solution
Implement `WorldQuery`, `QueryData` and `ReadOnlyQueryData` for
`RenderEntity` and `WorldEntity`.
These work the same as the `Entity` impls from a user-facing
perspective: they simply return an owned (copied) `Entity` identifier.
This dramatically reduces noise and eases migration.
Under the hood, these impls defer to the implementations for `&T` for
everything other than the "call .id() for the user" bit, as they involve
read-only access to component data. Doing it this way (as opposed to
implementing a custom fetch, as tried in the first commit) dramatically
reduces the maintenance risk of complex unsafe code outside of
`bevy_ecs`.
To make this easier (and encourage users to do this themselves!), I've
made `ReadFetch` and `WriteFetch` slightly more public: they're no
longer `doc(hidden)`. This is a good change, since trying to vendor the
logic is much worse than just deferring to the existing tested impls.
## Testing
I've run a handful of rendering examples (breakout, alien_cake_addict,
auto_exposure, fog_volumes, box_shadow) and nothing broke.
## Follow-up
We should lint for the uses of `&RenderEntity` and `&MainEntity` in
queries: this is just less nice for no reason.
---------
Co-authored-by: Trashtalk217 <trashtalk217@gmail.com>
# Objective
Currently text is recomputed unnecessarily on any changes to its color,
which is extremely expensive.
## Solution
Split up `TextStyle` into two separate components `TextFont` and
`TextColor`.
## Testing
I added this system to `many_buttons`:
```rust
fn set_text_colors_changed(mut colors: Query<&mut TextColor>) {
for mut text_color in colors.iter_mut() {
text_color.set_changed();
}
}
```
reports ~4fps on main, ~50fps with this PR.
## Migration Guide
`TextStyle` has been renamed to `TextFont` and its `color` field has
been moved to a separate component named `TextColor` which newtypes
`Color`.