diff --git a/_release-content/release-notes/allow_self_relationships.md b/_release-content/release-notes/allow_self_relationships.md index 756cd360e4a5e..6e0d143b7225e 100644 --- a/_release-content/release-notes/allow_self_relationships.md +++ b/_release-content/release-notes/allow_self_relationships.md @@ -4,13 +4,10 @@ authors: ["@mrchantey"] pull_requests: [22269] --- -Relationships can now optionally point to their own entity by setting the `allow_self_referential` attribute on the `#[relationship]` macro. +By default, Bevy rejects relationship components that point to the entity they live on. If you insert one, Bevy will log a warning and remove it. +This default exists for good reason: structural relationships like `ChildOf` form hierarchies that Bevy traverses recursively — a self-referential `ChildOf` would produce an infinite loop. -By default pointing a relationship to its own entity will log a warning and remove the component. However, self-referential relationships are semantically valid in many cases: `Likes(self)`, `EmployedBy(self)`, `TalkingTo(self)`, `Healing(self)`, and many more. - -## Usage - -To allow a relationship to point to its own entity, add the `allow_self_referential` attribute: +But many relationships are purely semantic. `Likes(self)`, `EmployedBy(self)`, `Healing(self)` — these don't imply any traversal, and self-reference is perfectly valid. You can now opt in with `allow_self_referential`: ```rust #[derive(Component)] @@ -22,13 +19,4 @@ pub struct LikedBy(pub Entity); pub struct PeopleILike(Vec); ``` -Now entities can have relationships that point to themselves: - -```rust -let entity = world.spawn_empty().id(); -world.entity_mut(entity).insert(LikedBy(entity)); - -// The relationship is preserved -assert!(world.entity(entity).contains::()); -assert!(world.entity(entity).contains::()); -``` +With the attribute set, inserting a self-referential relationship is accepted without warning. diff --git a/_release-content/release-notes/area_lights.md b/_release-content/release-notes/area_lights.md deleted file mode 100644 index c6cc3d4922b5d..0000000000000 --- a/_release-content/release-notes/area_lights.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -title: Rectangle Area Lights -authors: ["@dylansechet"] -pull_requests: [23288] ---- - -*TODO: maybe add image from the PR?* - -Bevy's lighting toolkit just got a new addition: rectangular area lights! - -The implementation uses [Linearly Transformed Cosines](https://eheitzresearch.wordpress.com/415-2/), which is the standard method for real-time area lights and should also help make our spherical area lights more accurate in the near future. - -Rectangular lights currently don't cast shadows or have support for anisotropic materials. - -You need to enable the `area_light_luts` cargo feature to use it. - -Check out [the new example](https://github.com/bevyengine/bevy/tree/latest/examples/3d/rect_light.rs) to see them in action. diff --git a/_release-content/release-notes/asset_saving.md b/_release-content/release-notes/asset_saving.md index 3fb6f039568d8..c1296f0967315 100644 --- a/_release-content/release-notes/asset_saving.md +++ b/_release-content/release-notes/asset_saving.md @@ -1,18 +1,18 @@ --- title: Asset Saving authors: ["@andriyDev"] -pull_requests: [] +pull_requests: [22622] --- -Since Bevy 0.12, we've had the `AssetSaver` trait. Unfortunately, this trait was not really usable -for asset saving: it was only intended for use with asset processing! This was a common stumbling -block for users, and pointed to a gap in our API. +Bevy has had an `AssetSaver` trait since 0.12. +However, it was only ever intended for use inside asset processing pipelines, not for saving assets at runtime. +This left a frustrating gap: if you wanted to save a procedurally generated mesh, a baked lightmap, or the output of an in-editor workflow, there was no supported path to do it. -Now, users can save their assets using `save_using_saver`. To use this involves two steps. +Now there is. `save_using_saver` lets you save any asset to disk using an `AssetSaver` implementation of your choice. ## 1. Building the `SavedAsset` -To build the `SavedAsset`, either use `SavedAsset::from_asset`: +For simple assets with no sub-assets, use `SavedAsset::from_asset`: ```rust let main_asset = InlinedBook { @@ -21,10 +21,10 @@ let main_asset = InlinedBook { let saved_asset = SavedAsset::from_asset(&main_asset); ``` -Or for more complicated cases, `SavedAssetBuilder`: +For assets that reference other assets (sub-assets), use `SavedAssetBuilder`: ```rust -let asset_path: AssetPath<'static> = "my/file/path.whatever"; +let asset_path: AssetPath<'static> = "my/file/path.whatever".into(); let mut builder = SavedAssetBuilder::new(asset_server.clone(), asset_path.clone()); let subasset_1 = Line("howdy".into()); @@ -40,13 +40,11 @@ let main_asset = Book { let saved_asset = builder.build(&main_asset); ``` -Note that since these assets are borrowed, building the `SavedAsset` should happen in the same async -task as the next step. +`SavedAsset` borrows rather than owns its assets. +That means you can build and save in the same async block — no need to transfer ownership first. ## 2. Calling `save_using_saver` -Now, with a `SavedAsset`, we can just call `save_using_saver` and fill in any arguments: - ```rust save_using_saver( asset_server.clone(), @@ -57,5 +55,6 @@ save_using_saver( ).await.unwrap(); ``` -Part of this includes implementing the `AssetSaver` trait on `MyAssetSaver`. In addition, this is an -async function, so it is likely you will want to spawn this using `IoTaskPool::get().spawn(...)`. +`save_using_saver` is async. +Generally, you'll want to spawn it with `IoTaskPool::get().spawn(...)`. +You'll also need to implement `AssetSaver` for `MyAssetSaver` to define the serialization format. diff --git a/_release-content/release-notes/contiguous_access.md b/_release-content/release-notes/contiguous_access.md index 6921cd30c09f3..2e93a87bbc3f8 100644 --- a/_release-content/release-notes/contiguous_access.md +++ b/_release-content/release-notes/contiguous_access.md @@ -1,30 +1,48 @@ --- -title: Contiguous access +title: Contiguous query access authors: ["@Jenya705"] -pull_requests: [21984] +pull_requests: [21984, 24181] --- -Enables accessing slices from tables directly via Queries. +[SIMD] is a critical modern tool for performance optimization, but using it in Bevy has always been harder than it needed to be. +Table components in Bevy are already laid out flat in memory — all `Transform` components are stored as values in a contiguous table, exactly what SIMD wants. +The `Query` iterator just wasn't exposing that structure: it handed you one entity's component at a time, and the compiler had no way to know the underlying data was a contiguous array. -## Goals +`contiguous_iter` and `contiguous_iter_mut` hand you the whole table slice at once. LLVM can see the contiguous array and auto-vectorize — or you can reach for explicit SIMD yourself. -`Query` and `QueryState` have new methods `contiguous_iter`, `contiguous_iter_mut` and `contiguous_iter_inner`, which allows querying contiguously (i.e., over tables). For it to work the query data must implement `ContiguousQueryData` and the query filter `ArchetypeFilter`. When a contiguous iterator is used, the iterator will jump over whole tables, returning corresponding data. Some notable implementors of `ContiguousQueryData` are `&T` and `&mut T`, returning `&[T]` and `ContiguousMut` correspondingly, where the latter structure lets you get a mutable slice of components as well as corresponding ticks. Some notable implementors of `ArchetypeFilter` are `With` and `Without` and notable types **not implementing** it are `Changed` and `Added`. +On a bulk `position += velocity` update over 10,000 entities, this gives some serious speedups: -For example, this is useful, when an operation must be applied on a large amount of entities lying in the same tables, which allows for the compiler to auto-vectorize the code, thus speeding it up. +| Method | Time | Time (AVX2) | +| ------------------------------- | ------- | ----------- | +| Normal iteration | 5.58 µs | 5.51 µs | +| Contiguous iteration | 4.88 µs | 1.87 µs | +| Contiguous, no change detection | 4.40 µs | 1.58 µs | -### Usage - -`Query::contiguous_iter` and `Query::contiguous_iter_mut` return a `Option`, which is only `None`, when the query is not dense (i.e., iterates over archetypes, not over tables). +If your project has CPU-heavy workloads (physics engines are a prime example), you should try this out immediately. ```rust -fn apply_velocity(query: Query<(&Velocity, &mut Position)>) { - // `contiguous_iter_mut()` cannot ensure all invariants on the compilation stage, thus - // when a component uses a sparse set storage, the method will return `None` - for (velocity, mut position) in query.contiguous_iter_mut().unwrap() { - // we could also have used position.bypass_change_detection() to do even less work. - for (v, p) in velocity.iter().zip(position.iter_mut()) { - p.0 += v.0; +fn apply_health_decay(mut query: Query<(&mut Health, &HealthDecay)>) { + for (mut health, decay) in query.contiguous_iter_mut().unwrap() { + for (h, d) in health.iter_mut().zip(decay) { + h.0 *= d.0; } } } ``` + +The `contiguous_iter` family of methods only returns `Ok` if the query is dense. That means: + +- All of the fetched components must use the default "table" storage strategy. +- The query filters cannot disrupt the returned query data. "Archetypal filters" like `With` and `Without` are fine; `Changed` and `Added` are not, since they require a per-entity check that makes it impossible to return raw table slices. + +Because these conditions are fixed properties of the query type, you're safe to unwrap here unless you are writing generic code, +or working with dynamic components. + +You may have noticed that the table above had *three* rows. +While change detection is a generally useful feature, it does incur measurable performance overhead. +By default, `contiguous_iter_mut` returns `ContiguousMut`. +Just like the ordinary `Mut`, it triggers change detection automatically on dereference. +If you don't care about that, `bypass_change_detection()` gives you the raw `&mut [T]` directly for even faster access. +Vroom! + +[SIMD]: https://en.wikipedia.org/wiki/Single_instruction,_multiple_data diff --git a/_release-content/release-notes/delayed_commands.md b/_release-content/release-notes/delayed_commands.md index 640012fed125e..a8533032803c0 100644 --- a/_release-content/release-notes/delayed_commands.md +++ b/_release-content/release-notes/delayed_commands.md @@ -5,8 +5,12 @@ pull_requests: [23090] --- Scheduling things to happen some time in the future is a common and useful tool in game development -for everything from gameplay logic to VFX. To support common use-cases, Bevy now has a general mechanism -for delaying commands to be executed after a specified duration. +for everything from gameplay logic to audio cues to VFX. + +While this was previously possible through careful use of timers, +getting the details right was surprisingly tricky and naive solutions were heavy on boilerplate. + +Now, you can simply delay arbitrary commands to be executed later. ```rust fn delayed_spawn(mut commands: Commands) { @@ -20,9 +24,5 @@ fn delayed_spawn_then_insert(mut commands: Commands) { } ``` -Our goal for this mechanism is to provide a "good-enough" system for simple use-cases. As a result, -there are certain limitations - for example, delayed commands are currently always ticked by the default -clock during `PreUpdate` (typically `Time`). - -If you need something more sophisticated, you can always roll your own version of delayed commands using -the new helpers added for this feature. +Note that this does not have a built-in, blessed cancellation mechanism yet. +We recommend embedding the originating `Entity` into the command if you want to cancel the action if that entity dies or is despawned. diff --git a/_release-content/release-notes/diagnostics_overlay.md b/_release-content/release-notes/diagnostics_overlay.md index 2ed4919799ac3..72302377626ba 100644 --- a/_release-content/release-notes/diagnostics_overlay.md +++ b/_release-content/release-notes/diagnostics_overlay.md @@ -4,34 +4,28 @@ authors: ["@hukasu"] pull_requests: [22486] --- -You can now visualize values from the `DiagnosticStore` using a `DiagnosticsOverlay` window. +*TODO: Add a screenshot of the DiagnosticsOverlay in a real app.* -An overlay can be built by spawning an entity with the [`DiagnosticsOverlay`] component -passing your custom [`DiagnosticPath`] list or using one of the provided presets. +Bevy's diagnostics have always been easy to dump to the terminal, but displaying them in-game meant wiring up your own UI. +`DiagnosticsOverlayPlugin` adds a built-in overlay for this, with presets for common cases: ```rust -commands.spawn(DiagnosticsOverlay::new("MyDiagnostics", vec![MyDiagnostics::COUNTER.into()])); commands.spawn(DiagnosticsOverlay::fps()); commands.spawn(DiagnosticsOverlay::mesh_and_standard_materials()); ``` -By default the overlay will display the smoothed moving average for the diagnostic, but -you can also visualize the latest value or the moving average by passing -[`DiagnosticsOverlayStatistic`]. The floating point precision can also be configured -through the [`DiagnosticsOverlayItem::precision`] field. +You can also build a custom overlay from any [`DiagnosticPath`] list: + +```rust +commands.spawn(DiagnosticsOverlay::new("MyDiagnostics", vec![MyDiagnostics::COUNTER.into()])); +``` + +By default the overlay shows the smoothed moving average. You can switch to the latest value or the raw moving average via [`DiagnosticsOverlayStatistic`], and configure floating-point precision with [`DiagnosticsOverlayItem::precision`]: ```rust commands.spawn(DiagnosticsOverlay::new("MyDiagnostics", vec![DiagnosticsOverlayItem { path: MyDiagnostics::COUNTER, statistic: DiagnosticsOverlayStatistic::Value, - precisiom: 4, + precision: 4, }])); ``` - -All [`DiagnosticsOverlay`] will be managed by the [`DiagnosticsOverlayPlugin`], this includes -having them being added as a child of [`DiagnosticsOverlayPlane`]. The plane will be initially -spawned on the [`GlobalZIndex`] defined by [`INITIAL_DIAGNOSTICS_OVERLAY_Z_INDEX`]. You can order -the UIs relative to it, or edit the [`GlobalZIndex`] of the plane. - -The contents of the [`DiagnosticsOverlay`] entity are rebuilt every second. The system that rebuilds -the contents of the overlays is defined on the [`DiagnosticsOverlaySystems::Rebuild`] system set. diff --git a/_release-content/release-notes/generic_font_families.md b/_release-content/release-notes/generic_font_families.md deleted file mode 100644 index 5b227cc053318..0000000000000 --- a/_release-content/release-notes/generic_font_families.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -title: "Generic Font Families" -authors: ["@ickshonpe"] -pull_requests: [22396, 22879] ---- - -Bevy now supports generic font families, allowing font faces to be selected using broadly defined categories (such as `FontSource::Serif` or `FontSource::Monospace`) without naming a specific font family. - -The `FontCx` resource can be used to update the font family associated with each generic font variant: - -```rust -fn font_families(mut font_system: ResMut) { - font_system.set_serif_family("Allegro"); - font_system.set_sans_serif_family("Encode Sans"); - font_system.set_cursive_family("Cedarville Cursive"); - font_system.set_fantasy_family("Argusho"); - font_system.set_monospace_family("Lucida Console"); - - // `FontCx::get_family` can be used to look up the family associated with a `FontSource` - let family_name = font_system.get_family(&FontSource::Serif).unwrap(); - assert_eq!(family_name.as_str(), "Allegro"); -} -``` diff --git a/_release-content/release-notes/infinite_grid.md b/_release-content/release-notes/infinite_grid.md index d16338b7dc0ed..76da3c71bab41 100644 --- a/_release-content/release-notes/infinite_grid.md +++ b/_release-content/release-notes/infinite_grid.md @@ -1,14 +1,57 @@ --- -title: "Add an infinite grid to bevy_dev_tools" -authors: [ "@icesentry" ] +title: "Infinite Grid" +authors: [ "@IceSentry" ] pull_requests: [ 23482 ] --- -When working on a 3d scene in an editor it's often very useful to have a transparent grid that indicates the ground plane and the major axis. +*TODO: Add a screenshot of the infinite grid with a real model.* -There are various techniques to render an infinite grid and avoid artifacts. -This implementation works by rendering the grid as a fullscreen shader. -The grid is rendered from the perspective of the camera and fades out relative to the camera position. -The fade out hides artifacts from drawing lines too far in the horizon. +A transparent ground-plane grid is a staple of 3D editor tooling: it marks the major axes, orients the scene, and makes scale immediately legible. -This is an upstreamed version of the bevy_infinite_grid crate that is maintained by foresight spatial labs. +Simply drawing lines doesn't work well: the mesh has to end somewhere, and the lines that reach toward the horizon create aliasing artifacts and Moiré patterns no matter how far you extend it. + +Our implementation renders the grid as a fullscreen shader: the grid is computed per-pixel in screen space from the camera's perspective, and fades out with distance to eliminate aliasing at the horizon. + +To add an infinite grid to your app, register `InfiniteGridPlugin` and spawn the `InfiniteGrid` component: + +```rust +use bevy::dev_tools::infinite_grid::{InfiniteGrid, InfiniteGridPlugin}; +use bevy::prelude::*; + +App::new() + .add_plugins((DefaultPlugins, InfiniteGridPlugin)) + .add_systems(Startup, setup) + .run(); + +fn setup(mut commands: Commands) { + commands.spawn(InfiniteGrid); +} +``` + +Grid appearance — colors, fade distance, line scale — is controlled by `InfiniteGridSettings`, which can be placed on the grid entity or on a specific camera to override it per-view: + +```rust +use bevy::dev_tools::infinite_grid::{InfiniteGrid, InfiniteGridSettings}; + +// On the grid entity (applies to all cameras) +commands.spawn(( + InfiniteGrid, + InfiniteGridSettings { + fadeout_distance: 200.0, + ..default() + }, +)); + +// On a camera (overrides settings for that camera only) +commands.spawn(( + Camera3d::default(), + InfiniteGridSettings { + scale: 0.5, + ..default() + }, +)); +``` + +This is an upstreamed version of the [`bevy_infinite_grid` crate], created and maintained by Foresight Spatial Labs — thank you for building it and generously contributing it to Bevy! + +[`bevy_infinite_grid` crate]: https://github.com/fslabs/bevy_infinite_grid diff --git a/_release-content/release-notes/lens_distortion.md b/_release-content/release-notes/lens_distortion.md deleted file mode 100644 index e6020fef34088..0000000000000 --- a/_release-content/release-notes/lens_distortion.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -title: "Lens Distortion" -authors: ["@Breakdown-Dog"] -pull_requests: [23110] ---- - -We’ve added lens distortion, which is a common post-processing effect that creates a spatial warping of the image towards the periphery compared to the image center. It’s often used to simulate the optical characteristics of a real camera lens or to add a stylized, dynamic look to the scene. - -To use it, add the `LensDistortion` component to your camera: - -```rust -commands.spawn(( - Camera3d::default(), - LensDistortion::default(), -)) -``` diff --git a/_release-content/release-notes/more_widgets.md b/_release-content/release-notes/more_widgets.md index 7813a6d63ec01..36b7cd14f8a53 100644 --- a/_release-content/release-notes/more_widgets.md +++ b/_release-content/release-notes/more_widgets.md @@ -1,17 +1,52 @@ --- -title: "Moar widgets!" +title: "More Feathers widgets" authors: ["@viridia"] -pull_requests: [23645, 23707, 23788, 23787, 23804, 23842] +pull_requests: [23645, 23707, 23788, 23787, 23804, 23817, 23842] --- -Bevy Feathers, the opinionated UI widget collection, has added several new widgets: +*TODO: Add screenshots of the new Feathers widgets (text input, number input, dropdown, pane/group decorators).* -- text input -- number input -- dropdown menu buttons -- icon (displays an image) -- label (displays a text string in the standard font) -- pane, subpane, and group (decorative frames which can be used in editors) +Bevy Feathers, our opinionated UI widget collection designed with the Bevy editor in mind, has added several new widgets this cycle: -Note that unlike the older widgets, these are _only_ available through -BSN (which will be the primary access to feathers going forward). +- Text input (see the dedicated release note for far more details) +- Number input +- Dropdown menu button and menu divider +- Disclosure toggle (chevron expand/collapse) +- Icon and label (display primitives) +- Pane, subpane, and group (decorative frames for editors) + +For full usage and an interactive demo, try out the [`feathers_gallery`] example. + +[`feathers_gallery`]: https://github.com/bevyengine/bevy/blob/main/examples/ui/widgets/feathers_gallery.rs + +## Feathers + BSN = ❤️ + +The Feathers widgets are migrating to BSN, Bevy's next-generation scene system. +The new widgets above are BSN-only from the start; the older widgets (button, checkbox, slider, and friends) now have a `bsn!` definition (their original APIs have been deprecated). + +BSN is a better foundation for widgets than the old spawn-function approach. +UI controls are inherently multi-entity assemblages — a slider isn't one node, it's a track, a fill, a thumb, and a label wired together. +BSN describes all of that in one place, reduces boilerplate, and lets you compose widgets together, attach props, reference font/image assets and register observers in the same declaration. + +```rust +// Before: label children passed as a generic argument, observer wired separately +commands.spawn(checkbox_bundle( + MyCheckbox, + children![(Text::new("Enable shadows"), ThemedText)], +)).observe(|trigger: Trigger>, mut config: ResMut| { + config.enabled = trigger.value; +}); + +// After: caption, extra components, and observer all defined in one call +bsn! { + :FeathersCheckbox { + @caption: { bsn! { Text("Enable shadows") ThemedText } } + } + MyCheckbox + on(|change: On>, mut config: ResMut| { + config.enabled = change.value; + }) +} +``` + +In the future, the same BSN syntax used in the `bsn!` macro will be portable to `.bsn` files, letting devs choose and rapidly swap between code-first and asset-driven workflows when defining UI. diff --git a/_release-content/release-notes/new_font_features.md b/_release-content/release-notes/new_font_features.md index d4befd9faa33d..fc6055609b70e 100644 --- a/_release-content/release-notes/new_font_features.md +++ b/_release-content/release-notes/new_font_features.md @@ -1,84 +1,93 @@ --- -title: "New Text features" -authors: [ "@ickshonpe", "@gregcsokas" ] -pull_requests: [ 22156, 22614, 22879, 23380 ] +title: "Richer text" +authors: ["@ickshonpe", "@alice-i-cecile", "@gregcsokas"] +pull_requests: [22156, 22396, 22614, 22879, 23380] --- -## Text Font +TODO: screenshot showing generic families, weights, and responsive sizing in action -`TextFont` has been expanded to include new fields: +Bevy's text system has historically been sparse: pick a font by asset handle, set a size in pixels, done. +Want bold? Load a separate bold font asset. +Want italic? Another asset. +Want the user's system monospace? No luck. +Want text that scales with the viewport? Roll it yourself. -```rust -pub struct TextFont { - pub font: FontSource, - pub font_size: FontSize, - pub weight: FontWeight, - pub width: FontWidth, - pub style: FontStyle, - pub font_smoothing: FontSmoothing, - pub font_features: FontFeatures, -} -``` +Not anymore. -FontSource has two variants: Handle, which identifies a font by asset handle, and Family, which selects a font by its -family name. +## Better font selection -`FontWidth` is a newtype struct representing OpenType font stretch classifications ranging from `ULTRA_CONDENSED` (50%) -to `ULTRA_EXPANDED` (200%). +`FontSource` now offers three ways to identify a font: -`FontStyle` is an enum used to set the slant style of a font, with variants `Normal`, `Italic`, or `Oblique`. - -The system font support is very basic for now. To load them, you must enable the `bevy/system_font_discovery` feature. +```rust +// By asset handle — same behavior as before, now wrapped in FontSource +TextFont::default().with_font(asset_server.load("fonts/FiraMono.ttf")) -Then they are available to be selected by family name using `FontSource::Family` via the `FontCx` resource. +// By family name — resolved from the font database +TextFont { font: FontSource::Family("FiraMono".into()), ..default() } -The `font_size` field is now a `FontSize`, enabling responsive font sizing. +// By semantic category +TextFont { font: FontSource::Monospace, ..default() } +``` -`FontSize` is an enum with variants: +The generic variants — `Serif`, `SansSerif`, `Cursive`, `Fantasy`, `Monospace`, and several UI-specific ones (`SystemUi`, `Emoji`, `Math`, and others) — resolve to configurable defaults. Override them via `FontCx`: ```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), +fn configure_fonts(mut font_cx: ResMut) { + font_cx.set_serif_family("Merriweather"); + font_cx.set_monospace_family("JetBrains Mono"); } ``` -`Rem` units are currently resolved using `RemSize`, which is a new `Resource`. `RemSize` just newtypes an `f32` -currently. +Editor tooling and non-game applications that want to respect the user's font preferences without hardcoding an asset path will find this particularly useful. -`Text2d`'s support for viewport coords is limited. A `Text2d` entity's resolved font size is always based on the size of -the primary window, not on the size of its render target(s). +System fonts were already loadable via the backend resource in previous releases, but `FontSource::Family` is a cleaner, more powerful way to load them. +Enable the `bevy/system_font_discovery` feature to make installed system fonts available by name; without it, `FontSource::Family("...")` will only find fonts explicitly loaded as Bevy assets. -## Letter Spacing +## Variable font properties -A new `LetterSpacing` component has been added, controlling the spacing between -characters in a text entity. It follows the same pattern as `LineHeight` and supports -absolute and relative units: +`TextFont` has gained the `weight`, `width`, and `style` fields. Pick a variable font, and say goodbye to separate assets for every variant of a typeface: ```rust -pub enum LetterSpacing { - /// Letter spacing in pixels. - Px(f32), - /// Letter spacing relative to the `RemSize` resource. - Rem(f32), +TextFont { + font: FontSource::SansSerif, + weight: FontWeight::BOLD, + style: FontStyle::Italic, + width: FontWidth::CONDENSED, + ..default() } ``` -The default value is `LetterSpacing::Px(0.0)`, preserving existing behavior. -Negative values are supported and bring characters closer together. +`FontWeight` accepts any value from 1–1000. `FontStyle` is `Normal`, `Italic`, or `Oblique`. +`FontWidth` covers the full OpenType stretch range from `ULTRA_CONDENSED` (50%) to `ULTRA_EXPANDED` (200%). + +## Responsive font sizing + +`font_size` is now a `FontSize` enum rather than a bare `f32`: + +```rust +TextFont::from_font_size(FontSize::Px(24.0)) // fixed pixels — unchanged behavior +TextFont::from_font_size(FontSize::Vh(5.0)) // 5% of viewport height +TextFont::from_font_size(FontSize::Rem(1.5)) // relative to the RemSize resource +``` + +The full set of variants mirrors CSS: `Px`, `Vw`, `Vh`, `VMin`, `VMax`, and `Rem`. `Rem` values scale with the `RemSize` resource, giving you a single knob to resize all relative text at once. Note that `Text2d` resolves viewport units against the primary window, not the render target — a deliberate compromise for entities that can render to multiple viewports. + +## Letter spacing + +A new `LetterSpacing` component controls the spacing between characters: ```rust commands.spawn(( - Text::new("Hello, Bevy!"), + Text::new("SPACED OUT"), LetterSpacing::Px(4.0), )); +``` + +It follows the same pattern as `LineHeight`, so negative values bring characters closer together. Note that LetterSpacing currently only supports `Px` — `Rem` support remains planned. + +While all of these features would have been possible in [`cosmic_text`], +we've chosen to migrate to [`parley`] during this cycle. +Both are solid, modern choices, but we found `parley` had meaningfully better documentation and was somewhat nicer to use. + +[`cosmic_text`]: https://github.com/pop-os/cosmic-text +[`Parley`]: https://github.com/linebender/parley diff --git a/_release-content/release-notes/new_ui_debug_overlay_features.md b/_release-content/release-notes/new_ui_debug_overlay_features.md deleted file mode 100644 index a3f79b9758a47..0000000000000 --- a/_release-content/release-notes/new_ui_debug_overlay_features.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -title: "New UI debug overlay features" -authors: ["@ickshonpe"] -pull_requests: [21931] ---- - -`UiDebugOptions` now lets you toggle outlines for border, padding, content and scrollbar regions, and optionally ignore border radius to render node outlines without curved corners. It can be used both as a `Resource` (global defaults) and as a `Component` (per-node overrides). - -The scroll example was updated to outline the scrollbar bounds when the `bevy_ui_debug` feature is enabled. diff --git a/_release-content/release-notes/observer_run_conditions.md b/_release-content/release-notes/observer_run_conditions.md index d17cb3c4dea47..8da289bacd0ac 100644 --- a/_release-content/release-notes/observer_run_conditions.md +++ b/_release-content/release-notes/observer_run_conditions.md @@ -4,7 +4,11 @@ authors: ["@jonas-meyer"] pull_requests: [22602] --- -Observers can now use run conditions with `.run_if()`, the same pattern systems use for conditional execution. +Run conditions are a convenient, reusable pattern for skipping systems when certain conditions are met. +Previously, run conditions only worked for ordinary systems. +Observers couldn't use them. + +Now, they can! ```rust #[derive(Resource)] diff --git a/_release-content/release-notes/partial_bindless_metal.md b/_release-content/release-notes/partial_bindless_metal.md index d8a4efd0fa5e8..0e4507d40dbbc 100644 --- a/_release-content/release-notes/partial_bindless_metal.md +++ b/_release-content/release-notes/partial_bindless_metal.md @@ -4,20 +4,33 @@ authors: ["@holg"] pull_requests: [23436] --- -Bindless rendering was previously disabled entirely on Metal (macOS, iOS) because Bevy required both `TEXTURE_BINDING_ARRAY` and `BUFFER_BINDING_ARRAY` support unconditionally. Metal supports the former but not the latter. Since `StandardMaterial` only needs texture and sampler binding arrays - not buffer binding arrays — this requirement was unnecessarily restrictive. +Cross-platform game engines must constantly navigate real differences in platform APIs. +Bevy's goal is to let users write a single application and ship it everywhere — +Windows, Mac, Linux, mobile — with confidence that it will just work. +That's a tough promise to live up to: rendering complex scenes on Mac and iOS was markedly slower. -`BUFFER_BINDING_ARRAY` is now only required when a material actually uses buffer binding arrays. Materials that only use `#[data(...)]`, textures, and samplers (including `StandardMaterial`) can now use the bindless path on Metal. A related fix corrects the sampler limit check to use `max_binding_array_sampler_elements_per_shader_stage` (the array element count) instead of `max_samplers_per_shader_stage` (the binding slot count). +Bindless rendering is how modern engines handle scenes with many different materials efficiently: shaders index into shared pools of textures and buffers rather than rebinding them per draw call. +Bindless is not just a performance optimization — it's how modern renderers are structured. -Additionally, `create_bindless_bind_group_layout_entries` now only creates binding arrays for resource types the material actually uses, reducing bind group overhead and memory consumption on all platforms. +Metal (Apple's GPU API) supports texture binding arrays but not buffer binding arrays. +Bevy required both to enable bindless, which previously excluded Metal entirely — even for materials that never use buffer arrays. +If you were shipping on Mac or iOS, your game was running on a slower, fundamentally different code path. -## Performance +Most materials, including `StandardMaterial`, only use `#[data(...)]`, textures, and samplers — they never needed buffer array support. +Bevy now checks what each material actually needs; +if it only needs texture arrays, it gets bindless on Metal. +Materials using `#[uniform(..., binding_array(...))]` still fall back to non-bindless on Metal. + +Two correctness bugs were fixed in the process. +The sampler limit check was testing the wrong metric: `max_samplers_per_shader_stage` counts binding slots, but the relevant limit is `max_binding_array_sampler_elements_per_shader_stage`, the array element count — a mismatch that could silently exceed hardware limits. +Bevy now also skips creating binding array slots for resource types a material doesn't use, staying within Metal's hard 31 argument buffer slot limit and reducing overhead on all platforms. Benchmarked on Bistro Exterior (698 materials), 5-minute runs: -| GPU | Avg FPS improvement | Min FPS improvement | Memory | -| --- | --- | --- | --- | -| Apple M2 Max (Metal) | +18% | +77% | −57 MB RAM | -| NVIDIA 5060 Ti | +84% | +174% | Same | -| Intel i360P | +15% | Same | Same | -| AMD Vega 8 / Ryzen 4800U | Same | Same | −88 MB VRAM | -| Intel Iris XE | Same | Same | No regression | +| GPU | Avg FPS improvement | Min FPS improvement | Memory | +| ------------------------ | ------------------- | ------------------- | ----------- | +| Apple M2 Max (Metal) | +18% | +77% | −57 MB RAM | +| NVIDIA 5060 Ti | +84% | +174% | Same | +| Intel i360P | +15% | Same | Same | +| AMD Vega 8 / Ryzen 4800U | Same | Same | −88 MB VRAM | +| Intel Iris XE | Same | Same | Same | diff --git a/_release-content/release-notes/pccm.md b/_release-content/release-notes/pccm.md index 8701bb3809b30..b09a89466c124 100644 --- a/_release-content/release-notes/pccm.md +++ b/_release-content/release-notes/pccm.md @@ -4,10 +4,24 @@ authors: ["@pcwalton"] pull_requests: [22582] --- -Bevy previously didn't ever apply parallax correction to cubemaps, so reflections were rendered as though the environment were infinitely far away. This is often acceptable for outdoor scenes in which the environment is very distant, but for indoor scenes and dense environments this is undesirable. The standard solution for this problem is parallax correction, in which each reflection probe is augmented with a bounding box, and a raytrace is performed against the bounding box in order to determine the proper direction for sampling the cubemap. +*TODO: Add a before/after screenshot from the `pccm` example showing reflections with and without parallax correction.* -This commit implements parallax correction in Bevy for light probes in an opt-out manner. (You may add the `NoParallaxCorrection` component to a `LightProbe` with an `EnvironmentMapLight` in order to opt out of it.) The bounding box used for parallax correction is assumed to be identical to the bounding box of the influence of the reflection probe itself. This is a reasonable default and matches what Blender does; it's what you want when you have, for example, a cubemap that captures the interior of a rectangular room. +Bevy previously rendered cubemap reflections as though the environment were infinitely far away. +For outdoor scenes this was often fine, but for indoor scenes and dense environments the result looked wrong — +reflections didn't line up with the actual geometry around the viewer. -Additionally, a bug was fixed where the transform of each cubemap reflection probe wasn't being taken into account in the shader. I believe that this was being masked because most cubemaps are rendered in world space and therefore most cubemap reflection probes have an identity rotation. +The standard fix is parallax correction: each reflection probe gets its own bounding box, and a raytrace against that box determines the correct sampling direction for the cubemap. +Bevy now applies this automatically for light probes, using the probe's influence bounding box as the correction volume. +This is a reasonable default for a cubemap capturing a rectangular room interior, and matches Blender's approach. -A new example, `pccm`, has been added, demonstrating the effect of parallax correction. It shows a scene consisting of an outer textured cube with an inner reflective cuboid. The outer textured cube contains a reflection probe containing a snapshot of the scene (pre-rendered in Blender). Parallax correction can be toggled on and off in the example in order to demonstrate its effect. +Parallax correction is enabled by default. To opt out on a specific probe, add `NoParallaxCorrection`: + +```rust +commands.spawn(( + LightProbe, + EnvironmentMapLight { .. }, + NoParallaxCorrection, +)); +``` + +A new `pccm` example demonstrates the effect, with parallax correction toggleable at runtime. diff --git a/_release-content/release-notes/platform_dirs.md b/_release-content/release-notes/platform_dirs.md deleted file mode 100644 index 65096e76fbd42..0000000000000 --- a/_release-content/release-notes/platform_dirs.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -title: "bevy_platform standard directories" -authors: ["@viridia"] -pull_requests: [22891] ---- - -The `dirs` module in `bevy_platform` provides the location of standard directories in the -filesystem. Currently this includes the directory for storing application preferences. -Linux includes support for the XDG Base Directory specification. diff --git a/_release-content/release-notes/post-process_vignette_effect.md b/_release-content/release-notes/post-process_vignette_effect.md deleted file mode 100644 index 0e92dde0d6979..0000000000000 --- a/_release-content/release-notes/post-process_vignette_effect.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -title: "Post-process vignette effect" -authors: ["@Breakdown-Dog"] -pull_requests: [22564] ---- - -We’ve added vignette, which is a common post-processing effect that creates a reduction of image brightness towards the periphery compared to the image center. It’s often used to draw focus to the center of the screen or to simulate the look of a camera lens. - -To use it, add the `Vignette` component to your camera: - -```rust -commands.spawn(( - Camera3d::default(), - Vignette::default() -)) -``` diff --git a/_release-content/release-notes/post_processing_effects.md b/_release-content/release-notes/post_processing_effects.md new file mode 100644 index 0000000000000..c58e6aff7ebee --- /dev/null +++ b/_release-content/release-notes/post_processing_effects.md @@ -0,0 +1,56 @@ +--- +title: "More Post-Processing Effects" +authors: ["@Breakdown-Dog"] +pull_requests: [22564, 23110] +--- + +Two new post-processing effects were added in this cycle, both classic tools for giving your camera a more cinematic or stylized look. + +## Vignette + +*TODO: Add a before/after image showing the vignette effect.* + +Vignette reduces image brightness towards the periphery of the frame, drawing the viewer's eye to the center. +It's a classic tool for simulating the look of a camera lens or adding cinematic tension — but its real power in games is as a dynamic effect. +Think pulsing red on damage (a first-person shooter staple), a low uneven dim for horror dread, or a subtle ease-in on cutscene transitions. +The `intensity` of a vignette is a float value; you can change the vignettes effect by animating it. + +To use it, add the `Vignette` component to your camera: + +```rust +commands.spawn(( + Camera3d::default(), + Vignette { + intensity: 1.0, + radius: 0.75, + smoothness: 5.0, + roundness: 1.0, + center: Vec2::new(0.5, 0.5), + edge_compensation: 1.0, + color: Color::BLACK, + }, +)); +``` + +## Lens Distortion + +*TODO: Add a before/after image showing the barrel/pincushion warping effect.* + +Lens distortion warps the image spatially — positive `intensity` pushes the edges outward (barrel distortion), negative pulls them inward (pincushion distortion). +Racing games ramp up barrel distortion as speed increases, making the world feel like it's bending around the player; push it further and you get a fisheye look, useful for diegetic security cameras, wide-angle surveillance aesthetic or that classic GoPro bodycam look. +Negative values lend themselves to impairment states — drunk, poisoned, or concussed — where you want the world to feel compressed and wrong. + +To use it, add the `LensDistortion` component to your camera: + +```rust +commands.spawn(( + Camera3d::default(), + LensDistortion { + intensity: 0.5, + scale: 1.0, + multiplier: Vec2::ONE, + center: Vec2::splat(0.5), + edge_curvature: 0.0, + }, +)); +``` diff --git a/_release-content/release-notes/reflect_serde_handles.md b/_release-content/release-notes/reflect_serde_handles.md index 0d6005680a529..f4bdc93817573 100644 --- a/_release-content/release-notes/reflect_serde_handles.md +++ b/_release-content/release-notes/reflect_serde_handles.md @@ -1,33 +1,19 @@ --- -title: Serializing and deserializing asset handles for reflection. +title: Serializing and deserializing asset handles authors: ["@andriyDev"] pull_requests: [23329] --- -Asset handles are not just data: they are a reference to an asset that also keeps that asset alive. -This poses a challenge for deserializing handles: there's no asset to keep alive when deserializing! -In particular, our world serialization format (writable through `DynamicWorld::serialize`, previously called "scenes") has been unfortunately -restricted by the fact that handles could not be serialized or deserialized. A lot of things you -want to put into a world asset, like 3D models or even other scenes, need to reference asset handles for -their data. +Asset handles can now be round-tripped successfully during serialization and deserialization. +This is particularly important for world assets — the serialization format written through `DynamicWorld::serialize`, previously called scenes. -To resolve this, we've introduced `HandleSerializeProcessor` and `HandleDeserializeProcessor` to -be used with `TypedReflectSerializer::with_processor` and `TypedReflectDeserializer::with_processor` -respectively. These allow the reflection (de)serialization to store and load handles! Serializing a -handle will store its "identifying" information (e.g., asset path), and deserializing the handle -will load the asset path to produce the handle. - -In addition, this now happens automatically for world asset loading and saving! - -While it isn't practical for us to directly support `serde::Serialize` and `serde::Deserialize` -(since these don't allow passing the `AssetServer` needed to execute loads), reflection allows us to -bypass these concerns and provide a reasonable API, and we expect most users to be using reflection -when wanting to serialize/deserialize handles anyway. +This wasn't a matter of just slapping on some derives, because handles aren't raw data: they're a pointer to the actual loaded asset. +As a result, there was no clear way to either persist or reconstruct one. +The new `HandleSerializeProcessor` and `HandleDeserializeProcessor` solve this by storing a handle's identifying information (its asset path) on serialization, then reloading the asset from that path on deserialization. Pass them to `TypedReflectSerializer::with_processor` and `TypedReflectDeserializer::with_processor` if you need the same behavior in your own serialization pipelines. ## Caveat -The important point to make this work is making sure your assets are correctly reflected. For -example, if your asset looks like: +For this to work, your assets need to be correctly reflected. If your asset looks like: ```rust #[derive(Asset, TypePath)] @@ -36,7 +22,7 @@ struct MyAsset { } ``` -Change this to: +Change it to: ```rust #[derive(Asset, Reflect)] @@ -45,6 +31,3 @@ struct MyAsset { ... } ``` - -For generic assets, you will also need to explicitly register each variant using -`app.register_type::()` (just like any generic type). diff --git a/_release-content/release-notes/render-recovery.md b/_release-content/release-notes/render-recovery.md index 7383282aaa580..f3e2fec5c5ea3 100644 --- a/_release-content/release-notes/render-recovery.md +++ b/_release-content/release-notes/render-recovery.md @@ -1,24 +1,32 @@ --- title: "Render Recovery" -authors: ["@atlv24"] +authors: ["@atlv24", "@kfc35"] pull_requests: [22761, 23350, 23349, 23433, 23458, 23444, 23459, 23461, 23463, 22714, 22759, 16481, 24131] --- -You can now recover from rendering errors such as device loss by reloading the renderer: +GPU errors previously had no recovery path — a driver crash, an out-of-memory condition, or a device loss would silently hang or crash the app. +This was particularly frustrating in long-lived applications (like art installations) +or on devices with frequent failures, such as VR headsets. +Bevy now surfaces these as typed errors and lets you decide what to do with each one: -```rs +```rust use bevy::render::error_handler::{ErrorType, RenderErrorHandler, RenderErrorPolicy}; app.insert_resource(RenderErrorHandler( |error, main_world, render_world| match error.ty { - ErrorType::Internal => panic!(), + ErrorType::DeviceLost => RenderErrorPolicy::Recover(default()), ErrorType::OutOfMemory => RenderErrorPolicy::StopRendering, ErrorType::Validation => RenderErrorPolicy::Ignore, - ErrorType::DeviceLost => RenderErrorPolicy::Recover(default()), + ErrorType::Internal => panic!(), }, )); ``` -NOTE: this is just an example showing the different errors and policies available, and not a recommendation for how to handle errors. +`DeviceLost` is the case most games will want to handle: it covers GPU driver crashes, thermal shutdowns, and hardware being physically disconnected. +`RenderErrorPolicy::Recover` reinitializes the renderer and keeps the app running. +`StopRendering` halts rendering but leaves the rest of the app alive — useful if you want to show an error screen or save state before exiting. +`Ignore` silently swallows the error, which is the existing behavior for validation errors. Panicking remains appropriate for `Internal` errors, which indicate bugs. -The default error handler does not attempt recovery, currently. It behaves similarly to how Bevy behaved before, except the application will exit instead of panicking on any `RenderError`. +Be sure to test your error recovery carefully in your games; we've seen hardware-specific cases of flickering during repeated failures (as might be caused by an out-of-memory problem), which are a serious accessibility risk for people with photosensitive epilepsy. +While we're looking to solve that problem for good in later releases, we've currently opted for a conservative default. +If you don't configure a `RenderErrorHandler`, behavior is similar to but not identical to before: Vulkan validation errors are ignored, everything else sends an `AppExit` event to gracefully shut down. diff --git a/_release-content/release-notes/render_diagnostics_additions.md b/_release-content/release-notes/render_diagnostics_additions.md deleted file mode 100644 index a0b337904caba..0000000000000 --- a/_release-content/release-notes/render_diagnostics_additions.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -title: Render Diagnostic Additions -authors: ["@JMS55"] -pull_requests: [22326] ---- - -Bevy's [RenderDiagnosticPlugin](https://docs.rs/bevy/0.19.0/bevy/render/diagnostic/struct.RenderDiagnosticsPlugin.html) has new methods for uploading data from GPU buffers to bevy_diagnostic. - -```rust -impl ViewNode for Foo { - fn run( - &self, - _graph: &mut RenderGraphContext, - render_context: &mut RenderContext, - _: QueryItem, - world: &World, - ) -> Result<(), NodeRunError> { - let diagnostics = render_context.diagnostic_recorder(); - - diagnostics.record_u32( - render_context.command_encoder(), - &my_buffer1.slice(..), // Buffer slice must be 4 bytes, and buffer must have BufferUsages::COPY_SRC - "my_diagnostics/foo", - ); - - diagnostics.record_f32( - render_context.command_encoder(), - &my_buffer2.slice(..), // Buffer slice must be 4 bytes, and buffer must have BufferUsages::COPY_SRC - "my_diagnostics/bar", - ); - - Ok(()) - } -} -``` diff --git a/_release-content/release-notes/resources_as_components.md b/_release-content/release-notes/resources_as_components.md index e4652ce4d97b5..7022a39df8731 100644 --- a/_release-content/release-notes/resources_as_components.md +++ b/_release-content/release-notes/resources_as_components.md @@ -1,29 +1,29 @@ --- title: Resources as Components -authors: ["@Trashtalk", "@cart"] -pull_requests: [20934, 22910, 22911, 22919, 22930] +authors: ["@Trashtalk217", "@cart", "@SpecificProtagonist"] +pull_requests: [20934, 22910, 22911, 22919, 22930, 23616, 23716, 24077, 24164] --- -Resources are very similar to Components: they are both data that can be stored in the ECS and queried. -The only real difference between them is that querying a resource will return either one or zero resources, whereas querying for a component can return any number of matching entities. +Resources and components have always been separate concepts in Bevy's ECS, even though they're fundamentally the same thing: data stored in the world. While the simple `Res