From 737ab2a81224bede3e8ebc776774913c9bc805d6 Mon Sep 17 00:00:00 2001 From: targrub Date: Mon, 26 Sep 2022 19:16:12 -0400 Subject: [PATCH 01/12] Made fields of ExtractedWindow private. Added ExtractedWindow::swap_chain_texture() accessor. --- crates/bevy_render/src/camera/camera.rs | 2 +- .../src/camera/camera_driver_node.rs | 2 +- crates/bevy_render/src/renderer/mod.rs | 4 +-- crates/bevy_render/src/view/window.rs | 29 ++++++++++++------- 4 files changed, 23 insertions(+), 14 deletions(-) diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index ed3373b12a20d..3b2a4323a16fc 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -272,7 +272,7 @@ impl RenderTarget { match self { RenderTarget::Window(window_id) => windows .get(window_id) - .and_then(|window| window.swap_chain_texture.as_ref()), + .and_then(|window| window.swap_chain_texture()), RenderTarget::Image(image_handle) => { images.get(image_handle).map(|image| &image.texture_view) } diff --git a/crates/bevy_render/src/camera/camera_driver_node.rs b/crates/bevy_render/src/camera/camera_driver_node.rs index 9cf23c106f3f3..24f0b3c55b877 100644 --- a/crates/bevy_render/src/camera/camera_driver_node.rs +++ b/crates/bevy_render/src/camera/camera_driver_node.rs @@ -78,7 +78,7 @@ impl Node for CameraDriverNode { continue; } - let swap_chain_texture = if let Some(swap_chain_texture) = &window.swap_chain_texture { + let swap_chain_texture = if let Some(swap_chain_texture) = window.swap_chain_texture() { swap_chain_texture } else { continue; diff --git a/crates/bevy_render/src/renderer/mod.rs b/crates/bevy_render/src/renderer/mod.rs index 5b175a1464406..3e8f28e5299d5 100644 --- a/crates/bevy_render/src/renderer/mod.rs +++ b/crates/bevy_render/src/renderer/mod.rs @@ -62,8 +62,8 @@ pub fn render_system(world: &mut World) { let mut windows = world.resource_mut::(); for window in windows.values_mut() { - if let Some(texture_view) = window.swap_chain_texture.take() { - if let Some(surface_texture) = texture_view.take_surface_texture() { + if let Some(texture_view) = window.swap_chain_texture() { + if let Some(surface_texture) = texture_view.clone().take_surface_texture() { surface_texture.present(); } } diff --git a/crates/bevy_render/src/view/window.rs b/crates/bevy_render/src/view/window.rs index 6fe1bc8627fda..61afe72f03503 100644 --- a/crates/bevy_render/src/view/window.rs +++ b/crates/bevy_render/src/view/window.rs @@ -39,14 +39,23 @@ impl Plugin for WindowRenderPlugin { } pub struct ExtractedWindow { - pub id: WindowId, - pub handle: RawWindowHandleWrapper, - pub physical_width: u32, - pub physical_height: u32, - pub present_mode: PresentMode, - pub swap_chain_texture: Option, - pub size_changed: bool, - pub present_mode_changed: bool, + id: WindowId, + raw_window_handle: RawWindowHandleWrapper, + physical_width: u32, + physical_height: u32, + present_mode: PresentMode, + swap_chain_texture: Option, + size_changed: bool, + present_mode_changed: bool, +} + +impl ExtractedWindow { + pub fn swap_chain_texture(&self) -> Option<&TextureView> { + match self.swap_chain_texture { + None => None, + _ => self.swap_chain_texture.as_ref(), + } + } } #[derive(Default, Resource)] @@ -85,7 +94,7 @@ fn extract_windows( .entry(window.id()) .or_insert(ExtractedWindow { id: window.id(), - handle: window.raw_window_handle(), + raw_window_handle: window.raw_window_handle(), physical_width: new_width, physical_height: new_height, present_mode: window.present_mode(), @@ -169,7 +178,7 @@ pub fn prepare_windows( .entry(window.id) .or_insert_with(|| unsafe { // NOTE: On some OSes this MUST be called from the main thread. - render_instance.create_surface(&window.handle.get_handle()) + render_instance.create_surface(&window.raw_window_handle.get_handle()) }); let swap_chain_descriptor = wgpu::SurfaceConfiguration { From a002a54b2395048ff31f52bd9cb439cc54602413 Mon Sep 17 00:00:00 2001 From: targrub Date: Mon, 26 Sep 2022 21:42:16 -0400 Subject: [PATCH 02/12] Made field swap_chain_texture public again. Removed its accessor method. --- crates/bevy_render/src/camera/camera.rs | 2 +- crates/bevy_render/src/camera/camera_driver_node.rs | 2 +- crates/bevy_render/src/renderer/mod.rs | 4 ++-- crates/bevy_render/src/view/window.rs | 11 +---------- 4 files changed, 5 insertions(+), 14 deletions(-) diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index 3b2a4323a16fc..ed3373b12a20d 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -272,7 +272,7 @@ impl RenderTarget { match self { RenderTarget::Window(window_id) => windows .get(window_id) - .and_then(|window| window.swap_chain_texture()), + .and_then(|window| window.swap_chain_texture.as_ref()), RenderTarget::Image(image_handle) => { images.get(image_handle).map(|image| &image.texture_view) } diff --git a/crates/bevy_render/src/camera/camera_driver_node.rs b/crates/bevy_render/src/camera/camera_driver_node.rs index 24f0b3c55b877..9cf23c106f3f3 100644 --- a/crates/bevy_render/src/camera/camera_driver_node.rs +++ b/crates/bevy_render/src/camera/camera_driver_node.rs @@ -78,7 +78,7 @@ impl Node for CameraDriverNode { continue; } - let swap_chain_texture = if let Some(swap_chain_texture) = window.swap_chain_texture() { + let swap_chain_texture = if let Some(swap_chain_texture) = &window.swap_chain_texture { swap_chain_texture } else { continue; diff --git a/crates/bevy_render/src/renderer/mod.rs b/crates/bevy_render/src/renderer/mod.rs index 3e8f28e5299d5..5b175a1464406 100644 --- a/crates/bevy_render/src/renderer/mod.rs +++ b/crates/bevy_render/src/renderer/mod.rs @@ -62,8 +62,8 @@ pub fn render_system(world: &mut World) { let mut windows = world.resource_mut::(); for window in windows.values_mut() { - if let Some(texture_view) = window.swap_chain_texture() { - if let Some(surface_texture) = texture_view.clone().take_surface_texture() { + if let Some(texture_view) = window.swap_chain_texture.take() { + if let Some(surface_texture) = texture_view.take_surface_texture() { surface_texture.present(); } } diff --git a/crates/bevy_render/src/view/window.rs b/crates/bevy_render/src/view/window.rs index 61afe72f03503..33637c4aff657 100644 --- a/crates/bevy_render/src/view/window.rs +++ b/crates/bevy_render/src/view/window.rs @@ -44,20 +44,11 @@ pub struct ExtractedWindow { physical_width: u32, physical_height: u32, present_mode: PresentMode, - swap_chain_texture: Option, + pub swap_chain_texture: Option, size_changed: bool, present_mode_changed: bool, } -impl ExtractedWindow { - pub fn swap_chain_texture(&self) -> Option<&TextureView> { - match self.swap_chain_texture { - None => None, - _ => self.swap_chain_texture.as_ref(), - } - } -} - #[derive(Default, Resource)] pub struct ExtractedWindows { pub windows: HashMap, From 6f3ed16810a128f7b50647ad47077c115b9544b5 Mon Sep 17 00:00:00 2001 From: targrub Date: Tue, 27 Sep 2022 13:35:21 -0400 Subject: [PATCH 03/12] Made raw_window_handle field in Window and ExtractedWindow an Option. Window::raw_window_handle() now returns Option. --- crates/bevy_render/src/lib.rs | 4 +- crates/bevy_render/src/view/window.rs | 83 ++++++++++++++------------ crates/bevy_window/src/window.rs | 10 ++-- crates/bevy_winit/src/winit_windows.rs | 2 +- 4 files changed, 52 insertions(+), 47 deletions(-) diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 1c0090bb6bb46..5ae5952f5a1f6 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -140,11 +140,11 @@ impl Plugin for RenderPlugin { .register_type::(); if let Some(backends) = options.backends { + let windows = app.world.resource_mut::(); let instance = wgpu::Instance::new(backends); let surface = { - let windows = app.world.resource_mut::(); let raw_handle = windows.get_primary().map(|window| unsafe { - let handle = window.raw_window_handle().get_handle(); + let handle = window.raw_window_handle().unwrap().get_handle(); instance.create_surface(&handle) }); raw_handle diff --git a/crates/bevy_render/src/view/window.rs b/crates/bevy_render/src/view/window.rs index 33637c4aff657..4a7a1f320cd17 100644 --- a/crates/bevy_render/src/view/window.rs +++ b/crates/bevy_render/src/view/window.rs @@ -40,7 +40,7 @@ impl Plugin for WindowRenderPlugin { pub struct ExtractedWindow { id: WindowId, - raw_window_handle: RawWindowHandleWrapper, + raw_window_handle: Option, physical_width: u32, physical_height: u32, present_mode: PresentMode, @@ -164,48 +164,53 @@ pub fn prepare_windows( ) { let window_surfaces = window_surfaces.deref_mut(); for window in windows.windows.values_mut() { - let surface = window_surfaces - .surfaces - .entry(window.id) - .or_insert_with(|| unsafe { - // NOTE: On some OSes this MUST be called from the main thread. - render_instance.create_surface(&window.raw_window_handle.get_handle()) - }); - - let swap_chain_descriptor = wgpu::SurfaceConfiguration { - format: TextureFormat::bevy_default(), - width: window.physical_width, - height: window.physical_height, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - present_mode: match window.present_mode { - PresentMode::Fifo => wgpu::PresentMode::Fifo, - PresentMode::Mailbox => wgpu::PresentMode::Mailbox, - PresentMode::Immediate => wgpu::PresentMode::Immediate, - PresentMode::AutoVsync => wgpu::PresentMode::AutoVsync, - PresentMode::AutoNoVsync => wgpu::PresentMode::AutoNoVsync, - }, - }; - - // Do the initial surface configuration if it hasn't been configured yet. Or if size or - // present mode changed. - if window_surfaces.configured_windows.insert(window.id) - || window.size_changed - || window.present_mode_changed - { - render_device.configure_surface(surface, &swap_chain_descriptor); + if window.raw_window_handle.is_none() { + continue; } + if let Some(raw_window_handle) = window.raw_window_handle.as_ref() { + let surface = window_surfaces + .surfaces + .entry(window.id) + .or_insert_with(|| unsafe { + // NOTE: On some OSes this MUST be called from the main thread. + render_instance.create_surface(&raw_window_handle.get_handle()) + }); - let frame = match surface.get_current_texture() { - Ok(swap_chain_frame) => swap_chain_frame, - Err(wgpu::SurfaceError::Outdated) => { + let swap_chain_descriptor = wgpu::SurfaceConfiguration { + format: TextureFormat::bevy_default(), + width: window.physical_width, + height: window.physical_height, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + present_mode: match window.present_mode { + PresentMode::Fifo => wgpu::PresentMode::Fifo, + PresentMode::Mailbox => wgpu::PresentMode::Mailbox, + PresentMode::Immediate => wgpu::PresentMode::Immediate, + PresentMode::AutoVsync => wgpu::PresentMode::AutoVsync, + PresentMode::AutoNoVsync => wgpu::PresentMode::AutoNoVsync, + }, + }; + + // Do the initial surface configuration if it hasn't been configured yet. Or if size or + // present mode changed. + if window_surfaces.configured_windows.insert(window.id) + || window.size_changed + || window.present_mode_changed + { render_device.configure_surface(surface, &swap_chain_descriptor); - surface - .get_current_texture() - .expect("Error reconfiguring surface") } - err => err.expect("Failed to acquire next swap chain texture!"), - }; - window.swap_chain_texture = Some(TextureView::from(frame)); + let frame = match surface.get_current_texture() { + Ok(swap_chain_frame) => swap_chain_frame, + Err(wgpu::SurfaceError::Outdated) => { + render_device.configure_surface(surface, &swap_chain_descriptor); + surface + .get_current_texture() + .expect("Error reconfiguring surface") + } + err => err.expect("Failed to acquire next swap chain texture!"), + }; + + window.swap_chain_texture = Some(TextureView::from(frame)); + } } } diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index 127eb4ca5a2ca..d1f8ada4ad95b 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -206,7 +206,7 @@ pub struct Window { cursor_visible: bool, cursor_locked: bool, physical_cursor_position: Option, - raw_window_handle: RawWindowHandleWrapper, + raw_window_handle: Option, focused: bool, mode: WindowMode, canvas: Option, @@ -315,7 +315,7 @@ impl Window { physical_height: u32, scale_factor: f64, position: Option, - raw_window_handle: RawWindowHandle, + raw_window_handle: Option, ) -> Self { Window { id, @@ -335,7 +335,7 @@ impl Window { cursor_locked: window_descriptor.cursor_locked, cursor_icon: CursorIcon::Default, physical_cursor_position: None, - raw_window_handle: RawWindowHandleWrapper::new(raw_window_handle), + raw_window_handle: raw_window_handle.map(RawWindowHandleWrapper::new), focused: true, mode: window_descriptor.mode, canvas: window_descriptor.canvas.clone(), @@ -720,8 +720,8 @@ impl Window { self.focused } /// Get the [`RawWindowHandleWrapper`] corresponding to this window - pub fn raw_window_handle(&self) -> RawWindowHandleWrapper { - self.raw_window_handle.clone() + pub fn raw_window_handle(&self) -> Option { + self.raw_window_handle.as_ref().cloned() } /// The "html canvas" element selector. diff --git a/crates/bevy_winit/src/winit_windows.rs b/crates/bevy_winit/src/winit_windows.rs index 4467574874c61..47423957cfbc5 100644 --- a/crates/bevy_winit/src/winit_windows.rs +++ b/crates/bevy_winit/src/winit_windows.rs @@ -201,7 +201,7 @@ impl WinitWindows { inner_size.height, scale_factor, position, - raw_window_handle, + Some(raw_window_handle), ) } From 1a8c812cb6a35fd0094fe66d0de21bd559adba68 Mon Sep 17 00:00:00 2001 From: targrub Date: Tue, 27 Sep 2022 14:11:27 -0400 Subject: [PATCH 04/12] Don't create `Surface` if don't have primary window or raw window handle. --- crates/bevy_render/src/lib.rs | 350 +++++++++++++++++----------------- 1 file changed, 178 insertions(+), 172 deletions(-) diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 5ae5952f5a1f6..35fed2a2ccd28 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -142,179 +142,185 @@ impl Plugin for RenderPlugin { if let Some(backends) = options.backends { let windows = app.world.resource_mut::(); let instance = wgpu::Instance::new(backends); - let surface = { - let raw_handle = windows.get_primary().map(|window| unsafe { - let handle = window.raw_window_handle().unwrap().get_handle(); - instance.create_surface(&handle) - }); - raw_handle - }; - let request_adapter_options = wgpu::RequestAdapterOptions { - power_preference: options.power_preference, - compatible_surface: surface.as_ref(), - ..Default::default() - }; - let (device, queue, adapter_info) = futures_lite::future::block_on( - renderer::initialize_renderer(&instance, &options, &request_adapter_options), - ); - debug!("Configured wgpu adapter Limits: {:#?}", device.limits()); - debug!("Configured wgpu adapter Features: {:#?}", device.features()); - app.insert_resource(device.clone()) - .insert_resource(queue.clone()) - .insert_resource(adapter_info.clone()) - .init_resource::() - .register_type::() - .register_type::(); - - let pipeline_cache = PipelineCache::new(device.clone()); - let asset_server = app.world.resource::().clone(); - - let mut render_app = App::empty(); - let mut extract_stage = - SystemStage::parallel().with_system(PipelineCache::extract_shaders); - // Get the ComponentId for MainWorld. This does technically 'waste' a `WorldId`, but that's probably fine - render_app.init_resource::(); - render_app.world.remove_resource::(); - let main_world_in_render = render_app - .world - .components() - .get_resource_id(TypeId::of::()); - // `Extract` systems must read from the main world. We want to emit an error when that doesn't occur - // Safe to unwrap: Ensured it existed just above - extract_stage.set_must_read_resource(main_world_in_render.unwrap()); - // don't apply buffers when the stage finishes running - // extract stage runs on the render world, but buffers are applied - // after access to the main world is removed - // See also https://github.com/bevyengine/bevy/issues/5082 - extract_stage.set_apply_buffers(false); - render_app - .add_stage(RenderStage::Extract, extract_stage) - .add_stage(RenderStage::Prepare, SystemStage::parallel()) - .add_stage(RenderStage::Queue, SystemStage::parallel()) - .add_stage(RenderStage::PhaseSort, SystemStage::parallel()) - .add_stage( - RenderStage::Render, - SystemStage::parallel() - .with_system(PipelineCache::process_pipeline_queue_system) - .with_system(render_system.at_end()), - ) - .add_stage(RenderStage::Cleanup, SystemStage::parallel()) - .init_resource::() - .insert_resource(RenderInstance(instance)) - .insert_resource(device) - .insert_resource(queue) - .insert_resource(adapter_info) - .insert_resource(pipeline_cache) - .insert_resource(asset_server); - - let (sender, receiver) = bevy_time::create_time_channels(); - app.insert_resource(receiver); - render_app.insert_resource(sender); - - app.add_sub_app(RenderApp, render_app, move |app_world, render_app| { - #[cfg(feature = "trace")] - let _render_span = bevy_utils::tracing::info_span!("renderer subapp").entered(); - { - #[cfg(feature = "trace")] - let _stage_span = - bevy_utils::tracing::info_span!("stage", name = "reserve_and_flush") - .entered(); - - // reserve all existing app entities for use in render_app - // they can only be spawned using `get_or_spawn()` - let meta_len = app_world.entities().meta_len(); - render_app - .world - .entities() - .reserve_entities(meta_len as u32); - - // flushing as "invalid" ensures that app world entities aren't added as "empty archetype" entities by default - // these entities cannot be accessed without spawning directly onto them - // this _only_ works as expected because clear_entities() is called at the end of every frame. - unsafe { render_app.world.entities_mut() }.flush_as_invalid(); - } - - { - #[cfg(feature = "trace")] - let _stage_span = - bevy_utils::tracing::info_span!("stage", name = "extract").entered(); - - // extract - extract(app_world, render_app); - } - - { - #[cfg(feature = "trace")] - let _stage_span = - bevy_utils::tracing::info_span!("stage", name = "prepare").entered(); - - // prepare - let prepare = render_app - .schedule - .get_stage_mut::(RenderStage::Prepare) - .unwrap(); - prepare.run(&mut render_app.world); - } - - { + if let Some(window) = windows.get_primary() { + let surface = { + if let Some(raw_window_handle) = window.raw_window_handle() { + unsafe { + let handle = raw_window_handle.get_handle(); + Some(instance.create_surface(&handle)) + } + } else { + None + } + }; + let request_adapter_options = wgpu::RequestAdapterOptions { + power_preference: options.power_preference, + compatible_surface: surface.as_ref(), + ..Default::default() + }; + let (device, queue, adapter_info) = futures_lite::future::block_on( + renderer::initialize_renderer(&instance, &options, &request_adapter_options), + ); + debug!("Configured wgpu adapter Limits: {:#?}", device.limits()); + debug!("Configured wgpu adapter Features: {:#?}", device.features()); + app.insert_resource(device.clone()) + .insert_resource(queue.clone()) + .insert_resource(adapter_info.clone()) + .init_resource::() + .register_type::() + .register_type::(); + + let pipeline_cache = PipelineCache::new(device.clone()); + let asset_server = app.world.resource::().clone(); + + let mut render_app = App::empty(); + let mut extract_stage = + SystemStage::parallel().with_system(PipelineCache::extract_shaders); + // Get the ComponentId for MainWorld. This does technically 'waste' a `WorldId`, but that's probably fine + render_app.init_resource::(); + render_app.world.remove_resource::(); + let main_world_in_render = render_app + .world + .components() + .get_resource_id(TypeId::of::()); + // `Extract` systems must read from the main world. We want to emit an error when that doesn't occur + // Safe to unwrap: Ensured it existed just above + extract_stage.set_must_read_resource(main_world_in_render.unwrap()); + // don't apply buffers when the stage finishes running + // extract stage runs on the render world, but buffers are applied + // after access to the main world is removed + // See also https://github.com/bevyengine/bevy/issues/5082 + extract_stage.set_apply_buffers(false); + render_app + .add_stage(RenderStage::Extract, extract_stage) + .add_stage(RenderStage::Prepare, SystemStage::parallel()) + .add_stage(RenderStage::Queue, SystemStage::parallel()) + .add_stage(RenderStage::PhaseSort, SystemStage::parallel()) + .add_stage( + RenderStage::Render, + SystemStage::parallel() + .with_system(PipelineCache::process_pipeline_queue_system) + .with_system(render_system.at_end()), + ) + .add_stage(RenderStage::Cleanup, SystemStage::parallel()) + .init_resource::() + .insert_resource(RenderInstance(instance)) + .insert_resource(device) + .insert_resource(queue) + .insert_resource(adapter_info) + .insert_resource(pipeline_cache) + .insert_resource(asset_server); + + let (sender, receiver) = bevy_time::create_time_channels(); + app.insert_resource(receiver); + render_app.insert_resource(sender); + + app.add_sub_app(RenderApp, render_app, move |app_world, render_app| { #[cfg(feature = "trace")] - let _stage_span = - bevy_utils::tracing::info_span!("stage", name = "queue").entered(); - - // queue - let queue = render_app - .schedule - .get_stage_mut::(RenderStage::Queue) - .unwrap(); - queue.run(&mut render_app.world); - } - - { - #[cfg(feature = "trace")] - let _stage_span = - bevy_utils::tracing::info_span!("stage", name = "sort").entered(); - - // phase sort - let phase_sort = render_app - .schedule - .get_stage_mut::(RenderStage::PhaseSort) - .unwrap(); - phase_sort.run(&mut render_app.world); - } - - { - #[cfg(feature = "trace")] - let _stage_span = - bevy_utils::tracing::info_span!("stage", name = "render").entered(); - - // render - let render = render_app - .schedule - .get_stage_mut::(RenderStage::Render) - .unwrap(); - render.run(&mut render_app.world); - } - - { - #[cfg(feature = "trace")] - let _stage_span = - bevy_utils::tracing::info_span!("stage", name = "cleanup").entered(); - - // cleanup - let cleanup = render_app - .schedule - .get_stage_mut::(RenderStage::Cleanup) - .unwrap(); - cleanup.run(&mut render_app.world); - } - { - #[cfg(feature = "trace")] - let _stage_span = - bevy_utils::tracing::info_span!("stage", name = "clear_entities").entered(); - - render_app.world.clear_entities(); - } - }); + let _render_span = bevy_utils::tracing::info_span!("renderer subapp").entered(); + { + #[cfg(feature = "trace")] + let _stage_span = + bevy_utils::tracing::info_span!("stage", name = "reserve_and_flush") + .entered(); + + // reserve all existing app entities for use in render_app + // they can only be spawned using `get_or_spawn()` + let meta_len = app_world.entities().meta_len(); + render_app + .world + .entities() + .reserve_entities(meta_len as u32); + + // flushing as "invalid" ensures that app world entities aren't added as "empty archetype" entities by default + // these entities cannot be accessed without spawning directly onto them + // this _only_ works as expected because clear_entities() is called at the end of every frame. + unsafe { render_app.world.entities_mut() }.flush_as_invalid(); + } + + { + #[cfg(feature = "trace")] + let _stage_span = + bevy_utils::tracing::info_span!("stage", name = "extract").entered(); + + // extract + extract(app_world, render_app); + } + + { + #[cfg(feature = "trace")] + let _stage_span = + bevy_utils::tracing::info_span!("stage", name = "prepare").entered(); + + // prepare + let prepare = render_app + .schedule + .get_stage_mut::(RenderStage::Prepare) + .unwrap(); + prepare.run(&mut render_app.world); + } + + { + #[cfg(feature = "trace")] + let _stage_span = + bevy_utils::tracing::info_span!("stage", name = "queue").entered(); + + // queue + let queue = render_app + .schedule + .get_stage_mut::(RenderStage::Queue) + .unwrap(); + queue.run(&mut render_app.world); + } + + { + #[cfg(feature = "trace")] + let _stage_span = + bevy_utils::tracing::info_span!("stage", name = "sort").entered(); + + // phase sort + let phase_sort = render_app + .schedule + .get_stage_mut::(RenderStage::PhaseSort) + .unwrap(); + phase_sort.run(&mut render_app.world); + } + + { + #[cfg(feature = "trace")] + let _stage_span = + bevy_utils::tracing::info_span!("stage", name = "render").entered(); + + // render + let render = render_app + .schedule + .get_stage_mut::(RenderStage::Render) + .unwrap(); + render.run(&mut render_app.world); + } + + { + #[cfg(feature = "trace")] + let _stage_span = + bevy_utils::tracing::info_span!("stage", name = "cleanup").entered(); + + // cleanup + let cleanup = render_app + .schedule + .get_stage_mut::(RenderStage::Cleanup) + .unwrap(); + cleanup.run(&mut render_app.world); + } + { + #[cfg(feature = "trace")] + let _stage_span = + bevy_utils::tracing::info_span!("stage", name = "clear_entities") + .entered(); + + render_app.world.clear_entities(); + } + }); + } } app.add_plugin(ValidParentCheckPlugin::::default()) From 2cccd038dfb900dfac86a13e1bd29bd82d90b2ae Mon Sep 17 00:00:00 2001 From: targrub Date: Tue, 27 Sep 2022 14:47:50 -0400 Subject: [PATCH 05/12] Better doc for Window::raw_window_handle(). --- crates/bevy_window/src/window.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index d1f8ada4ad95b..7b9cc1510b9a9 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -719,7 +719,9 @@ impl Window { pub fn is_focused(&self) -> bool { self.focused } - /// Get the [`RawWindowHandleWrapper`] corresponding to this window + /// Get the [`RawWindowHandleWrapper`] corresponding to this window if set. + /// + /// During normal use, this can be safely unwrapped; the value should only be [`None`] when synthetically constructed for tests. pub fn raw_window_handle(&self) -> Option { self.raw_window_handle.as_ref().cloned() } From fc3493b701a45d56a32481cf551b70986d9242da Mon Sep 17 00:00:00 2001 From: targrub Date: Wed, 28 Sep 2022 12:42:29 -0400 Subject: [PATCH 06/12] Clearer filtering of None values --- crates/bevy_render/src/view/window.rs | 90 +++++++++++++-------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/crates/bevy_render/src/view/window.rs b/crates/bevy_render/src/view/window.rs index 4a7a1f320cd17..70de9e873c5cc 100644 --- a/crates/bevy_render/src/view/window.rs +++ b/crates/bevy_render/src/view/window.rs @@ -162,55 +162,55 @@ pub fn prepare_windows( render_device: Res, render_instance: Res, ) { - let window_surfaces = window_surfaces.deref_mut(); - for window in windows.windows.values_mut() { - if window.raw_window_handle.is_none() { - continue; + for window in windows + .windows + .values_mut() + .filter(|x| x.raw_window_handle.is_some()) + { + let window_surfaces = window_surfaces.deref_mut(); + let surface = window_surfaces + .surfaces + .entry(window.id) + .or_insert_with(|| unsafe { + // NOTE: On some OSes this MUST be called from the main thread. + render_instance + .create_surface(&window.raw_window_handle.as_ref().unwrap().get_handle()) + }); + + let swap_chain_descriptor = wgpu::SurfaceConfiguration { + format: TextureFormat::bevy_default(), + width: window.physical_width, + height: window.physical_height, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + present_mode: match window.present_mode { + PresentMode::Fifo => wgpu::PresentMode::Fifo, + PresentMode::Mailbox => wgpu::PresentMode::Mailbox, + PresentMode::Immediate => wgpu::PresentMode::Immediate, + PresentMode::AutoVsync => wgpu::PresentMode::AutoVsync, + PresentMode::AutoNoVsync => wgpu::PresentMode::AutoNoVsync, + }, + }; + + // Do the initial surface configuration if it hasn't been configured yet. Or if size or + // present mode changed. + if window_surfaces.configured_windows.insert(window.id) + || window.size_changed + || window.present_mode_changed + { + render_device.configure_surface(surface, &swap_chain_descriptor); } - if let Some(raw_window_handle) = window.raw_window_handle.as_ref() { - let surface = window_surfaces - .surfaces - .entry(window.id) - .or_insert_with(|| unsafe { - // NOTE: On some OSes this MUST be called from the main thread. - render_instance.create_surface(&raw_window_handle.get_handle()) - }); - let swap_chain_descriptor = wgpu::SurfaceConfiguration { - format: TextureFormat::bevy_default(), - width: window.physical_width, - height: window.physical_height, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - present_mode: match window.present_mode { - PresentMode::Fifo => wgpu::PresentMode::Fifo, - PresentMode::Mailbox => wgpu::PresentMode::Mailbox, - PresentMode::Immediate => wgpu::PresentMode::Immediate, - PresentMode::AutoVsync => wgpu::PresentMode::AutoVsync, - PresentMode::AutoNoVsync => wgpu::PresentMode::AutoNoVsync, - }, - }; - - // Do the initial surface configuration if it hasn't been configured yet. Or if size or - // present mode changed. - if window_surfaces.configured_windows.insert(window.id) - || window.size_changed - || window.present_mode_changed - { + let frame = match surface.get_current_texture() { + Ok(swap_chain_frame) => swap_chain_frame, + Err(wgpu::SurfaceError::Outdated) => { render_device.configure_surface(surface, &swap_chain_descriptor); + surface + .get_current_texture() + .expect("Error reconfiguring surface") } + err => err.expect("Failed to acquire next swap chain texture!"), + }; - let frame = match surface.get_current_texture() { - Ok(swap_chain_frame) => swap_chain_frame, - Err(wgpu::SurfaceError::Outdated) => { - render_device.configure_surface(surface, &swap_chain_descriptor); - surface - .get_current_texture() - .expect("Error reconfiguring surface") - } - err => err.expect("Failed to acquire next swap chain texture!"), - }; - - window.swap_chain_texture = Some(TextureView::from(frame)); - } + window.swap_chain_texture = Some(TextureView::from(frame)); } } From fa61e2d0c46a07183702d8c4a1283730bee4b756 Mon Sep 17 00:00:00 2001 From: targrub Date: Wed, 28 Sep 2022 12:44:08 -0400 Subject: [PATCH 07/12] Missed comment --- crates/bevy_render/src/view/window.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_render/src/view/window.rs b/crates/bevy_render/src/view/window.rs index 70de9e873c5cc..b17e72b9bfc53 100644 --- a/crates/bevy_render/src/view/window.rs +++ b/crates/bevy_render/src/view/window.rs @@ -165,7 +165,7 @@ pub fn prepare_windows( for window in windows .windows .values_mut() - .filter(|x| x.raw_window_handle.is_some()) + .filter(|x| x.raw_window_handle.is_some()) // only None if synthetic test { let window_surfaces = window_surfaces.deref_mut(); let surface = window_surfaces From 6760e2a7e2aec122b81f1488d0d2570ba320d090 Mon Sep 17 00:00:00 2001 From: targrub Date: Wed, 28 Sep 2022 12:49:39 -0400 Subject: [PATCH 08/12] Moved and revised comment --- crates/bevy_render/src/view/window.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/bevy_render/src/view/window.rs b/crates/bevy_render/src/view/window.rs index b17e72b9bfc53..14dee19ba0858 100644 --- a/crates/bevy_render/src/view/window.rs +++ b/crates/bevy_render/src/view/window.rs @@ -165,7 +165,8 @@ pub fn prepare_windows( for window in windows .windows .values_mut() - .filter(|x| x.raw_window_handle.is_some()) // only None if synthetic test + // value of raw_winndow_handle only None if synthetic test + .filter(|x| x.raw_window_handle.is_some()) { let window_surfaces = window_surfaces.deref_mut(); let surface = window_surfaces From bfa85b3ed72467dd5b23a50680ef5f839b27241b Mon Sep 17 00:00:00 2001 From: targrub Date: Wed, 28 Sep 2022 15:12:18 -0400 Subject: [PATCH 09/12] Make all fields of ExtractedWindow public. --- crates/bevy_render/src/view/window.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/bevy_render/src/view/window.rs b/crates/bevy_render/src/view/window.rs index 14dee19ba0858..dad20aae94b07 100644 --- a/crates/bevy_render/src/view/window.rs +++ b/crates/bevy_render/src/view/window.rs @@ -39,14 +39,14 @@ impl Plugin for WindowRenderPlugin { } pub struct ExtractedWindow { - id: WindowId, - raw_window_handle: Option, - physical_width: u32, - physical_height: u32, - present_mode: PresentMode, + pub id: WindowId, + pub raw_window_handle: Option, + pub physical_width: u32, + pub physical_height: u32, + pub present_mode: PresentMode, pub swap_chain_texture: Option, - size_changed: bool, - present_mode_changed: bool, + pub size_changed: bool, + pub present_mode_changed: bool, } #[derive(Default, Resource)] From cac7b4eae980334cd65295ca3f29becf289d8758 Mon Sep 17 00:00:00 2001 From: targrub Date: Wed, 28 Sep 2022 21:15:17 -0400 Subject: [PATCH 10/12] Added doctest code to show how to test code that uses Window. Also added derive for PartialEq trait on WindowCommand. --- crates/bevy_window/src/window.rs | 55 +++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index 7b9cc1510b9a9..d45321f2f884f 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -187,6 +187,59 @@ impl WindowResizeConstraints { /// } /// } /// ``` +/// To test code that uses `Window`s, one can test it with varying `Window` parameters by +/// creating `WindowResizeConstraints` or `WindowDescriptor` structures. +/// values by setting +/// +/// ```no_run +/// # use bevy_utils::default; +/// # use bevy_window::{Window, WindowCommand, WindowDescriptor, WindowId, WindowResizeConstraints}; +/// # fn compute_window_area(w: &Window) -> f32 { +/// # w.width() * w.height() +/// # } +/// # fn grow_window_to_text_size(_window: &mut Window, _text: &str) {} +/// # fn set_new_title(window: &mut Window, text: String) { window.set_title(text); } +/// # fn a_window_resize_test() { +/// let resize_constraints = WindowResizeConstraints { +/// min_width: 400.0, +/// min_height: 300.0, +/// max_width: 1280.0, +/// max_height: 1024.0, +/// }; +/// let window_descriptor = WindowDescriptor { +/// width: 800.0, +/// height: 600.0, +/// resizable: true, +/// resize_constraints, +/// ..default() +/// }; +/// let mut window = Window::new( +/// WindowId::new(), +/// &window_descriptor, +/// 100, // physical_width +/// 100, // physical_height +/// 1.0, // scale_factor +/// None, None); +/// +/// let area = compute_window_area(&window); +/// assert_eq!(area, 100.0 * 100.0); +/// +/// grow_window_to_text_size(&mut window, "very long text that does not wrap"); +/// assert_eq!(window.physical_width(), window.requested_width() as u32); +/// grow_window_to_text_size(&mut window, "very long text that does wrap, creating a maximum width window"); +/// assert_eq!(window.physical_width(), window.requested_width() as u32); +/// +/// set_new_title(&mut window, "new title".to_string()); +/// let mut found_command = false; +/// for command in window.drain_commands() { +/// if command == (WindowCommand::SetTitle{ title: "new title".to_string() }) { +/// found_command = true; +/// break; +/// } +/// } +/// assert_eq!(found_command, true); +/// } +/// ``` #[derive(Debug)] pub struct Window { id: WindowId, @@ -217,7 +270,7 @@ pub struct Window { /// /// Bevy apps don't interact with this `enum` directly. Instead, they should use the methods on [`Window`]. /// This `enum` is meant for authors of windowing plugins. See the documentation on [`crate::WindowPlugin`] for more information. -#[derive(Debug)] +#[derive(Debug, PartialEq)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub enum WindowCommand { /// Set the window's [`WindowMode`]. From 68daf7807e8c7cefad8553baa95ff22c81237141 Mon Sep 17 00:00:00 2001 From: targrub Date: Fri, 14 Oct 2022 14:41:35 -0400 Subject: [PATCH 11/12] Removed "norun" from documentation. --- crates/bevy_window/src/window.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index 013b22cd710c5..c0eae67f02973 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -191,7 +191,7 @@ impl WindowResizeConstraints { /// creating `WindowResizeConstraints` or `WindowDescriptor` structures. /// values by setting /// -/// ```no_run +/// ``` /// # use bevy_utils::default; /// # use bevy_window::{Window, WindowCommand, WindowDescriptor, WindowId, WindowResizeConstraints}; /// # fn compute_window_area(w: &Window) -> f32 { From 875da36e8691962ce9bc7cdff959ea574d55a7f5 Mon Sep 17 00:00:00 2001 From: targrub Date: Fri, 14 Oct 2022 14:43:32 -0400 Subject: [PATCH 12/12] Moved test for no window inside of surface creation block. --- crates/bevy_render/src/lib.rs | 365 +++++++++++++++++----------------- 1 file changed, 183 insertions(+), 182 deletions(-) diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 2261a96447e38..1d289fe13eab8 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -146,8 +146,8 @@ impl Plugin for RenderPlugin { if let Some(backends) = options.backends { let windows = app.world.resource_mut::(); let instance = wgpu::Instance::new(backends); - if let Some(window) = windows.get_primary() { - let surface = { + let surface = { + if let Some(window) = windows.get_primary() { if let Some(raw_window_handle) = window.raw_window_handle() { unsafe { let handle = raw_window_handle.get_handle(); @@ -156,190 +156,191 @@ impl Plugin for RenderPlugin { } else { None } - }; - let request_adapter_options = wgpu::RequestAdapterOptions { - power_preference: options.power_preference, - compatible_surface: surface.as_ref(), - ..Default::default() - }; - let (device, queue, adapter_info, render_adapter, available_texture_formats) = - futures_lite::future::block_on(renderer::initialize_renderer( - &instance, - &options, - &request_adapter_options, - )); - let texture_format = RenderTextureFormat( - available_texture_formats - .get(0) - .cloned() - .unwrap_or_else(TextureFormat::bevy_default), - ); - debug!("Configured wgpu adapter Limits: {:#?}", device.limits()); - debug!("Configured wgpu adapter Features: {:#?}", device.features()); - app.insert_resource(device.clone()) - .insert_resource(queue.clone()) - .insert_resource(adapter_info.clone()) - .insert_resource(render_adapter.clone()) - .insert_resource(available_texture_formats.clone()) - .insert_resource(texture_format.clone()) - .init_resource::() - .register_type::() - .register_type::(); - - let pipeline_cache = PipelineCache::new(device.clone()); - let asset_server = app.world.resource::().clone(); - - let mut render_app = App::empty(); - let mut extract_stage = - SystemStage::parallel().with_system(PipelineCache::extract_shaders); - // Get the ComponentId for MainWorld. This does technically 'waste' a `WorldId`, but that's probably fine - render_app.init_resource::(); - render_app.world.remove_resource::(); - let main_world_in_render = render_app - .world - .components() - .get_resource_id(TypeId::of::()); - // `Extract` systems must read from the main world. We want to emit an error when that doesn't occur - // Safe to unwrap: Ensured it existed just above - extract_stage.set_must_read_resource(main_world_in_render.unwrap()); - // don't apply buffers when the stage finishes running - // extract stage runs on the render world, but buffers are applied - // after access to the main world is removed - // See also https://github.com/bevyengine/bevy/issues/5082 - extract_stage.set_apply_buffers(false); - render_app - .add_stage(RenderStage::Extract, extract_stage) - .add_stage(RenderStage::Prepare, SystemStage::parallel()) - .add_stage(RenderStage::Queue, SystemStage::parallel()) - .add_stage(RenderStage::PhaseSort, SystemStage::parallel()) - .add_stage( - RenderStage::Render, - SystemStage::parallel() - .with_system(PipelineCache::process_pipeline_queue_system) - .with_system(render_system.at_end()), - ) - .add_stage(RenderStage::Cleanup, SystemStage::parallel()) - .init_resource::() - .insert_resource(RenderInstance(instance)) - .insert_resource(device) - .insert_resource(queue) - .insert_resource(render_adapter) - .insert_resource(available_texture_formats) - .insert_resource(texture_format) - .insert_resource(adapter_info) - .insert_resource(pipeline_cache) - .insert_resource(asset_server); - - let (sender, receiver) = bevy_time::create_time_channels(); - app.insert_resource(receiver); - render_app.insert_resource(sender); - - app.add_sub_app(RenderApp, render_app, move |app_world, render_app| { + } else { + None + } + }; + let request_adapter_options = wgpu::RequestAdapterOptions { + power_preference: options.power_preference, + compatible_surface: surface.as_ref(), + ..Default::default() + }; + let (device, queue, adapter_info, render_adapter, available_texture_formats) = + futures_lite::future::block_on(renderer::initialize_renderer( + &instance, + &options, + &request_adapter_options, + )); + let texture_format = RenderTextureFormat( + available_texture_formats + .get(0) + .cloned() + .unwrap_or_else(TextureFormat::bevy_default), + ); + debug!("Configured wgpu adapter Limits: {:#?}", device.limits()); + debug!("Configured wgpu adapter Features: {:#?}", device.features()); + app.insert_resource(device.clone()) + .insert_resource(queue.clone()) + .insert_resource(adapter_info.clone()) + .insert_resource(render_adapter.clone()) + .insert_resource(available_texture_formats.clone()) + .insert_resource(texture_format.clone()) + .init_resource::() + .register_type::() + .register_type::(); + + let pipeline_cache = PipelineCache::new(device.clone()); + let asset_server = app.world.resource::().clone(); + + let mut render_app = App::empty(); + let mut extract_stage = + SystemStage::parallel().with_system(PipelineCache::extract_shaders); + // Get the ComponentId for MainWorld. This does technically 'waste' a `WorldId`, but that's probably fine + render_app.init_resource::(); + render_app.world.remove_resource::(); + let main_world_in_render = render_app + .world + .components() + .get_resource_id(TypeId::of::()); + // `Extract` systems must read from the main world. We want to emit an error when that doesn't occur + // Safe to unwrap: Ensured it existed just above + extract_stage.set_must_read_resource(main_world_in_render.unwrap()); + // don't apply buffers when the stage finishes running + // extract stage runs on the render world, but buffers are applied + // after access to the main world is removed + // See also https://github.com/bevyengine/bevy/issues/5082 + extract_stage.set_apply_buffers(false); + render_app + .add_stage(RenderStage::Extract, extract_stage) + .add_stage(RenderStage::Prepare, SystemStage::parallel()) + .add_stage(RenderStage::Queue, SystemStage::parallel()) + .add_stage(RenderStage::PhaseSort, SystemStage::parallel()) + .add_stage( + RenderStage::Render, + SystemStage::parallel() + .with_system(PipelineCache::process_pipeline_queue_system) + .with_system(render_system.at_end()), + ) + .add_stage(RenderStage::Cleanup, SystemStage::parallel()) + .init_resource::() + .insert_resource(RenderInstance(instance)) + .insert_resource(device) + .insert_resource(queue) + .insert_resource(render_adapter) + .insert_resource(available_texture_formats) + .insert_resource(texture_format) + .insert_resource(adapter_info) + .insert_resource(pipeline_cache) + .insert_resource(asset_server); + + let (sender, receiver) = bevy_time::create_time_channels(); + app.insert_resource(receiver); + render_app.insert_resource(sender); + + app.add_sub_app(RenderApp, render_app, move |app_world, render_app| { + #[cfg(feature = "trace")] + let _render_span = bevy_utils::tracing::info_span!("renderer subapp").entered(); + { #[cfg(feature = "trace")] - let _render_span = bevy_utils::tracing::info_span!("renderer subapp").entered(); - { - #[cfg(feature = "trace")] - let _stage_span = - bevy_utils::tracing::info_span!("stage", name = "reserve_and_flush") - .entered(); - - // reserve all existing app entities for use in render_app - // they can only be spawned using `get_or_spawn()` - let meta_len = app_world.entities().meta_len(); - render_app - .world - .entities() - .reserve_entities(meta_len as u32); - - // flushing as "invalid" ensures that app world entities aren't added as "empty archetype" entities by default - // these entities cannot be accessed without spawning directly onto them - // this _only_ works as expected because clear_entities() is called at the end of every frame. - unsafe { render_app.world.entities_mut() }.flush_as_invalid(); - } - - { - #[cfg(feature = "trace")] - let _stage_span = - bevy_utils::tracing::info_span!("stage", name = "extract").entered(); - - // extract - extract(app_world, render_app); - } - - { - #[cfg(feature = "trace")] - let _stage_span = - bevy_utils::tracing::info_span!("stage", name = "prepare").entered(); - - // prepare - let prepare = render_app - .schedule - .get_stage_mut::(RenderStage::Prepare) - .unwrap(); - prepare.run(&mut render_app.world); - } - - { - #[cfg(feature = "trace")] - let _stage_span = - bevy_utils::tracing::info_span!("stage", name = "queue").entered(); - - // queue - let queue = render_app - .schedule - .get_stage_mut::(RenderStage::Queue) - .unwrap(); - queue.run(&mut render_app.world); - } - - { - #[cfg(feature = "trace")] - let _stage_span = - bevy_utils::tracing::info_span!("stage", name = "sort").entered(); - - // phase sort - let phase_sort = render_app - .schedule - .get_stage_mut::(RenderStage::PhaseSort) - .unwrap(); - phase_sort.run(&mut render_app.world); - } + let _stage_span = + bevy_utils::tracing::info_span!("stage", name = "reserve_and_flush") + .entered(); + + // reserve all existing app entities for use in render_app + // they can only be spawned using `get_or_spawn()` + let meta_len = app_world.entities().meta_len(); + render_app + .world + .entities() + .reserve_entities(meta_len as u32); + + // flushing as "invalid" ensures that app world entities aren't added as "empty archetype" entities by default + // these entities cannot be accessed without spawning directly onto them + // this _only_ works as expected because clear_entities() is called at the end of every frame. + unsafe { render_app.world.entities_mut() }.flush_as_invalid(); + } + + { + #[cfg(feature = "trace")] + let _stage_span = + bevy_utils::tracing::info_span!("stage", name = "extract").entered(); - { - #[cfg(feature = "trace")] - let _stage_span = - bevy_utils::tracing::info_span!("stage", name = "render").entered(); - - // render - let render = render_app - .schedule - .get_stage_mut::(RenderStage::Render) - .unwrap(); - render.run(&mut render_app.world); - } + // extract + extract(app_world, render_app); + } - { - #[cfg(feature = "trace")] - let _stage_span = - bevy_utils::tracing::info_span!("stage", name = "cleanup").entered(); - - // cleanup - let cleanup = render_app - .schedule - .get_stage_mut::(RenderStage::Cleanup) - .unwrap(); - cleanup.run(&mut render_app.world); - } - { - #[cfg(feature = "trace")] - let _stage_span = - bevy_utils::tracing::info_span!("stage", name = "clear_entities") - .entered(); + { + #[cfg(feature = "trace")] + let _stage_span = + bevy_utils::tracing::info_span!("stage", name = "prepare").entered(); + + // prepare + let prepare = render_app + .schedule + .get_stage_mut::(RenderStage::Prepare) + .unwrap(); + prepare.run(&mut render_app.world); + } + + { + #[cfg(feature = "trace")] + let _stage_span = + bevy_utils::tracing::info_span!("stage", name = "queue").entered(); + + // queue + let queue = render_app + .schedule + .get_stage_mut::(RenderStage::Queue) + .unwrap(); + queue.run(&mut render_app.world); + } + + { + #[cfg(feature = "trace")] + let _stage_span = + bevy_utils::tracing::info_span!("stage", name = "sort").entered(); + + // phase sort + let phase_sort = render_app + .schedule + .get_stage_mut::(RenderStage::PhaseSort) + .unwrap(); + phase_sort.run(&mut render_app.world); + } + + { + #[cfg(feature = "trace")] + let _stage_span = + bevy_utils::tracing::info_span!("stage", name = "render").entered(); + + // render + let render = render_app + .schedule + .get_stage_mut::(RenderStage::Render) + .unwrap(); + render.run(&mut render_app.world); + } + + { + #[cfg(feature = "trace")] + let _stage_span = + bevy_utils::tracing::info_span!("stage", name = "cleanup").entered(); + + // cleanup + let cleanup = render_app + .schedule + .get_stage_mut::(RenderStage::Cleanup) + .unwrap(); + cleanup.run(&mut render_app.world); + } + { + #[cfg(feature = "trace")] + let _stage_span = + bevy_utils::tracing::info_span!("stage", name = "clear_entities").entered(); - render_app.world.clear_entities(); - } - }); - } + render_app.world.clear_entities(); + } + }); } app.add_plugin(ValidParentCheckPlugin::::default())