Migration Guide: 0.9 to 0.10

Bevy relies heavily on improvements in the Rust language and compiler. As a result, the Minimum Supported Rust Version (MSRV) is "the latest stable release" of Rust.

Migrate engine to Schedule v3 (stageless) #

Rendering
ECS
  • Calls to .label(MyLabel) should be replaced with .in_set(MySet)

  • SystemLabel derives should be replaced with SystemSet. You will also need to add the Debug, PartialEq, Eq, and Hash traits to satisfy the new trait bounds.

  • Stages have been removed. Replace these with system sets, and then add command flushes using the apply_system_buffers exclusive system where needed.

  • The CoreStage, StartupStage, RenderStage, and AssetStage enums have been replaced with CoreSet, StartupSet, RenderSet and AssetSet. The same scheduling guarantees have been preserved.

  • with_run_criteria has been renamed to run_if. Run criteria have been renamed to run conditions for clarity, and should now simply return a bool instead of schedule::ShouldRun.

  • Looping run criteria and state stacks have been removed. Use an exclusive system that runs a schedule if you need this level of control over system control flow.

  • App::add_state now takes 0 arguments: the starting state is set based on the Default impl.

  • Instead of creating SystemSet containers for systems that run in stages, use my_system.in_schedule(OnEnter(State::Variant)) or its OnExit sibling.

  • For app-level control flow over which schedules get run when (such as for rollback networking), create your own schedule and insert it under the CoreSchedule::Outer label.

  • Fixed timesteps are now evaluated in a schedule, rather than controlled via run criteria. The run_fixed_timestep system runs this schedule between CoreSet::First and CoreSet::PreUpdate by default.

  • Command flush points introduced by AssetStage have been removed. If you were relying on these, add them back manually.

  • The calculate_bounds system, with the CalculateBounds label, is now in CoreSet::Update, rather than in CoreSet::PostUpdate before commands are applied. You may need to order your movement systems to occur before this system in order to avoid system order ambiguities in culling behavior.

  • The RenderLabel AppLabel was renamed to RenderApp for clarity

  • When testing systems or otherwise running them in a headless fashion, simply construct and run a schedule using Schedule::new() and World::run_schedule rather than constructing stages

  • States have been dramatically simplified: there is no longer a “state stack”. To queue a transition to the next state, call NextState::set

  • Strings can no longer be used as a SystemLabel or SystemSet. Use a type, or use the system function instead.

Stages #

Stages had two key elements: they ran one after another, and they applied commands at their end.

The former can be replaced by system sets (unless you need branching or looping scheduling logic, in which case you should use a schedule), and the latter can be controlled manually via apply_system_buffers.

To migrate from Bevy's built-in stages, we've provided the CoreSet, StartupSet and RenderSet system sets. Command flushes have already been added to these, but if you have added custom stages you may need to add your own if you were relying on that behavior.

Before:

app
    .add_system_to_stage(CoreStage::PostUpdate, my_system)
    .add_startup_system_to_stage(StartupStage::PostStartup, my_startup_system);

After:

app
    .add_system(my_system.in_base_set(CoreSet::PostUpdate))
    .add_startup_system(my_startup_system.in_base_set(StartupSet::PostStartup));

If you had your own stage:

// Bevy 0.9
#[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)]
pub struct AfterUpdate;

app.add_stage_after(CoreStage::Update, AfterUpdate, SystemStage::parallel());

// Bevy 0.10, no command flush
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
#[system_set(base)]
pub struct AfterUpdate;

app.configure_set(
    AfterUpdate
        .after(CoreSet::UpdateFlush)
        .before(CoreSet::PostUpdate),
);

// Bevy 0.10, with a command flush
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
#[system_set(base)]
pub enum AfterUpdate {
    Parallel,
    CommandFlush
}

app.configure_sets(
    (
        CoreSet::UpdateFlush,
        AfterUpdate::Parallel,
        AfterUpdate::CommandFlush,
        CoreSet::PostUpdate,
    ).chain()
).add_system(apply_system_buffers.in_base_set(AfterUpdate::CommandFlush));

Label types #

System labels have been renamed to systems sets and unified with stage labels. The StageLabel trait should be replaced by a system set, using the SystemSet trait as discussed immediately below.

Before:

#[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)]
enum MyStage {
    BeforeRound,
    AfterRound,
}

#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)]
enum MySystem {
    ComputeForces,
    FindCollisions,
}

After:

#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
enum MySet {
    BeforeRound,
    AfterRound,
}

#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
enum MySystem {
    ComputeForces,
    FindCollisions,
}

System sets (Bevy 0.9) #

In Bevy 0.9, you could use the SystemSet type and various methods to configure many systems at once. Additionally, this was the only way to interact with various scheduling APIs like run criteria.

Before:

app.add_system_set(SystemSet::new().with_system(a).with_system(b).with_run_criteria(my_run_criteria));

After:

app.add_systems((a, b).run_if(my_run_condition));

Ambiguity detection #

The ReportExecutionOrderAmbiguities resource has been removed. Instead, this is configured on a per-schedule basis.

app.edit_schedule(CoreSchedule::Main, |schedule| {
    schedule.set_build_settings(ScheduleBuildSettings {
        ambiguity_detection: LogLevel::Warn,
        ..default()
    });
})

Fixed timesteps #

The FixedTimestep run criteria has been removed, and is now handled by either a schedule or the on_timer / on_fixed_timer run conditions.

Before:

app.add_stage_after(
    CoreStage::Update,
    FixedUpdateStage,
    SystemStage::parallel()
        .with_run_criteria(
            FixedTimestep::step(0.5)
        )
        .with_system(fixed_update),
);

After:

// This will affect the update frequency of fixed time for your entire app
app.insert_resource(FixedTime::new_from_secs(0.5))

    // This schedule is automatically added with DefaultPlugins
    .add_system(fixed_update.in_schedule(CoreSchedule::FixedUpdate));

Apps may now only have one unified fixed timestep. CoreSchedule::FixedTimestep is intended to be used for determinism and stability during networks, physics and game mechanics. Unlike timers, it will run repeatedly if more than a single period of time has elapsed since it was last run.

It is not intended to serve as a looping timer to regularly perform work or poll. If you were relying on multiple FixedTimestep run criteria with distinct periods, you should swap to using timers, via the on_timer(MY_PERIOD) or on_fixed_timer(MY_PERIOD) run conditions.

Before:

app.add_system_set(
    SystemSet::new()
        .with_run_criteria(FixedTimestep::step(0.5))
        .with_system(update_pathfinding),
)
.add_system_set(
    SystemSet::new()
        .with_run_criteria(FixedTimestep::step(0.1))
        .with_system(apply_damage_over_time),
);

After:

app
.add_system(update_pathfinding.run_if(on_timer(Duration::from_secs_f32(0.5))))
.add_system(apply_damage_over_time.run_if(on_timer(Duration::from_secs_f32(0.1))));

States #

States have been significantly simplied and no longer have a state stack. Each state type (usually an enum), requires the States trait, typically implemented via the derive macro.

For example:

#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Hash, States)]
enum AppState {
    #[default]
    Menu,
    InGame,
}

App::add_state no longer takes an argument: the starting state is now controlled via the Default impl for your state type.

To access the current state of the the States type above, use Res<State<AppState>, and access the tuple field via .0. To queue up a state transition, use ResMut<NextState<AppState>> and call .set(AppState::Menu).

State transitions are now applied via the apply_state_transitions exclusive system, a copy of which is added CoreSet::StateTransitions when you call App::add_state. You can add more copies as needed, specific to the state being applied.

OnEnter and OnExit systems now live in schedules, run on the World via the apply_state_transitions system. By contrast, OnUpdate is now a system set which is nested within CoreSet::Update.

Before:

#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
enum AppState {
    Menu,
    InGame,
}

app.add_state(AppState::Menu)
    .add_system_set(SystemSet::on_enter(AppState::Menu).with_system(setup_menu))
    .add_system_set(SystemSet::on_update(AppState::Menu).with_system(menu))
    .add_system_set(SystemSet::on_exit(AppState::Menu).with_system(cleanup_menu))

After:

#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Hash, States)]
enum AppState {
    #[default]
    Menu,
    InGame,
}

app.add_state::<AppState>()
    .add_system(setup_menu.in_schedule(OnEnter(AppState::Menu)))
    .add_system(menu.in_set(OnUpdate(AppState::Menu)))
    .add_system(cleanup_menu.in_schedule(OnExit(AppState::Menu)));

When you need to run your state-specific systems outside of CoreSet::Update, you can use the built-in in_state run condition.

Windows as Entities #

Windowing

Replace WindowDescriptor with Window.

Change width and height fields into a WindowResolution, either by doing

WindowResolution::new(width, height) // Explicitly
// or using From<_> for tuples for convenience
(1920., 1080.).into()

Replace any WindowCommand code to just modify the Window’s fields directly and creating/closing windows is now by spawning/despawning an entity with a Window component like so:

let window = commands.spawn(Window { ... }).id(); // open window
commands.entity(window).despawn(); // close window

To get a window, you now need to use a Query instead of a Res

// 0.9
fn count_pixels(windows: Res<Windows>) {
    let Some(primary) = windows.get_primary() else {
        return;
    };
    println!("{}", primary.width() * primary.height());
}

// 0.10
fn count_pixels(primary_query: Query<&Window, With<PrimaryWindow>>) {
    let Ok(primary) = primary_query.get_single() else {
        return;
    };
    println!("{}", primary.width() * primary.height());
}

Make the SystemParam derive macro more flexible #

ECS

The lifetime 's has been removed from EventWriter. Any code that explicitly specified the lifetimes for this type will need to be updated.

// 0.9
#[derive(SystemParam)]
struct MessageWriter<'w, 's> {
    events: EventWriter<'w, 's, Message>,
}

// 0.10
#[derive(SystemParam)]
struct MessageWriter<'w> {
    events: EventWriter<'w, Message>,
}

Basic adaptive batching for parallel query iteration #

ECS

The batch_size parameter for Query(State)::par_for_each(_mut) has been removed. These calls will automatically compute a batch size for you. Remove these parameters from all calls to these functions.

// 0.9
fn parallel_system(query: Query<&MyComponent>) {
   query.par_for_each(32, |comp| {
        ...
   });
}

// 0.10
fn parallel_system(query: Query<&MyComponent>) {
   query.par_iter().for_each(|comp| {
        ...
   });
}

Enum Visibility component #

Rendering
  • Evaluation of the visibility.is_visible field should now check for visibility == Visibility::Inherited.
  • Setting the visibility.is_visible field should now directly set the value: *visibility = Visibility::Inherited.
  • Usage of Visibility::VISIBLE or Visibility::INVISIBLE should now use Visibility::Inherited or Visibility::Hidden respectively.
  • ComputedVisibility::INVISIBLE and SpatialBundle::VISIBLE_IDENTITY have been renamed to ComputedVisibility::HIDDEN and SpatialBundle::INHERITED_IDENTITY respectively.

bevy_reflect: Pre-parsed paths #

Animation
Reflection

GetPath methods have been renamed according to the following:

  • path -> reflect_path
  • path_mut -> reflect_path_mut
  • get_path -> path
  • get_path_mut -> path_mut

Remove App::add_sub_app #

App

App::add_sub_app has been removed in favor of App::insert_sub_app. Use SubApp::new and insert it via App::insert_sub_app

// 0.9
let mut sub_app = App::new()
// Build subapp here
app.add_sub_app(MySubAppLabel, sub_app, extract_fn);

// 0.10
let mut sub_app = App::new()
// Build subapp here
app.insert_sub_app(MySubAppLabel, SubApp::new(sub_app, extract_fn));

Make HandleUntyped::id private #

Assets

Instead of directly accessing the ID of a HandleUntyped as handle.id, use the new getter handle.id().

Break CorePlugin into TaskPoolPlugin, TypeRegistrationPlugin, FrameCountPlugin. #

Core

CorePlugin was broken into separate plugins. If not using DefaultPlugins or MinimalPlugins PluginGroups, the replacement for CorePlugin is now to add TaskPoolPlugin, TypeRegistrationPlugin, and FrameCountPlugin to the app.

Immutable sparse sets for metadata storage #

ECS

Table::component_capacity() has been removed as Tables do not support adding/removing columns after construction.

Split Component Ticks #

ECS

Various low level APIs interacting with the change detection ticks no longer return &UnsafeCell<ComponentTicks>, instead returning TickCells which contains two separate &UnsafeCell<Tick>s instead.

// 0.9
column.get_ticks(row).deref().changed

// 0.10
column.get_ticks(row).changed.deref()

Document and lock down types in bevy_ecs::archetype #

ECS

ArchetypeId, ArchetypeGeneration, and ArchetypeComponentId are all now opaque IDs and cannot be turned into a numeric value. Please file an issue if this does not work for your use case or check bevy_ecs is excessively public for more info.

Archetype and Archetypes are not constructible outside of bevy_ecs now. Use World::archetypes to get a read-only reference to either of these types.

Lock down access to Entities #

ECS

Entities’s Default implementation has been removed. You can fetch a reference to a World’s Entities via World::entities and World::entities_mut.

Entities::alloc_at_without_replacement and AllocAtWithoutReplacement has been made private due to difficulty in using it properly outside of bevy_ecs. If you still need use of this API, please file an issue or check bevy_ecs is excessively public for more info.

Borrow instead of consuming in EventReader::clear #

ECS

EventReader::clear now takes a mutable reference instead of consuming the event reader. This means that clear now needs explicit mutable access to the reader variable, which previously could have been omitted in some cases:

// Old (0.9)
fn clear_events(reader: EventReader<SomeEvent>) {
  reader.clear();
}

// New (0.10)
fn clear_events(mut reader: EventReader<SomeEvent>) {
  reader.clear();
}

Newtype ArchetypeRow and TableRow #

ECS

Archetype indices and Table rows have been newtyped as ArchetypeRow and TableRow.

Round out the untyped APIs #

ECS

MutUntyped::into_inner now marks things as changed.

Extend EntityLocation with TableId and TableRow #

ECS

A World can only hold a maximum of 232 - 1 archetypes and tables now. If your use case requires more than this, please file an issue explaining your use case.

Remove ExclusiveSystemParam::apply #

ECS

The trait method ExclusiveSystemParamState::apply has been removed. If you have an exclusive system with buffers that must be applied, you should apply them within the body of the exclusive system.

Remove the SystemParamState trait and remove types like ResState #

ECS

The traits SystemParamState and SystemParamFetch have been removed, and their functionality has been transferred to SystemParam.

The trait ReadOnlySystemParamFetch has been replaced with ReadOnlySystemParam.

// 0.9
impl SystemParam for MyParam<'_, '_> {
    type State = MyParamState;
}
unsafe impl SystemParamState for MyParamState {
    fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { ... }
}
unsafe impl<'w, 's> SystemParamFetch<'w, 's> for MyParamState {
    type Item = MyParam<'w, 's>;
    fn get_param(&mut self, ...) -> Self::Item;
}
unsafe impl ReadOnlySystemParamFetch for MyParamState { }

// 0.10
unsafe impl SystemParam for MyParam<'_, '_> {
    type State = MyParamState;
    type Item<'w, 's> = MyParam<'w, 's>;
    fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { ... }
    fn get_param<'w, 's>(state: &mut Self::State, ...) -> Self::Item<'w, 's>;
}
unsafe impl ReadOnlySystemParam for MyParam<'_, '_> { }

Panic on dropping NonSend in non-origin thread. #

ECS

Normal resources and NonSend resources no longer share the same backing storage. If R: Resource, then NonSend<R> and Res<R> will return different instances from each other. If you are using both Res<T> and NonSend<T> (or their mutable variants), to fetch the same resources, it’s strongly advised to use Res<T>.

Document alignment requirements of Ptr, PtrMut and OwningPtr #

ECS

Safety invariants on bevy_ptr types’ new byte_add and byte_offset methods have been changed. All callers should re-audit for soundness.

Added resource_id and changed init_resource and init_non_send_resource to return ComponentId #

ECS
  • Added Components::resource_id.
  • Changed World::init_resource to return the generated ComponentId.
  • Changed World::init_non_send_resource to return the generated ComponentId.

Replace RemovedComponents<T> backing with Events<Entity> #

ECS
  • Add a mut for removed: RemovedComponents<T> since we are now modifying an event reader internally.
  • Iterating over removed components now requires &mut removed_components or removed_components.iter() instead of &removed_components.

Remove broken DoubleEndedIterator impls on event iterators #

ECS

ManualEventIterator and ManualEventIteratorWithId are no longer DoubleEndedIterators since the impls didn't work correctly, and any code using this was likely broken.

Rename Tick::is_older_than to Tick::is_newer_than #

ECS

Replace usages of Tick::is_older_than with Tick::is_newer_than.

Cleanup system sets called labels #

ECS

PrepareAssetLabel is now called PrepareAssetSet

Simplify generics for the SystemParamFunction trait #

ECS

For the SystemParamFunction trait, the type parameters In, Out, and Param have been turned into associated types.

// 0.9
fn my_generic_system<T, In, Out, Param, Marker>(system_function: T)
where
    T: SystemParamFunction<In, Out, Param, Marker>,
    Param: SystemParam,
{ ... }

// 0.10
fn my_generic_system<T, Marker>(system_function: T)
where
    T: SystemParamFunction<Marker>,
{ ... }

For the ExclusiveSystemParamFunction trait, the type parameter Param has been turned into an associated type. Also, In and Out associated types have been added, since exclusive systems now support system piping.

// 0.9
fn my_exclusive_system<T, Param, Marker>(system_function: T)
where
    T: ExclusiveSystemParamFunction<Param, Marker>,
    T: Param: ExclusiveSystemParam,
{ ... }

// 0.10
fn my_exclusive_system<T, Marker>(system_function: T)
where
    T: ExclusiveSystemParamFunction<Marker>,
{ ... }

Deprecate ChangeTrackers<T> in favor of Ref<T> #

ECS

ChangeTrackers<T> has been deprecated, and will be removed in the next release. Any usage should be replaced with Ref<T>.

// 0.9
fn my_system(q: Query<(&MyComponent, ChangeTrackers<MyComponent>)>) {
    for (value, trackers) in &q {
        if trackers.is_changed() {
            // Do something with `value`.
        }
    }
}

// 0.10
fn my_system(q: Query<Ref<MyComponent>>) {
    for value in &q {
        if value.is_changed() {
            // Do something with `value`.
        }
    }
}

EntityMut: rename remove_intersection to remove and remove to take #

ECS
// 0.9
fn clear_children(parent: Entity, world: &mut World) {
    if let Some(children) = world.entity_mut(parent).remove::<Children>() {
        for &child in &children.0 {
            world.entity_mut(child).remove_intersection::<Parent>();
        }
    }
}

// 0.10
fn clear_children(parent: Entity, world: &mut World) {
    if let Some(children) = world.entity_mut(parent).take::<Children>() {
        for &child in &children.0 {
            world.entity_mut(child).remove::<Parent>();
        }
    }
}

bevy_ecs: ReflectComponentFns without World #

ECS
Reflection

Call World::entity before calling into the changed ReflectComponent methods, most likely user already has a EntityRef or EntityMut which was being queried redundantly.

Allow iterating over with EntityRef over the entire World #

ECS
Scenes

World::iter_entities now returns an iterator of EntityRef instead of Entity. To get the actual ID, use EntityRef::id from the returned EntityRefs.

Remove BuildWorldChildren impl from WorldChildBuilder #

Hierarchy

Hierarchy editing methods such as with_children and push_children have been removed from WorldChildBuilder. You can edit the hierarchy via EntityMut instead.

Rename dynamic feature #

Meta

dynamic feature was renamed to dynamic_linking

bevy_reflect: add insert and remove methods to List #

Reflection

Manual implementors of List need to implement the new methods insert and remove and consider whether to use the new default implementation of push and pop.

bevy_reflect: Decouple List and Array traits #

Reflection

The List trait is no longer dependent on Array. Implementors of List can remove the Array impl and move its methods into the List impl (with only a couple tweaks).

// 0.9
impl Array for Foo {
  fn get(&self, index: usize) -> Option<&dyn Reflect> {/* ... */}
  fn get_mut(&mut self, index: usize) -> Option<&mut dyn Reflect> {/* ... */}
  fn len(&self) -> usize {/* ... */}
  fn is_empty(&self) -> bool {/* ... */}
  fn iter(&self) -> ArrayIter {/* ... */}
  fn drain(self: Box<Self>) -> Vec<Box<dyn Reflect>> {/* ... */}
  fn clone_dynamic(&self) -> DynamicArray {/* ... */}
}

impl List for Foo {
  fn insert(&mut self, index: usize, element: Box<dyn Reflect>) {/* ... */}
  fn remove(&mut self, index: usize) -> Box<dyn Reflect> {/* ... */}
  fn push(&mut self, value: Box<dyn Reflect>) {/* ... */}
  fn pop(&mut self) -> Option<Box<dyn Reflect>> {/* ... */}
  fn clone_dynamic(&self) -> DynamicList {/* ... */}
}

// 0.10
impl List for Foo {
  fn get(&self, index: usize) -> Option<&dyn Reflect> {/* ... */}
  fn get_mut(&mut self, index: usize) -> Option<&mut dyn Reflect> {/* ... */}
  fn insert(&mut self, index: usize, element: Box<dyn Reflect>) {/* ... */}
  fn remove(&mut self, index: usize) -> Box<dyn Reflect> {/* ... */}
  fn push(&mut self, value: Box<dyn Reflect>) {/* ... */}
  fn pop(&mut self) -> Option<Box<dyn Reflect>> {/* ... */}
  fn len(&self) -> usize {/* ... */}
  fn is_empty(&self) -> bool {/* ... */}
  fn iter(&self) -> ListIter {/* ... */}
  fn drain(self: Box<Self>) -> Vec<Box<dyn Reflect>> {/* ... */}
  fn clone_dynamic(&self) -> DynamicList {/* ... */}
}

Some other small tweaks that will need to be made include:

  • Use ListIter for List::iter instead of ArrayIter (the return type from Array::iter)
  • Replace array_hash with list_hash in Reflect::reflect_hash for implementors of List

bevy_reflect: Remove ReflectSerialize and ReflectDeserialize registrations from most glam types #

Reflection
Scenes

This PR removes ReflectSerialize and ReflectDeserialize registrations from most glam types. This means any code relying on either of those type data existing for those glam types will need to not do that.

This also means that some serialized glam types will need to be updated. For example, here is Affine3A:

// 0.9
(
  "glam::f32::affine3a::Affine3A": (1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0),

// 0.10
  "glam::f32::affine3a::Affine3A": (
    matrix3: (
      x_axis: (
        x: 1.0,
        y: 0.0,
        z: 0.0,
      ),
      y_axis: (
        x: 0.0,
        y: 1.0,
        z: 0.0,
      ),
      z_axis: (
        x: 0.0,
        y: 0.0,
        z: 1.0,
      ),
    ),
    translation: (
      x: 0.0,
      y: 0.0,
      z: 0.0,
    ),
  )
)

Add AutoMax next to ScalingMode::AutoMin #

Rendering

Rename ScalingMode::Auto to ScalingMode::AutoMin.

Change From<Icosphere> to TryFrom<Icosphere> #

Rendering
// 0.9
shape::Icosphere {
    radius: 0.5,
    subdivisions: 5,
}
.into()

// 0.10
shape::Icosphere {
    radius: 0.5,
    subdivisions: 5,
}
.try_into()
.unwrap()

Add try_* to add_slot_edge, add_node_edge #

Rendering

Remove .unwrap() from add_node_edge and add_slot_edge. For cases where the error was handled, use try_add_node_edge and try_add_slot_edge instead.

Remove .unwrap() from input_node. For cases where the option was handled, use get_input_node instead.

Shader defs can now have a value #

Rendering
  • Replace shader_defs.push(String::from("NAME")); by shader_defs.push("NAME".into());
  • If you used shader def NO_STORAGE_BUFFERS_SUPPORT, check how AVAILABLE_STORAGE_BUFFER_BINDINGS is now used in Bevy default shaders

Get pixel size from wgpu #

Rendering

PixelInfo has been removed. PixelInfo::components is equivalent to texture_format.describe().components. PixelInfo::type_size can be gotten from texture_format.describe().block_size/ texture_format.describe().components. But note this can yield incorrect results for some texture types like Rg11b10Float.

Run clear trackers on render world #

Rendering

The call to clear_trackers in App has been moved from the schedule to App::update for the main world and calls to clear_trackers have been added for sub_apps in the same function. This was due to needing stronger guarantees. If clear_trackers isn’t called on a world it can lead to memory leaks in RemovedComponents. If you were ordering systems with clear_trackers this is no longer possible.

Rename camera "priority" to "order" #

Rendering

Rename priority to order in usage of Camera.

Reduce branching in TrackedRenderPass #

Rendering

TrackedRenderPass now requires a RenderDevice to construct. To make this easier, use RenderContext.begin_tracked_render_pass instead.

// 0.9
TrackedRenderPass::new(render_context.command_encoder.begin_render_pass(
  &RenderPassDescriptor {
    ...
  },
));

// 0.10
render_context.begin_tracked_render_pass(RenderPassDescriptor {
  ...
});

Make PipelineCache internally mutable. #

Rendering

Most usages of resource_mut::<PipelineCache> and ResMut<PipelineCache> can be changed to resource::<PipelineCache> and Res<PipelineCache> as long as they don’t use any methods requiring mutability - the only public method requiring it is process_queue.

Changed Msaa to Enum #

Rendering
// 0.9
let multi = Msaa { samples: 4 }
// 0.10
let multi = Msaa::Sample4

// 0.9
multi.samples
// 0.10
multi.samples()

Support recording multiple CommandBuffers in RenderContext #

Rendering

RenderContext’s fields are now private. Use the accessors on RenderContext instead, and construct it with RenderContext::new.

Improve OrthographicCamera consistency and usability #

Rendering
  • Change window_origin to viewport_origin; replace WindowOrigin::Center with Vec2::new(0.5, 0.5) and WindowOrigin::BottomLeft with Vec2::new(0.0, 0.0)

  • For shadow projections and such, replace left, right, bottom, and top with area: Rect::new(left, bottom, right, top)

  • For camera projections, remove l/r/b/t values from OrthographicProjection instantiations, as they no longer have any effect in any ScalingMode

  • Change ScalingMode::None to ScalingMode::Fixed

    • Replace manual changes of l/r/b/t with:
      • Arguments in ScalingMode::Fixed to specify size
      • viewport_origin to specify offset
  • Change ScalingMode::WindowSize to ScalingMode::WindowSize(1.0)

Changed &mut PipelineCache to &PipelineCache #

Rendering

SpecializedComputePipelines::specialize now takes a &PipelineCache instead of a &mut PipelineCache

Introduce detailed_trace macro, use in TrackedRenderPass #

Rendering

Some detailed bevy trace events now require the use of the cargo feature detailed_trace in addition to enabling TRACE level logging to view. Should you wish to see these logs, please compile your code with the bevy feature detailed_trace. Currently, the only logs that are affected are the renderer logs pertaining to TrackedRenderPass functions

Added subdivisions to shape::Plane #

Rendering

shape::Plane now takes an additional subdivisions parameter so users should provide it or use the new shape::Plane::from_size().

Change standard material defaults and update docs #

Rendering

StandardMaterial’s default have now changed to be a fully dielectric material with medium roughness. If you want to use the old defaults, you can set perceptual_roughness = 0.089 and metallic = 0.01 (though metallic should generally only be set to 0.0 or 1.0).

Remove dead code after #7784 #

Rendering

Removed SetShadowViewBindGroup, queue_shadow_view_bind_group(), and LightMeta::shadow_view_bind_group in favor of reusing the prepass view bind group.

Directly extract joints into SkinnedMeshJoints #

Rendering
Animation

ExtractedJoints has been removed. Read the bound bones from SkinnedMeshJoints instead.

Intepret glTF colors as linear instead of sRGB #

Rendering
Assets

No API changes are required, but it's possible that your gltf meshes look different

Send emissive color to uniform as linear instead of sRGB #

  • If you have previously manually specified emissive values with Color::rgb() and would like to retain the old visual results, you must now use Color::rgb_linear() instead;
  • If you have previously manually specified emissive values with Color::rgb_linear() and would like to retain the old visual results, you'll need to apply a one-time gamma calculation to your channels manually to get the actual linear RGB value:
    • For channel values greater than 0.0031308, use (1.055 * value.powf(1.0 / 2.4)) - 0.055;
    • For channel values lower than or equal to 0.0031308, use value * 12.92;
  • Otherwise, the results should now be more consistent with other tools/engines.

The update_frame_count system should be placed in CorePlugin #

Rendering
Core
Time

The FrameCount resource was previously only updated when using the bevy_render feature. If you are not using this feature but still want the FrameCount it will now be updated correctly.

Pipelined Rendering #

Rendering
Tasks

App runner and SubApp extract functions are now required to be Send

This was changed to enable pipelined rendering. If this breaks your use case please report it as these new bounds might be able to be relaxed.

Remove ImageMode #

Rendering
UI

ImageMode never worked, if you were using it please create an issue.

Rename the background_color of 'ExtractedUiNodetocolor` #

Rendering
UI

The background_color field of ExtractedUiNode is now named color.

Remove the GlobalTransform::translation_mut method #

Transform
Hierarchy

GlobalTransform::translation_mut has been removed without alternative, if you were relying on this, update the Transform instead. If the given entity had children or parent, you may need to remove its parent to make its transform independent (in which case the new Commands::set_parent_in_place and Commands::remove_parent_in_place may be of interest)

Bevy may add in the future a way to toggle transform propagation on an entity basis.

Flip UI image #

UI
  • UiImage is a struct now, so use UiImage::new(handler) instead of UiImage(handler)
  • UiImage no longer implements Deref and DerefMut, so use &image.texture or &mut image.texture instead

Remove TextError::ExceedMaxTextAtlases(usize) variant #

UI

TextError::ExceedMaxTextAtlases(usize) was never thrown so if you were matching on this variant you can simply remove it.

Change default FocusPolicy to Pass #

UI

FocusPolicy default has changed from FocusPolicy::Block to FocusPolicy::Pass

Remove VerticalAlign from TextAlignment #

UI

The alignment field of Text now only affects the text’s internal alignment.

Change TextAlignment to TextAlignment` which is now an enum. Replace:

  • TextAlignment::TOP_LEFT, TextAlignment::CENTER_LEFT, TextAlignment::BOTTOM_LEFT with TextAlignment::Left
  • TextAlignment::TOP_CENTER, TextAlignment::CENTER_LEFT, TextAlignment::BOTTOM_CENTER with TextAlignment::Center
  • TextAlignment::TOP_RIGHT, TextAlignment::CENTER_RIGHT, TextAlignment::BOTTOM_RIGHT with TextAlignment::Right

Changes for Text2dBundle

Text2dBundle has a new field text_anchor that takes an Anchor component that controls its position relative to its transform.

Text2dSize was removed. Use TextLayoutInfo instead.

Remove QueuedText #

UI

QueuedText was never meant to be user facing. If you relied on it, please make an issue.

Change the default width and height of Size to Val::Auto #

UI

The default values for Size width and height have been changed from Val::Undefined to Val::Auto. It’s unlikely to cause any issues with existing code.

Fix the Size helper functions using the wrong default value and improve the UI examples #

UI

The Size::width constructor function now sets the height to Val::Auto instead of Val::Undefined. The Size::height constructor function now sets the width to Val::Auto instead of Val::Undefined.

The size field of CalculatedSize should not be a Size #

UI

The size field of CalculatedSize has been changed to a Vec2.

Update winit to 0.28 #

Windowing
// 0.9
app.new()
    .add_plugins(DefaultPlugins.set(WindowPlugin {
        primary_window: Some(Window {
            always_on_top: true,
            ..default()
        }),
        ..default()
    }));

// 0.10
app.new()
    .add_plugins(DefaultPlugins.set(WindowPlugin {
        primary_window: Some(Window {
            window_level: bevy::window::WindowLevel::AlwaysOnTop,
            ..default()
        }),
        ..default()
    }));