diff --git a/glutin_examples/src/lib.rs b/glutin_examples/src/lib.rs index e997dd01d3..e5b75e17bf 100644 --- a/glutin_examples/src/lib.rs +++ b/glutin_examples/src/lib.rs @@ -7,10 +7,11 @@ use gl::types::GLfloat; use raw_window_handle::HasWindowHandle; use winit::application::ApplicationHandler; use winit::event::{KeyEvent, WindowEvent}; +use winit::event_loop::ActiveEventLoop; use winit::keyboard::{Key, NamedKey}; -use winit::window::Window; +use winit::window::{Window, WindowAttributes}; -use glutin::config::{Config, ConfigTemplateBuilder}; +use glutin::config::{Config, ConfigTemplateBuilder, GetGlConfig}; use glutin::context::{ ContextApi, ContextAttributesBuilder, NotCurrentContext, PossiblyCurrentContext, Version, }; @@ -28,10 +29,6 @@ pub mod gl { } pub fn main(event_loop: winit::event_loop::EventLoop<()>) -> Result<(), Box> { - let window_attributes = Window::default_attributes() - .with_transparent(true) - .with_title("Glutin triangle gradient example (press Escape to exit)"); - // The template will match only the configurations supporting rendering // to windows. // @@ -43,7 +40,7 @@ pub fn main(event_loop: winit::event_loop::EventLoop<()>) -> Result<(), Box) -> Result<(), Box ok, - Err(e) => { - self.exit_state = Err(e); - event_loop.exit(); - return; - }, - }; - - println!("Picked a config with {} samples", gl_config.num_samples()); - - let raw_window_handle = window - .as_ref() - .and_then(|window| window.window_handle().ok()) - .map(|handle| handle.as_raw()); - - // XXX The display could be obtained from any object created by it, so we can - // query it from the config. - let gl_display = gl_config.display(); - - // The context creation part. - let context_attributes = ContextAttributesBuilder::new().build(raw_window_handle); - - // Since glutin by default tries to create OpenGL core context, which may not be - // present we should try gles. - let fallback_context_attributes = ContextAttributesBuilder::new() - .with_context_api(ContextApi::Gles(None)) - .build(raw_window_handle); - - // There are also some old devices that support neither modern OpenGL nor GLES. - // To support these we can try and create a 2.1 context. - let legacy_context_attributes = ContextAttributesBuilder::new() - .with_context_api(ContextApi::OpenGl(Some(Version::new(2, 1)))) - .build(raw_window_handle); - - // Reuse the uncurrented context from a suspended() call if it exists, otherwise - // this is the first time resumed() is called, where the context still - // has to be created. - let not_current_gl_context = self.not_current_gl_context.take().unwrap_or_else(|| unsafe { - gl_display.create_context(&gl_config, &context_attributes).unwrap_or_else(|_| { - gl_display.create_context(&gl_config, &fallback_context_attributes).unwrap_or_else( - |_| { - gl_display - .create_context(&gl_config, &legacy_context_attributes) - .expect("failed to create context") + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + let (window, gl_config) = match &self.gl_display { + // We just created the event loop, so initialize the display, pick the config, and + // create the context. + GlDisplayCreationState::Builder(display_builder) => { + let (window, gl_config) = match display_builder.clone().build( + event_loop, + self.template.clone(), + gl_config_picker, + ) { + Ok((window, gl_config)) => (window.unwrap(), gl_config), + Err(err) => { + self.exit_state = Err(err); + event_loop.exit(); + return; }, - ) - }) - }); + }; - #[cfg(android_platform)] - println!("Android window available"); + println!("Picked a config with {} samples", gl_config.num_samples()); - let window = window.take().unwrap_or_else(|| { - let window_attributes = Window::default_attributes() - .with_transparent(true) - .with_title("Glutin triangle gradient example (press Escape to exit)"); - glutin_winit::finalize_window(event_loop, window_attributes, &gl_config).unwrap() - }); + // Mark the display as initialized to not recreate it on resume, since the + // display is valid until we explicitly destroy it. + self.gl_display = GlDisplayCreationState::Init; + + // Create gl context. + self.gl_context = + Some(create_gl_context(&window, &gl_config).treat_as_possibly_current()); + + (window, gl_config) + }, + GlDisplayCreationState::Init => { + println!("Recreating window in `resumed`"); + // Pick the config which we already use for the context. + let gl_config = self.gl_context.as_ref().unwrap().config(); + match glutin_winit::finalize_window(event_loop, window_attributes(), &gl_config) { + Ok(window) => (window, gl_config), + Err(err) => { + self.exit_state = Err(err.into()); + event_loop.exit(); + return; + }, + } + }, + }; let attrs = window .build_surface_attributes(Default::default()) @@ -123,41 +100,42 @@ impl ApplicationHandler for App { let gl_surface = unsafe { gl_config.display().create_window_surface(&gl_config, &attrs).unwrap() }; - // Make it current. - let gl_context = not_current_gl_context.make_current(&gl_surface).unwrap(); - // The context needs to be current for the Renderer to set up shaders and // buffers. It also performs function loading, which needs a current context on // WGL. - self.renderer.get_or_insert_with(|| Renderer::new(&gl_display)); + let gl_context = self.gl_context.as_ref().unwrap(); + gl_context.make_current(&gl_surface).unwrap(); + + self.renderer.get_or_insert_with(|| Renderer::new(&gl_config.display())); // Try setting vsync. if let Err(res) = gl_surface - .set_swap_interval(&gl_context, SwapInterval::Wait(NonZeroU32::new(1).unwrap())) + .set_swap_interval(gl_context, SwapInterval::Wait(NonZeroU32::new(1).unwrap())) { eprintln!("Error setting vsync: {res:?}"); } - assert!(self.state.replace(AppState { gl_context, gl_surface, window }).is_none()); + assert!(self.state.replace(AppState { gl_surface, window }).is_none()); } - fn suspended(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) { + fn suspended(&mut self, _event_loop: &ActiveEventLoop) { // This event is only raised on Android, where the backing NativeWindow for a GL // Surface can appear and disappear at any moment. println!("Android window removed"); // Destroy the GL Surface and un-current the GL Context before ndk-glue releases // the window back to the system. - let gl_context = self.state.take().unwrap().gl_context; - assert!(self - .not_current_gl_context - .replace(gl_context.make_not_current().unwrap()) - .is_none()); + self.state = None; + + // Make context not current. + self.gl_context = Some( + self.gl_context.take().unwrap().make_not_current().unwrap().treat_as_possibly_current(), + ); } fn window_event( &mut self, - event_loop: &winit::event_loop::ActiveEventLoop, + event_loop: &ActiveEventLoop, _window_id: winit::window::WindowId, event: WindowEvent, ) { @@ -167,12 +145,14 @@ impl ApplicationHandler for App { // Notable platforms here are Wayland and macOS, other don't require it // and the function is no-op, but it's wise to resize it for portability // reasons. - if let Some(AppState { gl_context, gl_surface, window: _ }) = self.state.as_ref() { + if let Some(AppState { gl_surface, window: _ }) = self.state.as_ref() { + let gl_context = self.gl_context.as_ref().unwrap(); gl_surface.resize( gl_context, NonZeroU32::new(size.width).unwrap(), NonZeroU32::new(size.height).unwrap(), ); + let renderer = self.renderer.as_ref().unwrap(); renderer.resize(size.width as i32, size.height as i32); } @@ -186,8 +166,26 @@ impl ApplicationHandler for App { } } - fn about_to_wait(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) { - if let Some(AppState { gl_context, gl_surface, window }) = self.state.as_ref() { + fn exiting(&mut self, _event_loop: &ActiveEventLoop) { + // NOTE: The handling below is only needed due to nvidia on Wayland to not crash + // on exit due to nvidia driver touching the Wayland display from on + // `exit` hook. + let _gl_display = self.gl_context.take().unwrap().display(); + + // Clear the window. + self.state = None; + #[cfg(egl_backend)] + #[allow(irrefutable_let_patterns)] + if let glutin::display::Display::Egl(display) = _gl_display { + unsafe { + display.terminate(); + } + } + } + + fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) { + if let Some(AppState { gl_surface, window }) = self.state.as_ref() { + let gl_context = self.gl_context.as_ref().unwrap(); let renderer = self.renderer.as_ref().unwrap(); renderer.draw(); window.request_redraw(); @@ -197,23 +195,72 @@ impl ApplicationHandler for App { } } +fn create_gl_context(window: &Window, gl_config: &Config) -> NotCurrentContext { + let raw_window_handle = window.window_handle().ok().map(|wh| wh.as_raw()); + + // The context creation part. + let context_attributes = ContextAttributesBuilder::new().build(raw_window_handle); + + // Since glutin by default tries to create OpenGL core context, which may not be + // present we should try gles. + let fallback_context_attributes = ContextAttributesBuilder::new() + .with_context_api(ContextApi::Gles(None)) + .build(raw_window_handle); + + // There are also some old devices that support neither modern OpenGL nor GLES. + // To support these we can try and create a 2.1 context. + let legacy_context_attributes = ContextAttributesBuilder::new() + .with_context_api(ContextApi::OpenGl(Some(Version::new(2, 1)))) + .build(raw_window_handle); + + // Reuse the uncurrented context from a suspended() call if it exists, otherwise + // this is the first time resumed() is called, where the context still + // has to be created. + let gl_display = gl_config.display(); + + unsafe { + gl_display.create_context(gl_config, &context_attributes).unwrap_or_else(|_| { + gl_display.create_context(gl_config, &fallback_context_attributes).unwrap_or_else( + |_| { + gl_display + .create_context(gl_config, &legacy_context_attributes) + .expect("failed to create context") + }, + ) + }) + } +} + +fn window_attributes() -> WindowAttributes { + Window::default_attributes() + .with_transparent(true) + .with_title("Glutin triangle gradient example (press Escape to exit)") +} + +enum GlDisplayCreationState { + /// The display was not build yet. + Builder(DisplayBuilder), + /// The display was already created for the application. + Init, +} + struct App { template: ConfigTemplateBuilder, - display_builder: DisplayBuilder, - exit_state: Result<(), Box>, - not_current_gl_context: Option, renderer: Option, // NOTE: `AppState` carries the `Window`, thus it should be dropped after everything else. state: Option, + gl_context: Option, + gl_display: GlDisplayCreationState, + exit_state: Result<(), Box>, } impl App { fn new(template: ConfigTemplateBuilder, display_builder: DisplayBuilder) -> Self { Self { template, - display_builder, + gl_display: GlDisplayCreationState::Builder(display_builder), exit_state: Ok(()), - not_current_gl_context: None, + gl_context: None, state: None, renderer: None, } @@ -221,7 +268,6 @@ impl App { } struct AppState { - gl_context: PossiblyCurrentContext, gl_surface: Surface, // NOTE: Window should be dropped after all resources created using its // raw-window-handle.