From c3ee6c4afeb34f425cb50c6c24cb9c56229cb60b Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Fri, 3 Jun 2022 19:22:27 +0100 Subject: [PATCH 1/4] App: Add render_init callbacks API This adds an App mechanism for deferring the initialization of render state via `add_render_init` callbacks. The App runner is responsible for deciding when to call `app.render_init()` once it's determined that the system is in a suitable state. All render_init callbacks are called in the order they are added so they can have inter dependencies in much the same way as plugin `build()` functions. --- crates/bevy_app/src/app.rs | 81 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) 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) { From 0ca6a0ab86a4dde00cda7fc1d070c62968de3229 Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Fri, 3 Jun 2022 19:40:18 +0100 Subject: [PATCH 2/4] Use render_init callbacks to init RenderApp state This wraps all plugin initialization that depends on the RenderApp sub app into a render_init callback so that render state initialization can be deferred by the runner. For now the winit runner immediately calls app.render_init() so it's not yet significantly deferred, but plugin building and render state initialization now happen in two separate passes. Note: This patch intentionally avoids making functional change to the code that's moved into render_init functions to help keep this easy to review. In the case of bevy_render/src/lib.rs any change in indentation was also avoided to help reduce churn. --- crates/bevy_core_pipeline/src/core_2d/mod.rs | 58 +++---- crates/bevy_core_pipeline/src/core_3d/mod.rs | 66 ++++---- crates/bevy_pbr/src/lib.rs | 122 ++++++++------- crates/bevy_pbr/src/material.rs | 21 +-- crates/bevy_pbr/src/render/mesh.rs | 22 +-- crates/bevy_pbr/src/wireframe.rs | 18 ++- crates/bevy_render/src/camera/mod.rs | 14 +- crates/bevy_render/src/extract_component.rs | 29 ++-- crates/bevy_render/src/extract_resource.rs | 8 +- crates/bevy_render/src/lib.rs | 27 ++-- crates/bevy_render/src/render_asset.rs | 43 ++--- crates/bevy_render/src/texture/mod.rs | 12 +- crates/bevy_render/src/view/mod.rs | 20 +-- crates/bevy_render/src/view/window.rs | 24 +-- crates/bevy_sprite/src/lib.rs | 34 ++-- crates/bevy_sprite/src/mesh2d/material.rs | 17 +- crates/bevy_sprite/src/mesh2d/mesh.rs | 18 ++- crates/bevy_text/src/lib.rs | 14 +- crates/bevy_ui/src/lib.rs | 4 +- crates/bevy_ui/src/render/mod.rs | 156 ++++++++++--------- crates/bevy_winit/src/lib.rs | 1 + 21 files changed, 390 insertions(+), 338 deletions(-) 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..62ab56ec8b171 100644 --- a/crates/bevy_render/src/texture/mod.rs +++ b/crates/bevy_render/src/texture/mod.rs @@ -63,11 +63,13 @@ 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| { + 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..e27917b0e465d 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -283,6 +283,7 @@ impl Default for WinitPersistentState { struct WinitCreateWindowReader(ManualEventReader); pub fn winit_runner_with(mut app: App) { + app.render_init(); let mut event_loop = app .world .remove_non_send_resource::>() From 4bcc32a775f4e9636c4ec87a853c9d16006dc479 Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Fri, 3 Jun 2022 19:50:32 +0100 Subject: [PATCH 3/4] bevy_winit: defer render state initialization On Android the render state is now only initialized when the application resumes for the first time (at which point it as a valid SurfaceView). On all other platforms the render state will be initialized based on the `NewEvents(Init)` event (though this may change in the future if Winit starts to emit Resumed events consistently for all platforms). Considering how some events (CreateWindow) events can only be handled along with render state initialization (at least on Android) this change also blocks all app update()s until the render state is initialized, otherwise these events are liable to be cleared before they can be handled. --- crates/bevy_winit/src/lib.rs | 76 +++++++++++++++++++++++------------- 1 file changed, 49 insertions(+), 27 deletions(-) diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index e27917b0e465d..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,20 +284,12 @@ impl Default for WinitPersistentState { } } -#[derive(Default)] -struct WinitCreateWindowReader(ManualEventReader); - pub fn winit_runner_with(mut app: App) { - app.render_init(); 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(); @@ -307,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::(); @@ -550,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::(); From 1fb429b69899ab95141c1c4ce5ae193b8dcfd64a Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Sun, 5 Jun 2022 14:09:32 +0100 Subject: [PATCH 4/4] Defer init of texture loaders via render_init callback The texture loaders depend on being able to query a `RenderDevice` for supported compressed texture formats, and if no device is found they (silently) fallback to assuming `all()` formats are supported. This moves the initialization of the `ImageTextureLoader` and `HdrTextureLoader` asset loaders into the `.add_render_init()` callback in `ImagePlugin::build()` to ensure they are only initialized after the `RenderDevice` has been created. Tested with `examples/3d/texture.rs` --- crates/bevy_render/src/texture/mod.rs | 36 +++++++++++++-------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/crates/bevy_render/src/texture/mod.rs b/crates/bevy_render/src/texture/mod.rs index 62ab56ec8b171..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, )) @@ -64,6 +46,24 @@ impl Plugin for ImagePlugin { .set_untracked(DEFAULT_IMAGE_HANDLE, Image::default()); 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::()