Skip to content

Add release notes and a migration guide for RenderStartup. #20024

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions release-content/migration-guides/render_startup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
---
title: Many render resources now initialized in `RenderStartup`
pull_requests: [19841, 19926, 19885, 19886, 19897, 19898, 19901]
---

Many render resources are **no longer present** during `Plugin::finish`. Instead they are
initialized during `RenderStartup` (which occurs once the app starts running). If you only access
the resource during the `Render` schedule, then there should be no change. However, if you need one
of these render resources to initialize your own resource, you will need to convert your resource
initialization into a system.

The following are the (public) resources that are now initialized in `RenderStartup`.

- `CasPipeline`
- `FxaaPipeline`
- `SmaaPipelines`
- `TaaPipeline`
- `BoxShadowPipeline`
- `GradientPipeline`
- `UiPipeline`
- `UiMaterialPipeline<M>`
- `UiTextureSlicePipeline`

The vast majority of cases for initializing render resources look like so:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looked like this in Bevy 0.16?


```rust
impl Plugin for MyRenderingPlugin {
fn build(&self, app: &mut App) {
// Do nothing??
}

fn finish(&self, app: &mut App) {
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};

render_app.init_resource::<MyRenderResource>();
render_app.add_systems(Render, my_render_system);
}
}

pub struct MyRenderResource {
...
}

impl FromWorld for MyRenderResource {
fn from_world(world: &mut World) -> Self {
let render_device = world.resource::<RenderDevice>();
let render_adapter = world.resource::<RenderAdapter>();
let asset_server = world.resource::<AssetServer>();

MyRenderResource {
...
}
}
}
```

The two main things to focus on are:

1. The resource implements the `FromWorld` trait which collects all its dependent resources (most
commonly, `RenderDevice`), and then creates an instance of the resource.
2. The plugin adds its systems and resources in `Plugin::finish`.

First, we need to rewrite our `FromWorld` implementation as a system. This generally means
converting calls to `World::resource` into system params, and then using `Commands` to insert the
resource. In the above case, that would look like:

```rust
// Just a regular old system!!
fn init_my_resource(
mut commands: Commands,
render_device: Res<RenderDevice>,
render_adapter: Res<RenderAdapter>,
asset_server: Res<AssetServer>,
) {
commands.insert_resource(MyRenderResource {
...
});
}
```

Each case will be a slightly different. Two notes to be wary of:

1. Functions that accept `&RenderDevice` for example may no longer compile after switching to
`Res<RenderDevice>`. This can be resolved by passing `&render_device` instead of
`render_device`.
2. If you are using `load_embedded_asset(world, "my_asset.png")`, you may need to first add
`asset_server` as a system param, then change this to
`load_embedded_asset(asset_server.as_ref(), "my_asset.png")`.

Now that we have our initialization system, we just need to add the system to `RenderStartup`:

```rust
impl Plugin for MyRenderingPlugin {
fn build(&self, app: &mut App) {
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};

render_app
.add_systems(RenderStartup, init_my_resource)
.add_systems(Render, my_render_system);
}

// No more finish!!
}
```

In addition, if your resource requires one of the affected systems above, you will need to use
system ordering to ensure your resource initializes after the other system. For example, if your
system uses `Res<UiPipeline>`, you will need to add an ordering like:

```rust
render_app.add_systems(RenderStartup, init_my_resource.after(init_ui_pipeline));
```
103 changes: 103 additions & 0 deletions release-content/release-notes/render_startup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
---
title: `RenderStartup` and making the renderer my ECS-y
authors: ["@IceSentry", "@andriyDev"]
pull_requests: [19841, 19926, 19885, 19886, 19897, 19898, 19901]
---

Previous rendering code looked quite different from other Bevy code. In general, resources were
initialized with the `FromWorld` trait (where most Bevy code only uses the `Default` trait for most
resources) and systems/resources were added in `Plugin::finish` (where nearly all Bevy code does not
use `Plugin::finish` at all). This difference with Bevy code can make it harder for new developers
to learn rendering, and can result in "cargo cult" copying of rendering code (e.g., "is it important
to be using `FromWorld` here? Better to be safe and just do what the rendering code is doing!").

As a step towards making the renderer more accessible (and maintainable), we have introduced the
`RenderStartup` schedule and ported many rendering resources to be initialized in `RenderStartup`
with systems! This has several benefits:

1. Creating resources in systems makes it clearer that rendering resources **are just regular
resources**. Hopefully, this better communicates that how you initialize these resources is
totally up to you!
2. We can now use the system ordering API to ensure that resources are initialized in the correct
order. For example, we can do `init_material_pipeline.after(init_mesh_pipeline)` if we need the
mesh pipeline to initialize the material pipeline.
3. These initialization systems clearly describe what resources they require through their argument
list. If a system has an argument of `deferred_lighting_layout: Res<DeferredLightingLayout>`, it
clearly documents that this system needs to be run **after** we initialize the
`DeferredLightingLayout`.

We want developers to become more familiar and comfortable with Bevy's rendering stack, and hope
that bring the renderer closer to regular ECS code will encourage that. Code that previously looked
like this:

```rust
impl Plugin for MyRenderingPlugin {
fn build(&self, app: &mut App) {
// Do nothing??
}

fn finish(&self, app: &mut App) {
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};

render_app.init_resource::<MyRenderResource>();
}
}

pub struct MyRenderResource {
...
}

impl FromWorld for MyRenderResource {
fn from_world(world: &mut World) -> Self {
let render_device = world.resource::<RenderDevice>();
let render_adapter = world.resource::<RenderAdapter>();
let asset_server = world.resource::<AssetServer>();

MyRenderResource {
...
}
}
}
```

Can now be written like:

```rust
impl Plugin for MyRenderingPlugin {
fn build(&self, app: &mut App) {
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};

render_app.add_systems(RenderStartup, init_my_resource);
}

// No more finish!!
}

pub struct MyRenderResource {
...
}

// Just a regular old system!!
fn init_my_resource(
mut commands: Commands,
render_device: Res<RenderDevice>,
render_adapter: Res<RenderAdapter>,
asset_server: Res<AssetServer>,
) {
commands.insert_resource(MyRenderResource {
...
});
}
```

We highly encourage users to port their own rendering resources to this new system approach (and for
resources whose initialization depends on a Bevy core resource, it may be required). In fact, we
encourage users to abandon `Plugin::finish` entirely and move all their system and resource
initializations for rendering into `Plugin::build` instead.

As stated before, we've ported many resources to be initialized in `RenderStartup`. See the
migration guide for a full list of affected resources.