From eb6be27e0284a93ec613cdc95dd6bc4aee724fdb Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Fri, 16 Jun 2023 19:43:54 +0200 Subject: [PATCH] examples: Implement proper `Event::Resumed` semantics On Android the backing buffer (`NativeWindow`) disappears when the application is not focussed and/or the screen is locked. Winit handles this by requiring apps to create their `raw_window_handle()` consumers _after_ `Event::Resumed` and to clean it up _before_ returning from `Event::Suspended`. For consistency Winit also sends `Resumed` on all other platforms during init. --- README.md | 28 ++++++----- examples/animation.rs | 28 +++++++---- examples/fruit.rs | 26 ++++++---- examples/rectangle.rs | 30 +++++++----- examples/utils/winit_app.rs | 71 ++++++++++++++++++++-------- examples/winit.rs | 21 ++++---- examples/winit_multithread.rs | 71 ++++++++++++++++------------ examples/winit_wrong_sized_buffer.rs | 22 +++++---- 8 files changed, 189 insertions(+), 108 deletions(-) diff --git a/README.md b/README.md index b19d4ac..3d0c632 100644 --- a/README.md +++ b/README.md @@ -75,21 +75,27 @@ mod winit_app; fn main() { let event_loop = EventLoop::new().unwrap(); - let mut app = winit_app::WinitAppBuilder::with_init(|elwt| { - let window = { - let window = elwt.create_window(Window::default_attributes()); - Rc::new(window.unwrap()) - }; - let context = softbuffer::Context::new(window.clone()).unwrap(); - let surface = softbuffer::Surface::new(&context, window.clone()).unwrap(); - - (window, surface) - }).with_event_handler(|state, event, elwt| { - let (window, surface) = state; + let mut app = winit_app::WinitAppBuilder::with_init( + |elwt| { + let window = { + let window = elwt.create_window(Window::default_attributes()); + Rc::new(window.unwrap()) + }; + let context = softbuffer::Context::new(window.clone()).unwrap(); + + (window, context) + }, + |_elwt, (window, context)| softbuffer::Surface::new(context, window.clone()).unwrap(), + ) + .with_event_handler(|(window, _context), surface, event, elwt| { elwt.set_control_flow(ControlFlow::Wait); match event { Event::WindowEvent { window_id, event: WindowEvent::RedrawRequested } if window_id == window.id() => { + let Some(surface) = surface else { + eprintln!("RedrawRequested fired before Resumed or after Suspended"); + return; + }; let (width, height) = { let size = window.inner_size(); (size.width, size.height) diff --git a/examples/animation.rs b/examples/animation.rs index 72b387b..420e390 100644 --- a/examples/animation.rs +++ b/examples/animation.rs @@ -14,19 +14,23 @@ fn main() { let event_loop = EventLoop::new().unwrap(); let start = Instant::now(); - let app = winit_app::WinitAppBuilder::with_init(|event_loop| { - let window = winit_app::make_window(event_loop, |w| w); + let app = winit_app::WinitAppBuilder::with_init( + |event_loop| { + let window = winit_app::make_window(event_loop, |w| w); - let context = softbuffer::Context::new(window.clone()).unwrap(); - let surface = softbuffer::Surface::new(&context, window.clone()).unwrap(); + let context = softbuffer::Context::new(window.clone()).unwrap(); - let old_size = (0, 0); - let frames = pre_render_frames(0, 0); + let old_size = (0, 0); + let frames = pre_render_frames(0, 0); - (window, surface, old_size, frames) - }) - .with_event_handler(move |state, event, elwt| { - let (window, surface, old_size, frames) = state; + (window, context, old_size, frames) + }, + |_elwft, (window, context, _old_size, _frames)| { + softbuffer::Surface::new(context, window.clone()).unwrap() + }, + ) + .with_event_handler(move |state, surface, event, elwt| { + let (window, _context, old_size, frames) = state; elwt.set_control_flow(ControlFlow::Poll); @@ -35,6 +39,10 @@ fn main() { window_id, event: WindowEvent::RedrawRequested, } if window_id == window.id() => { + let Some(surface) = surface else { + eprintln!("RedrawRequested fired before Resumed or after Suspended"); + return; + }; if let (Some(width), Some(height)) = { let size = window.inner_size(); (NonZeroU32::new(size.width), NonZeroU32::new(size.height)) diff --git a/examples/fruit.rs b/examples/fruit.rs index f276ee9..febdbaf 100644 --- a/examples/fruit.rs +++ b/examples/fruit.rs @@ -14,18 +14,20 @@ fn main() { let event_loop = EventLoop::new().unwrap(); - let app = winit_app::WinitAppBuilder::with_init(move |elwt| { - let window = winit_app::make_window(elwt, |w| { - w.with_inner_size(winit::dpi::PhysicalSize::new(width, height)) - }); + let app = winit_app::WinitAppBuilder::with_init( + move |elwt| { + let window = winit_app::make_window(elwt, |w| { + w.with_inner_size(winit::dpi::PhysicalSize::new(width, height)) + }); - let context = softbuffer::Context::new(window.clone()).unwrap(); - let surface = softbuffer::Surface::new(&context, window.clone()).unwrap(); + let context = softbuffer::Context::new(window.clone()).unwrap(); - (window, surface) - }) - .with_event_handler(move |state, event, elwt| { - let (window, surface) = state; + (window, context) + }, + |_elwt, (window, context)| softbuffer::Surface::new(context, window.clone()).unwrap(), + ) + .with_event_handler(move |state, surface, event, elwt| { + let (window, _context) = state; elwt.set_control_flow(ControlFlow::Wait); match event { @@ -33,6 +35,10 @@ fn main() { window_id, event: WindowEvent::RedrawRequested, } if window_id == window.id() => { + let Some(surface) = surface else { + eprintln!("RedrawRequested fired before Resumed or after Suspended"); + return; + }; surface .resize( NonZeroU32::new(fruit.width()).unwrap(), diff --git a/examples/rectangle.rs b/examples/rectangle.rs index 7ab5917..0a1ae2e 100644 --- a/examples/rectangle.rs +++ b/examples/rectangle.rs @@ -25,20 +25,24 @@ fn redraw(buffer: &mut [u32], width: usize, height: usize, flag: bool) { fn main() { let event_loop = EventLoop::new().unwrap(); - let app = winit_app::WinitAppBuilder::with_init(|elwt| { - let window = winit_app::make_window(elwt, |w| { - w.with_title("Press space to show/hide a rectangle") - }); + let app = winit_app::WinitAppBuilder::with_init( + |elwt| { + let window = winit_app::make_window(elwt, |w| { + w.with_title("Press space to show/hide a rectangle") + }); - let context = softbuffer::Context::new(window.clone()).unwrap(); - let surface = softbuffer::Surface::new(&context, window.clone()).unwrap(); + let context = softbuffer::Context::new(window.clone()).unwrap(); - let flag = false; + let flag = false; - (window, surface, flag) - }) - .with_event_handler(|state, event, elwt| { - let (window, surface, flag) = state; + (window, context, flag) + }, + |_elwt, (window, context, _flag)| { + softbuffer::Surface::new(context, window.clone()).unwrap() + }, + ) + .with_event_handler(|state, surface, event, elwt| { + let (window, _context, flag) = state; elwt.set_control_flow(ControlFlow::Wait); @@ -47,6 +51,10 @@ fn main() { window_id, event: WindowEvent::RedrawRequested, } if window_id == window.id() => { + let Some(surface) = surface else { + eprintln!("RedrawRequested fired before Resumed or after Suspended"); + return; + }; // Grab the window's client area dimensions if let (Some(width), Some(height)) = { let size = window.inner_size(); diff --git a/examples/utils/winit_app.rs b/examples/utils/winit_app.rs index f427894..3b4cb4d 100644 --- a/examples/utils/winit_app.rs +++ b/examples/utils/winit_app.rs @@ -31,76 +31,94 @@ pub(crate) fn make_window( } /// Easily constructable winit application. -pub(crate) struct WinitApp { - /// Closure to initialize state. +pub(crate) struct WinitApp { + /// Closure to initialize `state`. init: Init, + /// Closure to initialize `surface_state`. + init_surface: InitSurface, + /// Closure to run on window events. event: Handler, /// Contained state. state: Option, + + /// Contained surface state. + surface_state: Option, } /// Builder that makes it so we don't have to name `T`. -pub(crate) struct WinitAppBuilder { - /// Closure to initialize state. +pub(crate) struct WinitAppBuilder { + /// Closure to initialize `state`. init: Init, + /// Closure to initialize `surface_state`. + init_surface: InitSurface, + /// Eat the type parameter. - _marker: PhantomData>, + _marker: PhantomData<(Option, Option)>, } -impl WinitAppBuilder +impl WinitAppBuilder where Init: FnMut(&ActiveEventLoop) -> T, + InitSurface: FnMut(&ActiveEventLoop, &mut T) -> S, { /// Create with an "init" closure. - pub(crate) fn with_init(init: Init) -> Self { + pub(crate) fn with_init(init: Init, init_surface: InitSurface) -> Self { Self { init, + init_surface, _marker: PhantomData, } } /// Build a new application. - pub(crate) fn with_event_handler(self, handler: F) -> WinitApp + pub(crate) fn with_event_handler(self, handler: F) -> WinitApp where - F: FnMut(&mut T, Event<()>, &ActiveEventLoop), + F: FnMut(&mut T, Option<&mut S>, Event<()>, &ActiveEventLoop), { - WinitApp::new(self.init, handler) + WinitApp::new(self.init, self.init_surface, handler) } } -impl WinitApp +impl WinitApp where Init: FnMut(&ActiveEventLoop) -> T, - Handler: FnMut(&mut T, Event<()>, &ActiveEventLoop), + InitSurface: FnMut(&ActiveEventLoop, &mut T) -> S, + Handler: FnMut(&mut T, Option<&mut S>, Event<()>, &ActiveEventLoop), { /// Create a new application. - pub(crate) fn new(init: Init, event: Handler) -> Self { + pub(crate) fn new(init: Init, init_surface: InitSurface, event: Handler) -> Self { Self { init, + init_surface, event, state: None, + surface_state: None, } } } -impl ApplicationHandler for WinitApp +impl ApplicationHandler + for WinitApp where Init: FnMut(&ActiveEventLoop) -> T, - Handler: FnMut(&mut T, Event<()>, &ActiveEventLoop), + InitSurface: FnMut(&ActiveEventLoop, &mut T) -> S, + Handler: FnMut(&mut T, Option<&mut S>, Event<()>, &ActiveEventLoop), { fn resumed(&mut self, el: &ActiveEventLoop) { debug_assert!(self.state.is_none()); - self.state = Some((self.init)(el)); + let mut state = (self.init)(el); + self.surface_state = Some((self.init_surface)(el, &mut state)); + self.state = Some(state); } fn suspended(&mut self, _event_loop: &ActiveEventLoop) { - let state = self.state.take(); - debug_assert!(state.is_some()); - drop(state); + let surface_state = self.surface_state.take(); + debug_assert!(surface_state.is_some()); + drop(surface_state); } fn window_event( @@ -110,12 +128,23 @@ where event: WindowEvent, ) { let state = self.state.as_mut().unwrap(); - (self.event)(state, Event::WindowEvent { window_id, event }, event_loop); + let surface_state = self.surface_state.as_mut(); + (self.event)( + state, + surface_state, + Event::WindowEvent { window_id, event }, + event_loop, + ); } fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { if let Some(state) = self.state.as_mut() { - (self.event)(state, Event::AboutToWait, event_loop); + (self.event)( + state, + self.surface_state.as_mut(), + Event::AboutToWait, + event_loop, + ); } } } diff --git a/examples/winit.rs b/examples/winit.rs index 7678fa5..fe80211 100644 --- a/examples/winit.rs +++ b/examples/winit.rs @@ -9,16 +9,17 @@ mod winit_app; fn main() { let event_loop = EventLoop::new().unwrap(); - let app = winit_app::WinitAppBuilder::with_init(|elwt| { - let window = winit_app::make_window(elwt, |w| w); + let app = winit_app::WinitAppBuilder::with_init( + |elwt| { + let window = winit_app::make_window(elwt, |w| w); - let context = softbuffer::Context::new(window.clone()).unwrap(); - let surface = softbuffer::Surface::new(&context, window.clone()).unwrap(); + let context = softbuffer::Context::new(window.clone()).unwrap(); - (window, surface) - }) - .with_event_handler(|state, event, elwt| { - let (window, surface) = state; + (window, context) + }, + |_elwt, (window, context)| softbuffer::Surface::new(context, window.clone()).unwrap(), + ) + .with_event_handler(|(window, _context), surface, event, elwt| { elwt.set_control_flow(ControlFlow::Wait); match event { @@ -26,6 +27,10 @@ fn main() { window_id, event: WindowEvent::RedrawRequested, } if window_id == window.id() => { + let Some(surface) = surface else { + eprintln!("RedrawRequested fired before Resumed or after Suspended"); + return; + }; if let (Some(width), Some(height)) = { let size = window.inner_size(); (NonZeroU32::new(size.width), NonZeroU32::new(size.height)) diff --git a/examples/winit_multithread.rs b/examples/winit_multithread.rs index 4c2be4d..a70260e 100644 --- a/examples/winit_multithread.rs +++ b/examples/winit_multithread.rs @@ -26,7 +26,7 @@ mod ex { loop { println!("waiting for render..."); if do_render.recv().is_err() { - // Main thread is dead. + // The surface state returned from init_surface below is destroyed. break; } @@ -63,34 +63,43 @@ mod ex { pub(super) fn entry() { let event_loop = EventLoop::new().unwrap(); - let app = winit_app::WinitAppBuilder::with_init(|elwt| { - let attributes = Window::default_attributes(); - #[cfg(target_arch = "wasm32")] - let attributes = - winit::platform::web::WindowAttributesExtWebSys::with_append(attributes, true); - let window = Arc::new(elwt.create_window(attributes).unwrap()); - - let context = softbuffer::Context::new(window.clone()).unwrap(); - let surface = { - println!("making surface..."); - let surface = softbuffer::Surface::new(&context, window.clone()).unwrap(); - Arc::new(Mutex::new(surface)) - }; - - // Spawn a thread to handle rendering. - let (start_render, do_render) = mpsc::channel(); - let (render_done, finish_render) = mpsc::channel(); - println!("starting thread..."); - std::thread::spawn({ - let window = window.clone(); - let surface = surface.clone(); - move || render_thread(window, surface, do_render, render_done) - }); - - (window, surface, start_render, finish_render) - }) - .with_event_handler(|state, event, elwt| { - let (window, _surface, start_render, finish_render) = state; + let app = winit_app::WinitAppBuilder::with_init( + |elwt| { + let attributes = Window::default_attributes(); + #[cfg(target_arch = "wasm32")] + let attributes = + winit::platform::web::WindowAttributesExtWebSys::with_append(attributes, true); + let window = Arc::new(elwt.create_window(attributes).unwrap()); + + let context = softbuffer::Context::new(window.clone()).unwrap(); + + (window, context) + }, + |_elwt, (window, context)| { + let surface = { + println!("making surface..."); + let surface = softbuffer::Surface::new(context, window.clone()).unwrap(); + Arc::new(Mutex::new(surface)) + }; + + // Spawn a thread to handle rendering for this specific surface. The channels will + // be closed and the thread will be stopped whenever this surface (the returned + // context below) is dropped, so that it can all be recreated again (on Android) + // when a new surface is created. + let (start_render, do_render) = mpsc::channel(); + let (render_done, finish_render) = mpsc::channel(); + println!("starting thread..."); + std::thread::spawn({ + let window = window.clone(); + let surface = surface.clone(); + move || render_thread(window, surface, do_render, render_done) + }); + + (surface, start_render, finish_render) + }, + ) + .with_event_handler(|state, surface, event, elwt| { + let (window, _context) = state; elwt.set_control_flow(ControlFlow::Wait); match event { @@ -98,6 +107,10 @@ mod ex { window_id, event: WindowEvent::RedrawRequested, } if window_id == window.id() => { + let Some((_surface, start_render, finish_render)) = surface else { + eprintln!("RedrawRequested fired before Resumed or after Suspended"); + return; + }; // Start the render and then finish it. start_render.send(()).unwrap(); finish_render.recv().unwrap(); diff --git a/examples/winit_wrong_sized_buffer.rs b/examples/winit_wrong_sized_buffer.rs index c0396d3..72e0e59 100644 --- a/examples/winit_wrong_sized_buffer.rs +++ b/examples/winit_wrong_sized_buffer.rs @@ -12,16 +12,18 @@ const BUFFER_HEIGHT: usize = 128; fn main() { let event_loop = EventLoop::new().unwrap(); - let app = winit_app::WinitAppBuilder::with_init(|elwt| { - let window = winit_app::make_window(elwt, |w| w); + let app = winit_app::WinitAppBuilder::with_init( + |elwt| { + let window = winit_app::make_window(elwt, |w| w); - let context = softbuffer::Context::new(window.clone()).unwrap(); - let surface = softbuffer::Surface::new(&context, window.clone()).unwrap(); + let context = softbuffer::Context::new(window.clone()).unwrap(); - (window, surface) - }) - .with_event_handler(|state, event, elwt| { - let (window, surface) = state; + (window, context) + }, + |_elwt, (window, context)| softbuffer::Surface::new(context, window.clone()).unwrap(), + ) + .with_event_handler(|state, surface, event, elwt| { + let (window, _context) = state; elwt.set_control_flow(ControlFlow::Wait); match event { @@ -29,6 +31,10 @@ fn main() { window_id, event: WindowEvent::RedrawRequested, } if window_id == window.id() => { + let Some(surface) = surface else { + eprintln!("RedrawRequested fired before Resumed or after Suspended"); + return; + }; surface .resize( NonZeroU32::new(BUFFER_WIDTH as u32).unwrap(),