diff --git a/release-content/migration-guides/render_startup.md b/release-content/migration-guides/render_startup.md new file mode 100644 index 0000000000000..ae358c37727fc --- /dev/null +++ b/release-content/migration-guides/render_startup.md @@ -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` +- `UiTextureSlicePipeline` + +The vast majority of cases for initializing render resources look like so (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::(); + 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::(); + let render_adapter = world.resource::(); + let asset_server = world.resource::(); + + 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, + render_adapter: Res, + asset_server: Res, +) { + 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`. 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`, you will need to add an ordering like: + +```rust +render_app.add_systems(RenderStartup, init_my_resource.after(init_ui_pipeline)); +``` diff --git a/release-content/release-notes/render_startup.md b/release-content/release-notes/render_startup.md new file mode 100644 index 0000000000000..10347b503f5b5 --- /dev/null +++ b/release-content/release-notes/render_startup.md @@ -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`, 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 (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::(); + } +} + +pub struct MyRenderResource { + ... +} + +impl FromWorld for MyRenderResource { + fn from_world(world: &mut World) -> Self { + let render_device = world.resource::(); + let render_adapter = world.resource::(); + let asset_server = world.resource::(); + + 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, + render_adapter: Res, + asset_server: Res, +) { + 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.