From 9535541f2a4e5a10341492e96467f1905c5c0655 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Wed, 27 Sep 2023 02:39:55 +0200 Subject: [PATCH 1/4] android: handle window on suspend / resume --- crates/bevy_winit/src/lib.rs | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index a13c158709bfe..cd9a58539b3a3 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -37,6 +37,8 @@ use bevy_utils::{ tracing::{trace, warn}, Duration, Instant, }; +#[cfg(target_os = "android")] +use bevy_window::PrimaryWindow; use bevy_window::{ exit_on_all_closed, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, Ime, ReceivedCharacter, RequestRedraw, Window, WindowBackendScaleFactorChanged, @@ -300,6 +302,9 @@ pub fn winit_runner(mut app: App) { let mut runner_state = WinitAppRunnerState::default(); + #[cfg(target_os = "android")] + let mut primary_window = None; + // prepare structures to access data in the world let mut app_exit_event_reader = ManualEventReader::::default(); let mut redraw_event_reader = ManualEventReader::::default(); @@ -664,16 +669,22 @@ pub fn winit_runner(mut app: App) { runner_state.is_active = false; #[cfg(target_os = "android")] { - // Android sending this event invalidates all render surfaces. - // TODO - // Upon resume, check if the new render surfaces are compatible with the - // existing render device. If not (which should basically never happen), - // then try to rebuild the renderer. - *control_flow = ControlFlow::Exit; + let mut query = app.world.query::<(Entity, &Window)>(); + let (entity, window) = query.single(&app.world); + primary_window = Some(window.clone()); + app.world.despawn(entity); + *control_flow = ControlFlow::Wait; } } event::Event::Resumed => { runner_state.is_active = true; + #[cfg(target_os = "android")] + { + if let Some(window) = primary_window.take() { + app.world.spawn((window, PrimaryWindow)); + } + *control_flow = ControlFlow::Poll; + } } event::Event::MainEventsCleared => { if runner_state.is_active { From 523ea85c076be340116e4776e6f104fcaf470adb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Thu, 28 Sep 2023 00:47:56 +0200 Subject: [PATCH 2/4] documentation --- examples/mobile/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/mobile/src/lib.rs b/examples/mobile/src/lib.rs index e08e6804705cc..5d88ddf09ccd5 100644 --- a/examples/mobile/src/lib.rs +++ b/examples/mobile/src/lib.rs @@ -5,6 +5,9 @@ use bevy::{input::touch::TouchPhase, prelude::*, window::WindowMode}; fn main() { let mut app = App::new(); app.add_plugins(DefaultPlugins.set(WindowPlugin { + // On Android, the entity with this window will be despawned when the application is suspended + // and respawned when it is resumed. The entity will change each time, and any extra component + // will be lost. primary_window: Some(Window { resizable: false, mode: WindowMode::BorderlessFullscreen, From 0330fed1526ec3d34095585014a4f692ea185f10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Fri, 29 Sep 2023 23:33:41 +0200 Subject: [PATCH 3/4] keep Bevy window, only recreate Winit window and wgpu surface --- crates/bevy_render/src/view/window/mod.rs | 14 +++++++ crates/bevy_winit/src/lib.rs | 50 +++++++++++++++++++---- examples/mobile/src/lib.rs | 3 -- 3 files changed, 55 insertions(+), 12 deletions(-) diff --git a/crates/bevy_render/src/view/window/mod.rs b/crates/bevy_render/src/view/window/mod.rs index 88820f8566096..389a86b82451a 100644 --- a/crates/bevy_render/src/view/window/mod.rs +++ b/crates/bevy_render/src/view/window/mod.rs @@ -106,6 +106,8 @@ fn extract_windows( screenshot_manager: Extract>, mut closed: Extract>, windows: Extract)>>, + mut removed: Extract>, + mut window_surfaces: ResMut, ) { for (entity, window, handle, primary) in windows.iter() { if primary.is_some() { @@ -163,6 +165,11 @@ fn extract_windows( for closed_window in closed.read() { extracted_windows.remove(&closed_window.window); + window_surfaces.remove(&closed_window.window); + } + for removed_window in removed.read() { + extracted_windows.remove(&removed_window); + window_surfaces.remove(&removed_window); } // This lock will never block because `callbacks` is `pub(crate)` and this is the singular callsite where it's locked. // Even if a user had multiple copies of this system, since the system has a mutable resource access the two systems would never run @@ -187,6 +194,13 @@ pub struct WindowSurfaces { configured_windows: HashSet, } +impl WindowSurfaces { + fn remove(&mut self, window: &Entity) { + self.surfaces.remove(window); + self.configured_windows.remove(window); + } +} + /// Creates and (re)configures window surfaces, and obtains a swapchain texture for rendering. /// /// NOTE: `get_current_texture` in `prepare_windows` can take a long time if the GPU workload is diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index cd9a58539b3a3..3aebee4c95bb8 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -302,9 +302,6 @@ pub fn winit_runner(mut app: App) { let mut runner_state = WinitAppRunnerState::default(); - #[cfg(target_os = "android")] - let mut primary_window = None; - // prepare structures to access data in the world let mut app_exit_event_reader = ManualEventReader::::default(); let mut redraw_event_reader = ManualEventReader::::default(); @@ -669,10 +666,13 @@ pub fn winit_runner(mut app: App) { runner_state.is_active = false; #[cfg(target_os = "android")] { - let mut query = app.world.query::<(Entity, &Window)>(); - let (entity, window) = query.single(&app.world); - primary_window = Some(window.clone()); - app.world.despawn(entity); + // Remove the `RawHandleWrapper` from the primary window. + // This will trigger the surface destruction. + let mut query = app.world.query_filtered::>(); + let entity = query.single(&app.world); + app.world + .entity_mut(entity) + .remove::(); *control_flow = ControlFlow::Wait; } } @@ -680,8 +680,40 @@ pub fn winit_runner(mut app: App) { runner_state.is_active = true; #[cfg(target_os = "android")] { - if let Some(window) = primary_window.take() { - app.world.spawn((window, PrimaryWindow)); + // Get windows that are cached but without raw handles. Those window were already created, but got their + // handle wrapper removed when the app was suspended. + let mut query = app + .world + .query_filtered::<(Entity, &Window), (With, Without)>(); + if let Ok((entity, window)) = query.get_single(&app.world) { + use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; + let window = window.clone(); + + let ( + _, + _, + _, + mut winit_windows, + mut adapters, + mut handlers, + accessibility_requested, + ) = create_window_system_state.get_mut(&mut app.world); + + let winit_window = winit_windows.create_window( + event_loop, + entity, + &window, + &mut adapters, + &mut handlers, + &accessibility_requested, + ); + + let wrapper = bevy_window::RawHandleWrapper { + window_handle: winit_window.raw_window_handle(), + display_handle: winit_window.raw_display_handle(), + }; + + app.world.entity_mut(entity).insert(wrapper); } *control_flow = ControlFlow::Poll; } diff --git a/examples/mobile/src/lib.rs b/examples/mobile/src/lib.rs index 5d88ddf09ccd5..e08e6804705cc 100644 --- a/examples/mobile/src/lib.rs +++ b/examples/mobile/src/lib.rs @@ -5,9 +5,6 @@ use bevy::{input::touch::TouchPhase, prelude::*, window::WindowMode}; fn main() { let mut app = App::new(); app.add_plugins(DefaultPlugins.set(WindowPlugin { - // On Android, the entity with this window will be despawned when the application is suspended - // and respawned when it is resumed. The entity will change each time, and any extra component - // will be lost. primary_window: Some(Window { resizable: false, mode: WindowMode::BorderlessFullscreen, From 6ace6e8bfad81b3a28532aaaddfdeddb8c304875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Fri, 29 Sep 2023 23:38:52 +0200 Subject: [PATCH 4/4] formatting --- crates/bevy_winit/src/lib.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index 3aebee4c95bb8..21b694cdc2982 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -37,14 +37,14 @@ use bevy_utils::{ tracing::{trace, warn}, Duration, Instant, }; -#[cfg(target_os = "android")] -use bevy_window::PrimaryWindow; use bevy_window::{ exit_on_all_closed, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, Ime, ReceivedCharacter, RequestRedraw, Window, WindowBackendScaleFactorChanged, WindowCloseRequested, WindowCreated, WindowDestroyed, WindowFocused, WindowMoved, WindowResized, WindowScaleFactorChanged, WindowThemeChanged, }; +#[cfg(target_os = "android")] +use bevy_window::{PrimaryWindow, RawHandleWrapper}; #[cfg(target_os = "android")] pub use winit::platform::android::activity::AndroidApp; @@ -670,9 +670,7 @@ pub fn winit_runner(mut app: App) { // This will trigger the surface destruction. let mut query = app.world.query_filtered::>(); let entity = query.single(&app.world); - app.world - .entity_mut(entity) - .remove::(); + app.world.entity_mut(entity).remove::(); *control_flow = ControlFlow::Wait; } } @@ -708,7 +706,7 @@ pub fn winit_runner(mut app: App) { &accessibility_requested, ); - let wrapper = bevy_window::RawHandleWrapper { + let wrapper = RawHandleWrapper { window_handle: winit_window.raw_window_handle(), display_handle: winit_window.raw_display_handle(), };