diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index d587344d6549b..6c5fe7d7498ee 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -55,6 +55,10 @@ pub struct App { /// Typically, it is not configured manually, but set by one of Bevy's built-in plugins. /// See `bevy::winit::WinitPlugin` and [`ScheduleRunnerPlugin`](crate::schedule_runner::ScheduleRunnerPlugin). pub runner: Box, + /// The [render init functions](Self::add_render_init) are responsible for completing + /// the initialization of plugins once the `runner` has determined that the system is + /// ready to support rendering. + pub render_inits: Vec>, /// A container of [`Stage`]s set to be run in a linear order. pub schedule: Schedule, sub_apps: HashMap, SubApp>, @@ -100,6 +104,7 @@ impl App { world: Default::default(), schedule: Default::default(), runner: Box::new(run_once), + render_inits: vec![], sub_apps: HashMap::default(), } } @@ -750,6 +755,59 @@ impl App { self } + /// Adds a function that will be called when the runner wants to initialize rendering state + /// + /// Each renderer init function `init_fn` is called only once by the app runner once it has + /// determined that the system is in a suitable state for rendering state to be initialized. + /// + /// Any number of render init callbacks may be added while building plugins and each added + /// function will be called exactly once. The functions are called in the same order that they + /// are added, after which all added functions are cleared. + /// + /// In case there are interdependencies between plugins then `add_render_init` may be called + /// before or after adding additional plugins (for dependency or dependant initialization + /// respectively). + /// + /// It can be assumed that the runner will block all system updates for this `App` until + /// after all render init functions have been called (as well as updates for any sub + /// applications). For example this means that events that are sent while building + /// plugins can be assumed to survive until after all render init functions have run. + /// + /// For example on Android a runner may wait until the application has reached a 'resumed' + /// state with an associated surface view before trying to initialize rendering state. + /// + /// `add_render_init` is typically only used by Bevy-internal plugins (e.g. `RenderPlugin`) + /// that need to defer initialization that depends on other render state being initialized. + /// + /// # Examples + /// + /// ``` + /// # use bevy_app::{prelude::*, AppLabel}; + /// # fn find_instance() {} + /// # fn open_device() {} + /// # fn create_context() {} + /// # #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel)] + /// # pub struct RenderApp; + /// fn my_render_init(app: &mut App) { + /// let instance = find_instance(); + /// let device = open_device(); + /// let context = create_context(); + /// let render_app = App::empty(); + /// app.add_sub_app(RenderApp, render_app, move |world, render_app| { + /// // prepare + /// // render + /// }); + /// } + /// + /// App::new() + /// .add_render_init(my_render_init); + /// ``` + pub fn add_render_init(&mut self, init_fn: impl FnOnce(&mut App) + 'static) -> &mut Self { + let init_fn = Box::new(init_fn); + self.render_inits.push(init_fn); + self + } + /// Adds a single [`Plugin`]. /// /// One of Bevy's core principles is modularity. All Bevy engine features are implemented @@ -926,6 +984,29 @@ impl App { .map(|sub_app| &sub_app.app) .ok_or(label) } + + /// Finishes plugin setup once the `App` runner has determined system is ready for rendering + /// + /// This should be called by the `runner` function once it has determined that the system is + /// in a suitable state to be able to initialize render state, such as creating a GPU + /// context. + /// + /// For example, on Android the runner will wait until the application is first 'resumed' and + /// it has a valid surface view. + /// + /// This will invoke all registered [render init functions](Self::add_render_init) in the + /// same order that they were added. + /// + /// All registered callbacks are cleared before returning, so this can only be (meaningfully) + /// called once. + pub fn render_init(&mut self) { + let render_inits = std::mem::take(&mut self.render_inits); + + // Initialize in the same order that render_init callbacks were registered + for callback in render_inits.into_iter() { + callback(self); + } + } } fn run_once(mut app: App) { diff --git a/crates/bevy_core_pipeline/src/core_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs index 8acdb42da54ef..72cb014a208b2 100644 --- a/crates/bevy_core_pipeline/src/core_2d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -37,35 +37,37 @@ impl Plugin for Core2dPlugin { app.register_type::() .add_plugin(ExtractComponentPlugin::::default()); - let render_app = match app.get_sub_app_mut(RenderApp) { - Ok(render_app) => render_app, - Err(_) => return, - }; - - render_app - .init_resource::>() - .add_system_to_stage(RenderStage::Extract, extract_core_2d_camera_phases) - .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::) - .add_system_to_stage(RenderStage::PhaseSort, batch_phase_system::); - - let pass_node_2d = MainPass2dNode::new(&mut render_app.world); - let mut graph = render_app.world.resource_mut::(); - - let mut draw_2d_graph = RenderGraph::default(); - draw_2d_graph.add_node(graph::node::MAIN_PASS, pass_node_2d); - let input_node_id = draw_2d_graph.set_input(vec![SlotInfo::new( - graph::input::VIEW_ENTITY, - SlotType::Entity, - )]); - draw_2d_graph - .add_slot_edge( - input_node_id, + app.add_render_init(move |app| { + let render_app = match app.get_sub_app_mut(RenderApp) { + Ok(render_app) => render_app, + Err(_) => return, + }; + + render_app + .init_resource::>() + .add_system_to_stage(RenderStage::Extract, extract_core_2d_camera_phases) + .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::) + .add_system_to_stage(RenderStage::PhaseSort, batch_phase_system::); + + let pass_node_2d = MainPass2dNode::new(&mut render_app.world); + let mut graph = render_app.world.resource_mut::(); + + let mut draw_2d_graph = RenderGraph::default(); + draw_2d_graph.add_node(graph::node::MAIN_PASS, pass_node_2d); + let input_node_id = draw_2d_graph.set_input(vec![SlotInfo::new( graph::input::VIEW_ENTITY, - graph::node::MAIN_PASS, - MainPass2dNode::IN_VIEW, - ) - .unwrap(); - graph.add_sub_graph(graph::NAME, draw_2d_graph); + SlotType::Entity, + )]); + draw_2d_graph + .add_slot_edge( + input_node_id, + graph::input::VIEW_ENTITY, + graph::node::MAIN_PASS, + MainPass2dNode::IN_VIEW, + ) + .unwrap(); + graph.add_sub_graph(graph::NAME, draw_2d_graph); + }); } } diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index 6342d8471291b..31b5e08d5ee45 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -43,39 +43,41 @@ impl Plugin for Core3dPlugin { app.register_type::() .add_plugin(ExtractComponentPlugin::::default()); - let render_app = match app.get_sub_app_mut(RenderApp) { - Ok(render_app) => render_app, - Err(_) => return, - }; - - render_app - .init_resource::>() - .init_resource::>() - .init_resource::>() - .add_system_to_stage(RenderStage::Extract, extract_core_3d_camera_phases) - .add_system_to_stage(RenderStage::Prepare, prepare_core_3d_depth_textures) - .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::) - .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::) - .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::); - - let pass_node_3d = MainPass3dNode::new(&mut render_app.world); - let mut graph = render_app.world.resource_mut::(); - - let mut draw_3d_graph = RenderGraph::default(); - draw_3d_graph.add_node(graph::node::MAIN_PASS, pass_node_3d); - let input_node_id = draw_3d_graph.set_input(vec![SlotInfo::new( - graph::input::VIEW_ENTITY, - SlotType::Entity, - )]); - draw_3d_graph - .add_slot_edge( - input_node_id, + app.add_render_init(move |app| { + let render_app = match app.get_sub_app_mut(RenderApp) { + Ok(render_app) => render_app, + Err(_) => return, + }; + + render_app + .init_resource::>() + .init_resource::>() + .init_resource::>() + .add_system_to_stage(RenderStage::Extract, extract_core_3d_camera_phases) + .add_system_to_stage(RenderStage::Prepare, prepare_core_3d_depth_textures) + .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::) + .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::) + .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::); + + let pass_node_3d = MainPass3dNode::new(&mut render_app.world); + let mut graph = render_app.world.resource_mut::(); + + let mut draw_3d_graph = RenderGraph::default(); + draw_3d_graph.add_node(graph::node::MAIN_PASS, pass_node_3d); + let input_node_id = draw_3d_graph.set_input(vec![SlotInfo::new( graph::input::VIEW_ENTITY, - graph::node::MAIN_PASS, - MainPass3dNode::IN_VIEW, - ) - .unwrap(); - graph.add_sub_graph(graph::NAME, draw_3d_graph); + SlotType::Entity, + )]); + draw_3d_graph + .add_slot_edge( + input_node_id, + graph::input::VIEW_ENTITY, + graph::node::MAIN_PASS, + MainPass3dNode::IN_VIEW, + ) + .unwrap(); + graph.add_sub_graph(graph::NAME, draw_3d_graph); + }); } } diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index 3f40f9b826c09..d64813eed6679 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -149,67 +149,69 @@ impl Plugin for PbrPlugin { }, ); - let render_app = match app.get_sub_app_mut(RenderApp) { - Ok(render_app) => render_app, - Err(_) => return, - }; + app.add_render_init(move |app| { + let render_app = match app.get_sub_app_mut(RenderApp) { + Ok(render_app) => render_app, + Err(_) => return, + }; - render_app - .add_system_to_stage( - RenderStage::Extract, - render::extract_clusters.label(RenderLightSystems::ExtractClusters), - ) - .add_system_to_stage( - RenderStage::Extract, - render::extract_lights.label(RenderLightSystems::ExtractLights), - ) - .add_system_to_stage( - RenderStage::Prepare, - // this is added as an exclusive system because it contributes new views. it must run (and have Commands applied) - // _before_ the `prepare_views()` system is run. ideally this becomes a normal system when "stageless" features come out - render::prepare_lights - .exclusive_system() - .label(RenderLightSystems::PrepareLights), - ) - .add_system_to_stage( - RenderStage::Prepare, - // NOTE: This needs to run after prepare_lights. As prepare_lights is an exclusive system, - // just adding it to the non-exclusive systems in the Prepare stage means it runs after - // prepare_lights. - render::prepare_clusters.label(RenderLightSystems::PrepareClusters), - ) - .add_system_to_stage( - RenderStage::Queue, - render::queue_shadows.label(RenderLightSystems::QueueShadows), - ) - .add_system_to_stage(RenderStage::Queue, render::queue_shadow_view_bind_group) - .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::) - .init_resource::() - .init_resource::>() - .init_resource::() - .init_resource::() - .init_resource::>(); + render_app + .add_system_to_stage( + RenderStage::Extract, + render::extract_clusters.label(RenderLightSystems::ExtractClusters), + ) + .add_system_to_stage( + RenderStage::Extract, + render::extract_lights.label(RenderLightSystems::ExtractLights), + ) + .add_system_to_stage( + RenderStage::Prepare, + // this is added as an exclusive system because it contributes new views. it must run (and have Commands applied) + // _before_ the `prepare_views()` system is run. ideally this becomes a normal system when "stageless" features come out + render::prepare_lights + .exclusive_system() + .label(RenderLightSystems::PrepareLights), + ) + .add_system_to_stage( + RenderStage::Prepare, + // NOTE: This needs to run after prepare_lights. As prepare_lights is an exclusive system, + // just adding it to the non-exclusive systems in the Prepare stage means it runs after + // prepare_lights. + render::prepare_clusters.label(RenderLightSystems::PrepareClusters), + ) + .add_system_to_stage( + RenderStage::Queue, + render::queue_shadows.label(RenderLightSystems::QueueShadows), + ) + .add_system_to_stage(RenderStage::Queue, render::queue_shadow_view_bind_group) + .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::) + .init_resource::() + .init_resource::>() + .init_resource::() + .init_resource::() + .init_resource::>(); - let shadow_pass_node = ShadowPassNode::new(&mut render_app.world); - render_app.add_render_command::(); - let mut graph = render_app.world.resource_mut::(); - let draw_3d_graph = graph - .get_sub_graph_mut(bevy_core_pipeline::core_3d::graph::NAME) - .unwrap(); - draw_3d_graph.add_node(draw_3d_graph::node::SHADOW_PASS, shadow_pass_node); - draw_3d_graph - .add_node_edge( - draw_3d_graph::node::SHADOW_PASS, - bevy_core_pipeline::core_3d::graph::node::MAIN_PASS, - ) - .unwrap(); - draw_3d_graph - .add_slot_edge( - draw_3d_graph.input_node().unwrap().id, - bevy_core_pipeline::core_3d::graph::input::VIEW_ENTITY, - draw_3d_graph::node::SHADOW_PASS, - ShadowPassNode::IN_VIEW, - ) - .unwrap(); + let shadow_pass_node = ShadowPassNode::new(&mut render_app.world); + render_app.add_render_command::(); + let mut graph = render_app.world.resource_mut::(); + let draw_3d_graph = graph + .get_sub_graph_mut(bevy_core_pipeline::core_3d::graph::NAME) + .unwrap(); + draw_3d_graph.add_node(draw_3d_graph::node::SHADOW_PASS, shadow_pass_node); + draw_3d_graph + .add_node_edge( + draw_3d_graph::node::SHADOW_PASS, + bevy_core_pipeline::core_3d::graph::node::MAIN_PASS, + ) + .unwrap(); + draw_3d_graph + .add_slot_edge( + draw_3d_graph.input_node().unwrap().id, + bevy_core_pipeline::core_3d::graph::input::VIEW_ENTITY, + draw_3d_graph::node::SHADOW_PASS, + ShadowPassNode::IN_VIEW, + ) + .unwrap(); + }); } } diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 28620aaec1d03..0011c20c2cdb6 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -226,15 +226,18 @@ impl Plugin for MaterialPlugin { app.add_asset::() .add_plugin(ExtractComponentPlugin::>::extract_visible()) .add_plugin(RenderAssetPlugin::::default()); - if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { - render_app - .add_render_command::>() - .add_render_command::>() - .add_render_command::>() - .init_resource::>() - .init_resource::>>() - .add_system_to_stage(RenderStage::Queue, queue_material_meshes::); - } + + app.add_render_init(move |app| { + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { + render_app + .add_render_command::>() + .add_render_command::>() + .add_render_command::>() + .init_resource::>() + .init_resource::>>() + .add_system_to_stage(RenderStage::Queue, queue_material_meshes::); + } + }); } } diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 204c44c3bd45a..dbe30e0155b20 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -74,16 +74,18 @@ impl Plugin for MeshRenderPlugin { app.add_plugin(UniformComponentPlugin::::default()); - if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { - render_app - .init_resource::() - .init_resource::() - .add_system_to_stage(RenderStage::Extract, extract_meshes) - .add_system_to_stage(RenderStage::Extract, extract_skinned_meshes) - .add_system_to_stage(RenderStage::Prepare, prepare_skinned_meshes) - .add_system_to_stage(RenderStage::Queue, queue_mesh_bind_group) - .add_system_to_stage(RenderStage::Queue, queue_mesh_view_bind_groups); - } + app.add_render_init(move |app| { + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { + render_app + .init_resource::() + .init_resource::() + .add_system_to_stage(RenderStage::Extract, extract_meshes) + .add_system_to_stage(RenderStage::Extract, extract_skinned_meshes) + .add_system_to_stage(RenderStage::Prepare, prepare_skinned_meshes) + .add_system_to_stage(RenderStage::Queue, queue_mesh_bind_group) + .add_system_to_stage(RenderStage::Queue, queue_mesh_view_bind_groups); + } + }); } } diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 6a7a4c3c297c2..5a724f6b77eb4 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -38,14 +38,16 @@ impl Plugin for WireframePlugin { app.init_resource::() .add_plugin(ExtractResourcePlugin::::default()); - if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { - render_app - .add_render_command::() - .init_resource::() - .init_resource::>() - .add_system_to_stage(RenderStage::Extract, extract_wireframes) - .add_system_to_stage(RenderStage::Queue, queue_wireframes); - } + app.add_render_init(move |app| { + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { + render_app + .add_render_command::() + .init_resource::() + .init_resource::>() + .add_system_to_stage(RenderStage::Extract, extract_wireframes) + .add_system_to_stage(RenderStage::Queue, queue_wireframes); + } + }); } } diff --git a/crates/bevy_render/src/camera/mod.rs b/crates/bevy_render/src/camera/mod.rs index 37acb0b895ea7..c2beaee36e45f 100644 --- a/crates/bevy_render/src/camera/mod.rs +++ b/crates/bevy_render/src/camera/mod.rs @@ -33,12 +33,14 @@ impl Plugin for CameraPlugin { .add_plugin(CameraProjectionPlugin::::default()) .add_plugin(CameraProjectionPlugin::::default()); - if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { - render_app.add_system_to_stage(RenderStage::Extract, extract_cameras); + app.add_render_init(move |app| { + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { + render_app.add_system_to_stage(RenderStage::Extract, extract_cameras); - let camera_driver_node = CameraDriverNode::new(&mut render_app.world); - let mut render_graph = render_app.world.resource_mut::(); - render_graph.add_node(crate::main_graph::node::CAMERA_DRIVER, camera_driver_node); - } + let camera_driver_node = CameraDriverNode::new(&mut render_app.world); + let mut render_graph = render_app.world.resource_mut::(); + render_graph.add_node(crate::main_graph::node::CAMERA_DRIVER, camera_driver_node); + } + }); } } diff --git a/crates/bevy_render/src/extract_component.rs b/crates/bevy_render/src/extract_component.rs index 673b23c30770e..062c7434df552 100644 --- a/crates/bevy_render/src/extract_component.rs +++ b/crates/bevy_render/src/extract_component.rs @@ -60,11 +60,13 @@ impl Default for UniformComponentPlugin { impl Plugin for UniformComponentPlugin { fn build(&self, app: &mut App) { - if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { - render_app - .insert_resource(ComponentUniforms::::default()) - .add_system_to_stage(RenderStage::Prepare, prepare_uniform_components::); - } + app.add_render_init(move |app| { + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { + render_app + .insert_resource(ComponentUniforms::::default()) + .add_system_to_stage(RenderStage::Prepare, prepare_uniform_components::); + } + }); } } @@ -157,14 +159,17 @@ impl ExtractComponentPlugin { impl Plugin for ExtractComponentPlugin { fn build(&self, app: &mut App) { - if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { - if self.only_extract_visible { - render_app - .add_system_to_stage(RenderStage::Extract, extract_visible_components::); - } else { - render_app.add_system_to_stage(RenderStage::Extract, extract_components::); + let only_extract_visible = self.only_extract_visible; + app.add_render_init(move |app| { + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { + if only_extract_visible { + render_app + .add_system_to_stage(RenderStage::Extract, extract_visible_components::); + } else { + render_app.add_system_to_stage(RenderStage::Extract, extract_components::); + } } - } + }); } } diff --git a/crates/bevy_render/src/extract_resource.rs b/crates/bevy_render/src/extract_resource.rs index 5db3be904b263..47653a01dc293 100644 --- a/crates/bevy_render/src/extract_resource.rs +++ b/crates/bevy_render/src/extract_resource.rs @@ -31,9 +31,11 @@ impl Default for ExtractResourcePlugin { impl Plugin for ExtractResourcePlugin { fn build(&self, app: &mut App) { - if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { - render_app.add_system_to_stage(RenderStage::Extract, extract_resource::); - } + app.add_render_init(move |app| { + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { + render_app.add_system_to_stage(RenderStage::Extract, extract_resource::); + } + }); } } diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 888d0d6865b96..60d6bb1ddfb36 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -114,19 +114,26 @@ struct ScratchRenderWorld(World); impl Plugin for RenderPlugin { /// Initializes the renderer, sets up the [`RenderStage`](RenderStage) and creates the rendering sub-app. fn build(&self, app: &mut App) { - let options = app - .world - .get_resource::() - .cloned() - .unwrap_or_default(); - app.add_asset::() .add_debug_asset::() .init_asset_loader::() .init_debug_asset_loader::() .register_type::(); - if let Some(backends) = options.backends { + // This renderer_init callback only happens once the top-level runner (e.g. bevy_winit) + // has determined that the system is ready for render state to be initialized + app.add_render_init(move |app| { + let options = app + .world + .get_resource::() + .cloned() + .unwrap_or_default(); + + let backends = match options.backends { + Some(backends) => backends, + None => return, + }; + let instance = wgpu::Instance::new(backends); let surface = { let windows = app.world.resource_mut::(); @@ -286,14 +293,14 @@ impl Plugin for RenderPlugin { render_app.world.clear_entities(); } }); - } + }); + // Only add these plugins after adding the above render_init function, to ensure + // any render initialization required for these plugins will find the sub RenderApp app.add_plugin(WindowRenderPlugin) .add_plugin(CameraPlugin) .add_plugin(ViewPlugin) .add_plugin(MeshPlugin) - // NOTE: Load this after renderer initialization so that it knows about the supported - // compressed texture formats .add_plugin(ImagePlugin); } } diff --git a/crates/bevy_render/src/render_asset.rs b/crates/bevy_render/src/render_asset.rs index 22936f3e677be..9d8b04864ade3 100644 --- a/crates/bevy_render/src/render_asset.rs +++ b/crates/bevy_render/src/render_asset.rs @@ -82,26 +82,29 @@ impl Default for RenderAssetPlugin { impl Plugin for RenderAssetPlugin { fn build(&self, app: &mut App) { - if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { - let prepare_asset_system = prepare_assets::.label(self.prepare_asset_label.clone()); - - let prepare_asset_system = match self.prepare_asset_label { - PrepareAssetLabel::PreAssetPrepare => prepare_asset_system, - PrepareAssetLabel::AssetPrepare => { - prepare_asset_system.after(PrepareAssetLabel::PreAssetPrepare) - } - PrepareAssetLabel::PostAssetPrepare => { - prepare_asset_system.after(PrepareAssetLabel::AssetPrepare) - } - }; - - render_app - .init_resource::>() - .init_resource::>() - .init_resource::>() - .add_system_to_stage(RenderStage::Extract, extract_render_asset::) - .add_system_to_stage(RenderStage::Prepare, prepare_asset_system); - } + let label = self.prepare_asset_label.clone(); + app.add_render_init(move |app| { + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { + let prepare_asset_system = prepare_assets::.label(label.clone()); + + let prepare_asset_system = match label { + PrepareAssetLabel::PreAssetPrepare => prepare_asset_system, + PrepareAssetLabel::AssetPrepare => { + prepare_asset_system.after(PrepareAssetLabel::PreAssetPrepare) + } + PrepareAssetLabel::PostAssetPrepare => { + prepare_asset_system.after(PrepareAssetLabel::AssetPrepare) + } + }; + + render_app + .init_resource::>() + .init_resource::>() + .init_resource::>() + .add_system_to_stage(RenderStage::Extract, extract_render_asset::) + .add_system_to_stage(RenderStage::Prepare, prepare_asset_system); + } + }); } } diff --git a/crates/bevy_render/src/texture/mod.rs b/crates/bevy_render/src/texture/mod.rs index 851eac1371b9e..86fa0a259b83c 100644 --- a/crates/bevy_render/src/texture/mod.rs +++ b/crates/bevy_render/src/texture/mod.rs @@ -37,24 +37,6 @@ pub struct ImagePlugin; impl Plugin for ImagePlugin { fn build(&self, app: &mut App) { - #[cfg(any( - feature = "png", - feature = "dds", - feature = "tga", - feature = "jpeg", - feature = "bmp", - feature = "basis-universal", - feature = "ktx2", - ))] - { - app.init_asset_loader::(); - } - - #[cfg(feature = "hdr")] - { - app.init_asset_loader::(); - } - app.add_plugin(RenderAssetPlugin::::with_prepare_asset_label( PrepareAssetLabel::PreAssetPrepare, )) @@ -63,11 +45,31 @@ impl Plugin for ImagePlugin { .resource_mut::>() .set_untracked(DEFAULT_IMAGE_HANDLE, Image::default()); - if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { - render_app - .init_resource::() - .add_system_to_stage(RenderStage::Cleanup, update_texture_cache_system); - } + app.add_render_init(move |app| { + #[cfg(any( + feature = "png", + feature = "dds", + feature = "tga", + feature = "jpeg", + feature = "bmp", + feature = "basis-universal", + feature = "ktx2", + ))] + { + app.init_asset_loader::(); + } + + #[cfg(feature = "hdr")] + { + app.init_asset_loader::(); + } + + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { + render_app + .init_resource::() + .add_system_to_stage(RenderStage::Cleanup, update_texture_cache_system); + } + }); } } diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs index 2638e94d4581a..9b8d96b658c55 100644 --- a/crates/bevy_render/src/view/mod.rs +++ b/crates/bevy_render/src/view/mod.rs @@ -33,15 +33,17 @@ impl Plugin for ViewPlugin { .add_plugin(ExtractResourcePlugin::::default()) .add_plugin(VisibilityPlugin); - if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { - render_app - .init_resource::() - .add_system_to_stage(RenderStage::Prepare, prepare_view_uniforms) - .add_system_to_stage( - RenderStage::Prepare, - prepare_view_targets.after(WindowSystem::Prepare), - ); - } + app.add_render_init(move |app| { + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { + render_app + .init_resource::() + .add_system_to_stage(RenderStage::Prepare, prepare_view_uniforms) + .add_system_to_stage( + RenderStage::Prepare, + prepare_view_targets.after(WindowSystem::Prepare), + ); + } + }); } } diff --git a/crates/bevy_render/src/view/window.rs b/crates/bevy_render/src/view/window.rs index 305a56ddd115b..52424be7c0412 100644 --- a/crates/bevy_render/src/view/window.rs +++ b/crates/bevy_render/src/view/window.rs @@ -24,17 +24,19 @@ pub enum WindowSystem { impl Plugin for WindowRenderPlugin { fn build(&self, app: &mut App) { - if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { - render_app - .init_resource::() - .init_resource::() - .init_resource::() - .add_system_to_stage(RenderStage::Extract, extract_windows) - .add_system_to_stage( - RenderStage::Prepare, - prepare_windows.label(WindowSystem::Prepare), - ); - } + app.add_render_init(move |app| { + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { + render_app + .init_resource::() + .init_resource::() + .init_resource::() + .add_system_to_stage(RenderStage::Extract, extract_windows) + .add_system_to_stage( + RenderStage::Prepare, + prepare_windows.label(WindowSystem::Prepare), + ); + } + }); } } diff --git a/crates/bevy_sprite/src/lib.rs b/crates/bevy_sprite/src/lib.rs index a7be1a4ab983b..6a3ce3049e33c 100644 --- a/crates/bevy_sprite/src/lib.rs +++ b/crates/bevy_sprite/src/lib.rs @@ -61,21 +61,23 @@ impl Plugin for SpritePlugin { .add_plugin(Mesh2dRenderPlugin) .add_plugin(ColorMaterialPlugin); - if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { - render_app - .init_resource::() - .init_resource::() - .init_resource::>() - .init_resource::() - .init_resource::() - .init_resource::() - .add_render_command::() - .add_system_to_stage( - RenderStage::Extract, - render::extract_sprites.label(SpriteSystem::ExtractSprites), - ) - .add_system_to_stage(RenderStage::Extract, render::extract_sprite_events) - .add_system_to_stage(RenderStage::Queue, queue_sprites); - }; + app.add_render_init(move |app| { + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { + render_app + .init_resource::() + .init_resource::() + .init_resource::>() + .init_resource::() + .init_resource::() + .init_resource::() + .add_render_command::() + .add_system_to_stage( + RenderStage::Extract, + render::extract_sprites.label(SpriteSystem::ExtractSprites), + ) + .add_system_to_stage(RenderStage::Extract, render::extract_sprite_events) + .add_system_to_stage(RenderStage::Queue, queue_sprites); + } + }); } } diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index 9ed1ce540757b..ccb9d1f40d5d9 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -196,13 +196,16 @@ impl Plugin for Material2dPlugin { app.add_asset::() .add_plugin(ExtractComponentPlugin::>::extract_visible()) .add_plugin(RenderAssetPlugin::::default()); - if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { - render_app - .add_render_command::>() - .init_resource::>() - .init_resource::>>() - .add_system_to_stage(RenderStage::Queue, queue_material2d_meshes::); - } + + app.add_render_init(move |app| { + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { + render_app + .add_render_command::>() + .init_resource::>() + .init_resource::>>() + .add_system_to_stage(RenderStage::Queue, queue_material2d_meshes::); + } + }); } } diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index c0c3e3010c302..29467bb520fbf 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -76,14 +76,16 @@ impl Plugin for Mesh2dRenderPlugin { app.add_plugin(UniformComponentPlugin::::default()); - if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { - render_app - .init_resource::() - .init_resource::>() - .add_system_to_stage(RenderStage::Extract, extract_mesh2d) - .add_system_to_stage(RenderStage::Queue, queue_mesh2d_bind_group) - .add_system_to_stage(RenderStage::Queue, queue_mesh2d_view_bind_groups); - } + app.add_render_init(move |app| { + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { + render_app + .init_resource::() + .init_resource::>() + .add_system_to_stage(RenderStage::Extract, extract_mesh2d) + .add_system_to_stage(RenderStage::Queue, queue_mesh2d_bind_group) + .add_system_to_stage(RenderStage::Queue, queue_mesh2d_view_bind_groups); + } + }); } } diff --git a/crates/bevy_text/src/lib.rs b/crates/bevy_text/src/lib.rs index 4dc1fc3b34070..615c5ad941517 100644 --- a/crates/bevy_text/src/lib.rs +++ b/crates/bevy_text/src/lib.rs @@ -52,11 +52,13 @@ impl Plugin for TextPlugin { update_text2d_layout.after(ModifiesWindows), ); - if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { - render_app.add_system_to_stage( - RenderStage::Extract, - extract_text2d_sprite.after(SpriteSystem::ExtractSprites), - ); - } + app.add_render_init(move |app| { + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { + render_app.add_system_to_stage( + RenderStage::Extract, + extract_text2d_sprite.after(SpriteSystem::ExtractSprites), + ); + } + }); } } diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index 135cca188eb49..b4578e31ccbb5 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -110,6 +110,8 @@ impl Plugin for UiPlugin { update_clipping_system.after(TransformSystem::TransformPropagate), ); - crate::render::build_ui_render(app); + app.add_render_init(move |app| { + crate::render::build_ui_render(app); + }); } } diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index b4bcb94e0a0c8..2267084f04f34 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -57,87 +57,89 @@ pub enum RenderUiSystem { pub fn build_ui_render(app: &mut App) { load_internal_asset!(app, UI_SHADER_HANDLE, "ui.wgsl", Shader::from_wgsl); - let render_app = match app.get_sub_app_mut(RenderApp) { - Ok(render_app) => render_app, - Err(_) => return, - }; - - render_app - .init_resource::() - .init_resource::>() - .init_resource::() - .init_resource::() - .init_resource::() - .init_resource::>() - .add_render_command::() - .add_system_to_stage( - RenderStage::Extract, - extract_default_ui_camera_view::, - ) - .add_system_to_stage( - RenderStage::Extract, - extract_default_ui_camera_view::, - ) - .add_system_to_stage( - RenderStage::Extract, - extract_uinodes.label(RenderUiSystem::ExtractNode), - ) - .add_system_to_stage( - RenderStage::Extract, - extract_text_uinodes.after(RenderUiSystem::ExtractNode), - ) - .add_system_to_stage(RenderStage::Prepare, prepare_uinodes) - .add_system_to_stage(RenderStage::Queue, queue_uinodes) - .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::); - - // Render graph - let ui_graph_2d = get_ui_graph(render_app); - let ui_graph_3d = get_ui_graph(render_app); - let mut graph = render_app.world.resource_mut::(); - - if let Some(graph_2d) = graph.get_sub_graph_mut(bevy_core_pipeline::core_2d::graph::NAME) { - graph_2d.add_sub_graph(draw_ui_graph::NAME, ui_graph_2d); - graph_2d.add_node( - draw_ui_graph::node::UI_PASS, - RunGraphOnViewNode::new(draw_ui_graph::NAME), - ); - graph_2d - .add_node_edge( - bevy_core_pipeline::core_2d::graph::node::MAIN_PASS, - draw_ui_graph::node::UI_PASS, + app.add_render_init(move |app| { + let render_app = match app.get_sub_app_mut(RenderApp) { + Ok(render_app) => render_app, + Err(_) => return, + }; + + render_app + .init_resource::() + .init_resource::>() + .init_resource::() + .init_resource::() + .init_resource::() + .init_resource::>() + .add_render_command::() + .add_system_to_stage( + RenderStage::Extract, + extract_default_ui_camera_view::, ) - .unwrap(); - graph_2d - .add_slot_edge( - graph_2d.input_node().unwrap().id, - bevy_core_pipeline::core_2d::graph::input::VIEW_ENTITY, - draw_ui_graph::node::UI_PASS, - RunGraphOnViewNode::IN_VIEW, + .add_system_to_stage( + RenderStage::Extract, + extract_default_ui_camera_view::, ) - .unwrap(); - } - - if let Some(graph_3d) = graph.get_sub_graph_mut(bevy_core_pipeline::core_3d::graph::NAME) { - graph_3d.add_sub_graph(draw_ui_graph::NAME, ui_graph_3d); - graph_3d.add_node( - draw_ui_graph::node::UI_PASS, - RunGraphOnViewNode::new(draw_ui_graph::NAME), - ); - graph_3d - .add_node_edge( - bevy_core_pipeline::core_3d::graph::node::MAIN_PASS, - draw_ui_graph::node::UI_PASS, + .add_system_to_stage( + RenderStage::Extract, + extract_uinodes.label(RenderUiSystem::ExtractNode), ) - .unwrap(); - graph_3d - .add_slot_edge( - graph_3d.input_node().unwrap().id, - bevy_core_pipeline::core_3d::graph::input::VIEW_ENTITY, - draw_ui_graph::node::UI_PASS, - RunGraphOnViewNode::IN_VIEW, + .add_system_to_stage( + RenderStage::Extract, + extract_text_uinodes.after(RenderUiSystem::ExtractNode), ) - .unwrap(); - } + .add_system_to_stage(RenderStage::Prepare, prepare_uinodes) + .add_system_to_stage(RenderStage::Queue, queue_uinodes) + .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::); + + // Render graph + let ui_graph_2d = get_ui_graph(render_app); + let ui_graph_3d = get_ui_graph(render_app); + let mut graph = render_app.world.resource_mut::(); + + if let Some(graph_2d) = graph.get_sub_graph_mut(bevy_core_pipeline::core_2d::graph::NAME) { + graph_2d.add_sub_graph(draw_ui_graph::NAME, ui_graph_2d); + graph_2d.add_node( + draw_ui_graph::node::UI_PASS, + RunGraphOnViewNode::new(draw_ui_graph::NAME), + ); + graph_2d + .add_node_edge( + bevy_core_pipeline::core_2d::graph::node::MAIN_PASS, + draw_ui_graph::node::UI_PASS, + ) + .unwrap(); + graph_2d + .add_slot_edge( + graph_2d.input_node().unwrap().id, + bevy_core_pipeline::core_2d::graph::input::VIEW_ENTITY, + draw_ui_graph::node::UI_PASS, + RunGraphOnViewNode::IN_VIEW, + ) + .unwrap(); + } + + if let Some(graph_3d) = graph.get_sub_graph_mut(bevy_core_pipeline::core_3d::graph::NAME) { + graph_3d.add_sub_graph(draw_ui_graph::NAME, ui_graph_3d); + graph_3d.add_node( + draw_ui_graph::node::UI_PASS, + RunGraphOnViewNode::new(draw_ui_graph::NAME), + ); + graph_3d + .add_node_edge( + bevy_core_pipeline::core_3d::graph::node::MAIN_PASS, + draw_ui_graph::node::UI_PASS, + ) + .unwrap(); + graph_3d + .add_slot_edge( + graph_3d.input_node().unwrap().id, + bevy_core_pipeline::core_3d::graph::input::VIEW_ENTITY, + draw_ui_graph::node::UI_PASS, + RunGraphOnViewNode::IN_VIEW, + ) + .unwrap(); + } + }); } fn get_ui_graph(render_app: &mut App) -> RenderGraph { diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index 40534dd8098eb..bad44921b4b3a 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -48,12 +48,8 @@ impl Plugin for WinitPlugin { #[cfg(target_arch = "wasm32")] app.add_plugin(web_resize::CanvasParentResizePlugin); let event_loop = EventLoop::new(); - let mut create_window_reader = WinitCreateWindowReader::default(); - // Note that we create a window here "early" because WASM/WebGL requires the window to exist prior to initializing - // the renderer. - handle_create_window_events(&mut app.world, &event_loop, &mut create_window_reader.0); - app.insert_resource(create_window_reader) - .insert_non_send_resource(event_loop); + + app.insert_non_send_resource(event_loop); } } @@ -256,6 +252,8 @@ pub fn winit_runner(app: App) { /// Stores state that must persist between frames. struct WinitPersistentState { + /// Tracks whether the application has reached its first `Resumed` event + reached_first_resume: bool, /// Tracks whether or not the application is active or suspended. active: bool, /// Tracks whether or not an event has occurred this frame that would trigger an update in low @@ -270,7 +268,14 @@ struct WinitPersistentState { impl Default for WinitPersistentState { fn default() -> Self { Self { - active: true, + reached_first_resume: false, + + // We have to start non-active and block update()s until we get our + // first 'Resumed' event where we will first check for any CreateWindow requests. + // If we don't block updates then the Events::update system will clear the requests + // before they are handled! + active: false, + low_power_event: false, redraw_request_sent: false, timeout_reached: false, @@ -279,19 +284,12 @@ impl Default for WinitPersistentState { } } -#[derive(Default)] -struct WinitCreateWindowReader(ManualEventReader); - pub fn winit_runner_with(mut app: App) { let mut event_loop = app .world .remove_non_send_resource::>() .unwrap(); - let mut create_window_event_reader = app - .world - .remove_resource::() - .unwrap() - .0; + let mut create_window_event_reader = ManualEventReader::::default(); let mut app_exit_event_reader = ManualEventReader::::default(); let mut redraw_event_reader = ManualEventReader::::default(); let mut winit_state = WinitPersistentState::default(); @@ -306,6 +304,32 @@ pub fn winit_runner_with(mut app: App) { event_loop: &EventLoopWindowTarget<()>, control_flow: &mut ControlFlow| { match event { + // Ideally Winit would emit `Resumed` events consistently for all platforms but for + // now we treat NewEvents(Init) the same as a Resumed event for all platforms except + // Android + event::Event::Resumed | event::Event::NewEvents(StartCause::Init) => { + if cfg!(not(target_os = "android")) || matches!(event, event::Event::Resumed) { + winit_state.active = true; + + if !winit_state.reached_first_resume { + // Create any primary winit window that's been requested before we initialize + // render state. This ensures it's possible to create a surface for the + // window that can in turn be used to find a compatible adapter. + // + // In particular this is required for webgl + handle_create_window_events( + &mut app.world, + event_loop, + &mut create_window_event_reader, + ); + app.render_init(); + winit_state.reached_first_resume = true; + } + } + } + event::Event::Suspended => { + winit_state.active = false; + } event::Event::NewEvents(start) => { let winit_config = app.world.resource::(); let windows = app.world.resource::(); @@ -549,18 +573,17 @@ pub fn winit_runner_with(mut app: App) { delta: Vec2::new(delta.0 as f32, delta.1 as f32), }); } - event::Event::Suspended => { - winit_state.active = false; - } - event::Event::Resumed => { - winit_state.active = true; - } event::Event::MainEventsCleared => { - handle_create_window_events( - &mut app.world, - event_loop, - &mut create_window_event_reader, - ); + // We only initialize render state and start creating any + // windows once the app has has 'resumed' for the first time. + if winit_state.reached_first_resume { + handle_create_window_events( + &mut app.world, + event_loop, + &mut create_window_event_reader, + ); + } + let winit_config = app.world.resource::(); let update = if winit_state.active { let windows = app.world.resource::();