From ff04f98d19fbed0e4af8972c973939b3566dbbe9 Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Wed, 25 Mar 2026 21:34:09 +0800 Subject: [PATCH 1/5] Add PreRenderStartup, PreRender and PostRender schedules --- crates/bevy_render/src/error_handler.rs | 11 ++++++++++- crates/bevy_render/src/lib.rs | 19 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/crates/bevy_render/src/error_handler.rs b/crates/bevy_render/src/error_handler.rs index 41fb3e4a2cd4d..61479a06a0eaa 100644 --- a/crates/bevy_render/src/error_handler.rs +++ b/crates/bevy_render/src/error_handler.rs @@ -12,7 +12,7 @@ use crate::{ render_resource::PipelineCache, renderer::{RenderDevice, WgpuWrapper}, settings::RenderCreation, - FutureRenderResources, RenderStartup, + FutureRenderResources, PreRenderStartup, RenderStartup, }; /// Resource to indicate renderer behavior upon error. @@ -66,6 +66,10 @@ impl Default for RenderErrorHandler { } } +/// Exists in the render world if [`PreRenderStartup`] has run. +#[derive(Resource, Debug)] +pub(crate) struct FirstRenderRun; + /// An error encountered during rendering. #[derive(Debug)] pub struct RenderError { @@ -165,6 +169,11 @@ impl DeviceErrorHandler { /// /// We need both the main and render world to properly handle errors, so we wedge ourselves into [extract](bevy_app::SubApp::set_extract). pub(crate) fn update_state(main_world: &mut World, render_world: &mut World) { + if render_world.get_resource::().is_none() { + render_world.run_schedule(PreRenderStartup); + render_world.insert_resource(FirstRenderRun); + } + if let Some(error) = render_world.resource::().poll() { render_world.insert_resource(RenderState::Errored(error)); }; diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 770ad69533ec4..3cc1d841e2b6f 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -201,6 +201,11 @@ pub enum RenderSystems { PostCleanup, } +/// The prestartup schedule of the [`RenderApp`]. +/// This runs only once, and runs before [`RenderStartup`] +#[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone, Default)] +pub struct PreRenderStartup; + /// The startup schedule of the [`RenderApp`]. /// This can potentially run multiple times, and not on a fresh render world. /// Every time a new [`RenderDevice`](renderer::RenderDevice) is acquired, @@ -238,6 +243,14 @@ impl GpuResourceAppExt for SubApp { #[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone)] struct RenderRecovery; +/// Runs immediately before the main render schedule. +#[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone, Default)] +pub struct PreRender; + +/// Runs immediately after the main render schedule. +#[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone, Default)] +pub struct PostRender; + /// The main render schedule. #[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone, Default)] pub struct Render; @@ -354,7 +367,11 @@ impl Plugin for RenderPlugin { ), ); + render_app.init_schedule(PreRender); render_app.add_schedule(RenderGraph::base_schedule()); + render_app.init_schedule(PostRender); + + render_app.init_schedule(PreRenderStartup); render_app.init_schedule(RenderStartup); render_app @@ -415,7 +432,9 @@ fn renderer_is_ready(state: Res) -> bool { } fn run_render_schedule(world: &mut World) { + world.run_schedule(PreRender); world.run_schedule(Render); + world.run_schedule(PostRender); } fn send_time(time_sender: Res) { From fdf7e9d52bcd3326121b1b4e77483ed0b26170c9 Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Wed, 25 Mar 2026 22:25:44 +0800 Subject: [PATCH 2/5] Add RenderMainScheduleOrder resource to manage render schedules --- crates/bevy_render/src/lib.rs | 54 +++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 3cc1d841e2b6f..f00f0ca23037f 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -99,7 +99,10 @@ use batching::gpu_preprocessing::BatchingPlugin; use bevy_app::{App, AppLabel, Plugin, SubApp}; use bevy_asset::{AssetApp, AssetServer}; use bevy_derive::Deref; -use bevy_ecs::{prelude::*, schedule::ScheduleLabel}; +use bevy_ecs::{ + prelude::*, + schedule::{InternedScheduleLabel, ScheduleLabel}, +}; use bevy_platform::time::Instant; use bevy_shader::{load_shader_library, Shader, ShaderLoader}; use bevy_time::TimeSender; @@ -238,11 +241,48 @@ impl GpuResourceAppExt for SubApp { } } -/// The render recovery schedule. This schedule runs the [`Render`] schedule if +/// The render recovery schedule. This schedule runs the [`RenderMainScheduleOrder`] schedules if /// we are in [`RenderState::Ready`], and is otherwise hidden from users. #[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone)] struct RenderRecovery; +/// Defines the schedules to be run for the main rendering, including their order. +#[derive(Resource, Debug)] +pub struct RenderMainScheduleOrder { + /// The labels to run for the main rendering schedule (in the order they will be run). + pub labels: Vec, +} + +impl Default for RenderMainScheduleOrder { + fn default() -> Self { + Self { + labels: vec![PreRender.intern(), Render.intern(), PostRender.intern()], + } + } +} + +impl RenderMainScheduleOrder { + /// Adds the given `schedule` after the `after` schedule + pub fn insert_after(&mut self, after: impl ScheduleLabel, schedule: impl ScheduleLabel) { + let index = self + .labels + .iter() + .position(|current| (**current).eq(&after)) + .unwrap_or_else(|| panic!("Expected {after:?} to exist")); + self.labels.insert(index + 1, schedule.intern()); + } + + /// Adds the given `schedule` before the `before` schedule + pub fn insert_before(&mut self, before: impl ScheduleLabel, schedule: impl ScheduleLabel) { + let index = self + .labels + .iter() + .position(|current| (**current).eq(&before)) + .unwrap_or_else(|| panic!("Expected {before:?} to exist")); + self.labels.insert(index, schedule.intern()); + } +} + /// Runs immediately before the main render schedule. #[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone, Default)] pub struct PreRender; @@ -367,6 +407,8 @@ impl Plugin for RenderPlugin { ), ); + render_app.init_resource::(); + render_app.init_schedule(PreRender); render_app.add_schedule(RenderGraph::base_schedule()); render_app.init_schedule(PostRender); @@ -432,9 +474,11 @@ fn renderer_is_ready(state: Res) -> bool { } fn run_render_schedule(world: &mut World) { - world.run_schedule(PreRender); - world.run_schedule(Render); - world.run_schedule(PostRender); + world.resource_scope(|world, order: Mut| { + for &label in &order.labels { + let _ = world.try_run_schedule(label); + } + }); } fn send_time(time_sender: Res) { From 9719238d84ce6e68b6d38c9a322bb182f726b4d5 Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Thu, 26 Mar 2026 06:19:36 +0800 Subject: [PATCH 3/5] Rm PreRenderStartup --- crates/bevy_render/src/error_handler.rs | 11 +---------- crates/bevy_render/src/lib.rs | 7 ------- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/crates/bevy_render/src/error_handler.rs b/crates/bevy_render/src/error_handler.rs index 61479a06a0eaa..41fb3e4a2cd4d 100644 --- a/crates/bevy_render/src/error_handler.rs +++ b/crates/bevy_render/src/error_handler.rs @@ -12,7 +12,7 @@ use crate::{ render_resource::PipelineCache, renderer::{RenderDevice, WgpuWrapper}, settings::RenderCreation, - FutureRenderResources, PreRenderStartup, RenderStartup, + FutureRenderResources, RenderStartup, }; /// Resource to indicate renderer behavior upon error. @@ -66,10 +66,6 @@ impl Default for RenderErrorHandler { } } -/// Exists in the render world if [`PreRenderStartup`] has run. -#[derive(Resource, Debug)] -pub(crate) struct FirstRenderRun; - /// An error encountered during rendering. #[derive(Debug)] pub struct RenderError { @@ -169,11 +165,6 @@ impl DeviceErrorHandler { /// /// We need both the main and render world to properly handle errors, so we wedge ourselves into [extract](bevy_app::SubApp::set_extract). pub(crate) fn update_state(main_world: &mut World, render_world: &mut World) { - if render_world.get_resource::().is_none() { - render_world.run_schedule(PreRenderStartup); - render_world.insert_resource(FirstRenderRun); - } - if let Some(error) = render_world.resource::().poll() { render_world.insert_resource(RenderState::Errored(error)); }; diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index f00f0ca23037f..b80c88f16743b 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -204,11 +204,6 @@ pub enum RenderSystems { PostCleanup, } -/// The prestartup schedule of the [`RenderApp`]. -/// This runs only once, and runs before [`RenderStartup`] -#[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone, Default)] -pub struct PreRenderStartup; - /// The startup schedule of the [`RenderApp`]. /// This can potentially run multiple times, and not on a fresh render world. /// Every time a new [`RenderDevice`](renderer::RenderDevice) is acquired, @@ -413,8 +408,6 @@ impl Plugin for RenderPlugin { render_app.add_schedule(RenderGraph::base_schedule()); render_app.init_schedule(PostRender); - render_app.init_schedule(PreRenderStartup); - render_app.init_schedule(RenderStartup); render_app .get_schedule_mut(RenderStartup) From f8d6bd9838d4a3e06a17d77bafae47efe444cebf Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Fri, 27 Mar 2026 10:23:44 +0800 Subject: [PATCH 4/5] Remove PreRender and PostRender --- crates/bevy_render/src/lib.rs | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index b80c88f16743b..d13cd89cc8d8a 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -241,22 +241,22 @@ impl GpuResourceAppExt for SubApp { #[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone)] struct RenderRecovery; -/// Defines the schedules to be run for the main rendering, including their order. +/// Defines the schedules to be run for the rendering, including their order. #[derive(Resource, Debug)] -pub struct RenderMainScheduleOrder { - /// The labels to run for the main rendering schedule (in the order they will be run). +pub struct RenderScheduleOrder { + /// The labels to run for the rendering schedule (in the order they will be run). pub labels: Vec, } -impl Default for RenderMainScheduleOrder { +impl Default for RenderScheduleOrder { fn default() -> Self { Self { - labels: vec![PreRender.intern(), Render.intern(), PostRender.intern()], + labels: vec![Render.intern()], } } } -impl RenderMainScheduleOrder { +impl RenderScheduleOrder { /// Adds the given `schedule` after the `after` schedule pub fn insert_after(&mut self, after: impl ScheduleLabel, schedule: impl ScheduleLabel) { let index = self @@ -278,14 +278,6 @@ impl RenderMainScheduleOrder { } } -/// Runs immediately before the main render schedule. -#[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone, Default)] -pub struct PreRender; - -/// Runs immediately after the main render schedule. -#[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone, Default)] -pub struct PostRender; - /// The main render schedule. #[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone, Default)] pub struct Render; @@ -389,6 +381,7 @@ impl Plugin for RenderPlugin { app.init_resource::() .init_resource::(); if let Some(render_app) = app.get_sub_app_mut(RenderApp) { + render_app.init_resource::(); render_app.init_resource::(); render_app.init_gpu_resource::(); render_app.insert_resource(sender); @@ -402,11 +395,7 @@ impl Plugin for RenderPlugin { ), ); - render_app.init_resource::(); - - render_app.init_schedule(PreRender); render_app.add_schedule(RenderGraph::base_schedule()); - render_app.init_schedule(PostRender); render_app.init_schedule(RenderStartup); render_app @@ -467,7 +456,7 @@ fn renderer_is_ready(state: Res) -> bool { } fn run_render_schedule(world: &mut World) { - world.resource_scope(|world, order: Mut| { + world.resource_scope(|world, order: Mut| { for &label in &order.labels { let _ = world.try_run_schedule(label); } From 53554cc949a02585a796c0ee6b8cbe130a8a21bb Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Fri, 27 Mar 2026 10:38:54 +0800 Subject: [PATCH 5/5] Docs --- crates/bevy_render/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index d13cd89cc8d8a..117876078ede0 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -236,7 +236,7 @@ impl GpuResourceAppExt for SubApp { } } -/// The render recovery schedule. This schedule runs the [`RenderMainScheduleOrder`] schedules if +/// The render recovery schedule. This schedule runs the [`RenderScheduleOrder`] schedules if /// we are in [`RenderState::Ready`], and is otherwise hidden from users. #[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone)] struct RenderRecovery;