From aa67fca8e393b26bb063553ea5f9d46e2861c32a Mon Sep 17 00:00:00 2001 From: Erich Gubler Date: Tue, 14 May 2024 11:10:54 -0400 Subject: [PATCH] glutin-winit: bump winit to 0.30.0 Co-Authored-By: marc2332 Co-Authored-By: Kirill Chibisov Co-Authored-By: Marijn Suijten --- glutin-winit/CHANGELOG.md | 5 + glutin-winit/Cargo.toml | 2 +- glutin-winit/src/event_loop.rs | 44 +++ glutin-winit/src/lib.rs | 70 ++-- glutin_examples/Cargo.toml | 4 +- glutin_examples/examples/android.rs | 4 +- .../examples/switch_render_thread.rs | 129 ++++--- glutin_examples/examples/window.rs | 4 +- glutin_examples/src/lib.rs | 325 ++++++++++-------- 9 files changed, 365 insertions(+), 222 deletions(-) create mode 100644 glutin-winit/src/event_loop.rs diff --git a/glutin-winit/CHANGELOG.md b/glutin-winit/CHANGELOG.md index 445b92c548..70bd0d61d7 100644 --- a/glutin-winit/CHANGELOG.md +++ b/glutin-winit/CHANGELOG.md @@ -1,5 +1,10 @@ # Unreleased +- **Breaking:** Update _winit_ to `0.30`. See [winit's CHANGELOG](https://github.com/rust-windowing/winit/releases/tag/v0.30.0) for more info. +- Add the `GlutinEventLoop` trait to maintain compatibility with the now + deprecated `EventLoop` but also support the new `ActiveEventLoop`. +- Update `DisplayBuilder` to use `WindowAttributes` instead of `WindowBuilder`. + # Version 0.4.2 - **Breaking:** Update _glutin_ to `0.31.0`. See [glutin's CHANGELOG](https://github.com/rust-windowing/glutin/releases/tag/v0.31.0) for more info. diff --git a/glutin-winit/Cargo.toml b/glutin-winit/Cargo.toml index 3a63d8b432..399187a95f 100644 --- a/glutin-winit/Cargo.toml +++ b/glutin-winit/Cargo.toml @@ -21,7 +21,7 @@ wayland = ["glutin/wayland", "winit/wayland"] [dependencies] glutin = { version = "0.31.0", path = "../glutin", default-features = false } raw-window-handle = "0.6" -winit = { version = "0.29.2", default-features = false, features = ["rwh_06"] } +winit = { version = "0.30.0", default-features = false, features = ["rwh_06"] } [build-dependencies] cfg_aliases = "0.2.1" diff --git a/glutin-winit/src/event_loop.rs b/glutin-winit/src/event_loop.rs new file mode 100644 index 0000000000..1d2c928b93 --- /dev/null +++ b/glutin-winit/src/event_loop.rs @@ -0,0 +1,44 @@ +use raw_window_handle::{DisplayHandle, HandleError, HasDisplayHandle}; +use winit::error::OsError; +use winit::event_loop::{ActiveEventLoop, EventLoop}; +use winit::window::{Window, WindowAttributes}; + +use crate::private::Sealed; + +/// [`ActiveEventLoop`] is the recommended way to interact with the event +/// loop, but for compatibility purposes [`EventLoop`] is also supported +/// although not recommended anymore as it has been deprecated by Winit. +pub trait GlutinEventLoop: Sealed { + /// Create the window. + /// + /// See [`ActiveEventLoop::create_window`] for details. + fn create_window(&self, window_attributes: WindowAttributes) -> Result; + + /// Get a handle to the display controller of the windowing system. + fn glutin_display_handle(&self) -> Result, HandleError>; +} + +impl Sealed for ActiveEventLoop {} + +impl GlutinEventLoop for ActiveEventLoop { + fn create_window(&self, window_attributes: WindowAttributes) -> Result { + self.create_window(window_attributes) + } + + fn glutin_display_handle(&self) -> Result, HandleError> { + self.display_handle() + } +} + +impl Sealed for EventLoop {} + +impl GlutinEventLoop for EventLoop { + #[allow(deprecated)] + fn create_window(&self, window_attributes: WindowAttributes) -> Result { + self.create_window(window_attributes) + } + + fn glutin_display_handle(&self) -> Result, HandleError> { + self.display_handle() + } +} diff --git a/glutin-winit/src/lib.rs b/glutin-winit/src/lib.rs index 4dc9129f6d..00b9a8f23d 100644 --- a/glutin-winit/src/lib.rs +++ b/glutin-winit/src/lib.rs @@ -8,8 +8,10 @@ #![deny(missing_docs)] #![cfg_attr(clippy, deny(warnings))] +mod event_loop; mod window; +use event_loop::GlutinEventLoop; pub use window::GlWindow; use std::error::Error; @@ -23,19 +25,25 @@ use glutin::prelude::*; #[cfg(wgl_backend)] use raw_window_handle::HasWindowHandle; -use raw_window_handle::{HasDisplayHandle, RawWindowHandle}; +use raw_window_handle::RawWindowHandle; use winit::error::OsError; -use winit::event_loop::EventLoopWindowTarget; -use winit::window::{Window, WindowBuilder}; +use winit::window::{Window, WindowAttributes}; #[cfg(glx_backend)] use winit::platform::x11::register_xlib_error_hook; #[cfg(x11_platform)] -use winit::platform::x11::WindowBuilderExtX11; +use winit::platform::x11::WindowAttributesExtX11; #[cfg(all(not(egl_backend), not(glx_backend), not(wgl_backend), not(cgl_backend)))] compile_error!("Please select at least one api backend"); +pub(crate) mod private { + /// Prevent traits from being implemented downstream, since those are used + /// purely for documentation organization and simplify platform api + /// implementation maintenance. + pub trait Sealed {} +} + /// The helper to perform [`Display`] creation and OpenGL platform /// bootstrapping with the help of [`winit`] with little to no platform specific /// code. @@ -50,7 +58,7 @@ compile_error!("Please select at least one api backend"); #[derive(Default, Debug, Clone)] pub struct DisplayBuilder { preference: ApiPreference, - window_builder: Option, + window_attributes: Option, } impl DisplayBuilder { @@ -65,30 +73,30 @@ impl DisplayBuilder { self } - /// The window builder to use when building a window. + /// The window attributes to use when building a window. /// /// By default no window is created. - pub fn with_window_builder(mut self, window_builder: Option) -> Self { - self.window_builder = window_builder; + pub fn with_window_attributes(mut self, window_attributes: Option) -> Self { + self.window_attributes = window_attributes; self } /// Initialize the OpenGL platform and create a compatible window to use - /// with it when the [`WindowBuilder`] was passed with - /// [`Self::with_window_builder`]. It's optional, since on some + /// with it when the [`WindowAttributes`] was passed with + /// [`Self::with_window_attributes()`]. It's optional, since on some /// platforms like `Android` it is not available early on, so you want to /// find configuration and later use it with the [`finalize_window`]. /// But if you don't care about such platform you can always pass - /// [`WindowBuilder`]. + /// [`WindowAttributes`]. /// /// # Api-specific /// - /// **WGL:** - [`WindowBuilder`] **must** be passed in - /// [`Self::with_window_builder`] if modern OpenGL(ES) is desired, + /// **WGL:** - [`WindowAttributes`] **must** be passed in + /// [`Self::with_window_attributes()`] if modern OpenGL(ES) is desired, /// otherwise only builtin functions like `glClear` will be available. - pub fn build( + pub fn build( mut self, - window_target: &EventLoopWindowTarget, + event_loop: &impl GlutinEventLoop, template_builder: ConfigTemplateBuilder, config_picker: Picker, ) -> Result<(Option, Config), Box> @@ -97,8 +105,8 @@ impl DisplayBuilder { { // XXX with WGL backend window should be created first. #[cfg(wgl_backend)] - let window = if let Some(wb) = self.window_builder.take() { - Some(wb.build(window_target)?) + let window = if let Some(wa) = self.window_attributes.take() { + Some(event_loop.create_window(wa)?) } else { None }; @@ -111,7 +119,7 @@ impl DisplayBuilder { #[cfg(not(wgl_backend))] let raw_window_handle = None; - let gl_display = create_display(window_target, self.preference, raw_window_handle)?; + let gl_display = create_display(event_loop, self.preference, raw_window_handle)?; // XXX the native window must be passed to config picker when WGL is used // otherwise very limited OpenGL features will be supported. @@ -130,8 +138,8 @@ impl DisplayBuilder { }; #[cfg(not(wgl_backend))] - let window = if let Some(wb) = self.window_builder.take() { - Some(finalize_window(window_target, wb, &gl_config)?) + let window = if let Some(wa) = self.window_attributes.take() { + Some(finalize_window(event_loop, wa, &gl_config)?) } else { None }; @@ -140,8 +148,8 @@ impl DisplayBuilder { } } -fn create_display( - window_target: &EventLoopWindowTarget, +fn create_display( + event_loop: &impl GlutinEventLoop, _api_preference: ApiPreference, _raw_window_handle: Option, ) -> Result> { @@ -173,7 +181,7 @@ fn create_display( ApiPreference::FallbackEgl => DisplayApiPreference::WglThenEgl(_raw_window_handle), }; - let handle = window_target.display_handle()?.as_raw(); + let handle = event_loop.glutin_display_handle()?.as_raw(); unsafe { Ok(Display::new(handle, _preference)?) } } @@ -183,24 +191,24 @@ fn create_display( /// /// [`Window`]: winit::window::Window /// [`Config`]: glutin::config::Config -pub fn finalize_window( - window_target: &EventLoopWindowTarget, - mut builder: WindowBuilder, +pub fn finalize_window( + event_loop: &impl GlutinEventLoop, + mut attributes: WindowAttributes, gl_config: &Config, ) -> Result { // Disable transparency if the end config doesn't support it. if gl_config.supports_transparency() == Some(false) { - builder = builder.with_transparent(false); + attributes = attributes.with_transparent(false); } #[cfg(x11_platform)] - let builder = if let Some(x11_visual) = gl_config.x11_visual() { - builder.with_x11_visual(x11_visual.visual_id() as _) + let attributes = if let Some(x11_visual) = gl_config.x11_visual() { + attributes.with_x11_visual(x11_visual.visual_id() as _) } else { - builder + attributes }; - builder.build(window_target) + event_loop.create_window(attributes) } /// Simplified version of the [`DisplayApiPreference`] which is used to simplify diff --git a/glutin_examples/Cargo.toml b/glutin_examples/Cargo.toml index a79fde076e..281e1bdbaa 100644 --- a/glutin_examples/Cargo.toml +++ b/glutin_examples/Cargo.toml @@ -23,10 +23,10 @@ glutin = { path = "../glutin", default-features = false } glutin-winit = { path = "../glutin-winit", default-features = false } png = { version = "0.17.6", optional = true } raw-window-handle = "0.6" -winit = { version = "0.29.2", default-features = false, features = ["rwh_05"] } +winit = { version = "0.30.0", default-features = false, features = ["rwh_06"] } [target.'cfg(target_os = "android")'.dependencies] -winit = { version = "0.29.2", default-features = false, features = ["android-native-activity", "rwh_06"] } +winit = { version = "0.30.0", default-features = false, features = ["android-native-activity", "rwh_06"] } [build-dependencies] gl_generator = "0.14" diff --git a/glutin_examples/examples/android.rs b/glutin_examples/examples/android.rs index fcd3bb31c1..818c2e44d0 100644 --- a/glutin_examples/examples/android.rs +++ b/glutin_examples/examples/android.rs @@ -1,10 +1,10 @@ #![cfg(android_platform)] -use winit::event_loop::EventLoopBuilder; +use winit::event_loop::EventLoop; use winit::platform::android::EventLoopBuilderExtAndroid; #[no_mangle] fn android_main(app: winit::platform::android::activity::AndroidApp) { - let event_loop = EventLoopBuilder::new().with_android_app(app).build().unwrap(); + let event_loop = EventLoop::builder().with_android_app(app).build().unwrap(); glutin_examples::main(event_loop).unwrap() } diff --git a/glutin_examples/examples/switch_render_thread.rs b/glutin_examples/examples/switch_render_thread.rs index 1f6584637a..8cffb6c26c 100644 --- a/glutin_examples/examples/switch_render_thread.rs +++ b/glutin_examples/examples/switch_render_thread.rs @@ -14,67 +14,106 @@ use glutin_examples::gl::types::GLfloat; use glutin_examples::{gl_config_picker, Renderer}; use glutin_winit::{self, DisplayBuilder, GlWindow}; use raw_window_handle::HasWindowHandle; +use winit::application::ApplicationHandler; use winit::dpi::PhysicalSize; -use winit::event::{ElementState, Event, WindowEvent}; -use winit::event_loop::{EventLoopBuilder, EventLoopProxy}; -use winit::window::{Window, WindowBuilder}; +use winit::event::{ElementState, WindowEvent}; +use winit::event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy}; +use winit::window::Window; fn main() -> Result<(), Box> { - let event_loop = EventLoopBuilder::::with_user_event().build().unwrap(); - - let (_window, render_context) = create_window_with_render_context(&event_loop)?; - let render_context = Arc::new(Mutex::new(render_context)); + let event_loop = EventLoop::::with_user_event().build().unwrap(); // `EventLoopProxy` allows you to dispatch custom events to the main Winit event // loop from any thread. let event_loop_proxy = event_loop.create_proxy(); - let (_render_threads, render_thread_senders) = - spawn_render_threads(render_context, event_loop_proxy); + let mut app = App::new(event_loop_proxy); - let mut app_state = AppState { - render_thread_senders, - render_thread_index: 0, - thread_switch_in_progress: false, - }; - app_state.send_event_to_current_render_thread(RenderThreadEvent::MakeCurrent); - - event_loop.run(move |event, elwt| match event { - Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => elwt.exit(), - Event::WindowEvent { event: WindowEvent::Resized(size), .. } => { - if size.width != 0 && size.height != 0 { - app_state.send_event_to_current_render_thread(RenderThreadEvent::Resize( - PhysicalSize { + event_loop.run_app(&mut app)?; + + app.exit_state +} + +struct App { + event_loop_proxy: EventLoopProxy, + exit_state: Result<(), Box>, + state: Option, +} + +impl App { + fn new(event_loop_proxy: EventLoopProxy) -> Self { + Self { event_loop_proxy, exit_state: Ok(()), state: None } + } +} + +impl ApplicationHandler for App { + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + if self.state.is_some() { + return; + } + + let (window, render_context) = match create_window_with_render_context(event_loop) { + Ok(ok) => ok, + Err(e) => { + self.exit_state = Err(e); + event_loop.exit(); + return; + }, + }; + let render_context = Arc::new(Mutex::new(render_context)); + + let (_render_threads, render_thread_senders) = + spawn_render_threads(render_context, self.event_loop_proxy.clone()); + + let state = AppState { + _window: window, + render_thread_senders, + render_thread_index: 0, + thread_switch_in_progress: false, + }; + state.send_event_to_current_render_thread(RenderThreadEvent::MakeCurrent); + assert!(self.state.replace(state).is_none()); + } + + fn window_event( + &mut self, + event_loop: &ActiveEventLoop, + _window_id: winit::window::WindowId, + event: WindowEvent, + ) { + match event { + WindowEvent::CloseRequested => event_loop.exit(), + WindowEvent::Resized(size) if size.width != 0 && size.height != 0 => { + self.state.as_ref().unwrap().send_event_to_current_render_thread( + RenderThreadEvent::Resize(PhysicalSize { width: NonZeroU32::new(size.width).unwrap(), height: NonZeroU32::new(size.height).unwrap(), - }, - )); - } - }, - Event::WindowEvent { event: WindowEvent::RedrawRequested, .. } => { - app_state.send_event_to_current_render_thread(RenderThreadEvent::Draw); - }, - Event::WindowEvent { - event: WindowEvent::MouseInput { state: ElementState::Pressed, .. }, - .. - } => { - app_state.start_render_thread_switch(); - }, - Event::UserEvent(event) => match event { - PlatformThreadEvent::ContextNotCurrent => { - app_state.complete_render_thread_switch(); + }), + ); + }, + WindowEvent::RedrawRequested => { + self.state + .as_ref() + .unwrap() + .send_event_to_current_render_thread(RenderThreadEvent::Draw); + }, + WindowEvent::MouseInput { state: ElementState::Pressed, .. } => { + self.state.as_mut().unwrap().start_render_thread_switch(); }, - }, - _ => (), - })?; + _ => (), + } + } - Ok(()) + fn user_event(&mut self, _event_loop: &ActiveEventLoop, _event: PlatformThreadEvent) { + self.state.as_mut().unwrap().complete_render_thread_switch(); + } } struct AppState { render_thread_senders: Vec>, render_thread_index: usize, thread_switch_in_progress: bool, + _window: Window, } impl AppState { @@ -165,13 +204,13 @@ impl RenderContext { } fn create_window_with_render_context( - event_loop: &winit::event_loop::EventLoop, + event_loop: &ActiveEventLoop, ) -> Result<(Window, RenderContext), Box> { - let window_builder = WindowBuilder::new().with_transparent(true); + let window_attributes = Window::default_attributes().with_transparent(true); let template = ConfigTemplateBuilder::new().with_alpha_size(8); - let display_builder = DisplayBuilder::new().with_window_builder(Some(window_builder)); + let display_builder = DisplayBuilder::new().with_window_attributes(Some(window_attributes)); let (mut window, gl_config) = display_builder.build(event_loop, template, gl_config_picker)?; diff --git a/glutin_examples/examples/window.rs b/glutin_examples/examples/window.rs index f2c97c6026..3417f2f67b 100644 --- a/glutin_examples/examples/window.rs +++ b/glutin_examples/examples/window.rs @@ -1,7 +1,7 @@ use std::error::Error; -use winit::event_loop::EventLoopBuilder; +use winit::event_loop::EventLoop; fn main() -> Result<(), Box> { - glutin_examples::main(EventLoopBuilder::new().build().unwrap()) + glutin_examples::main(EventLoop::new().unwrap()) } diff --git a/glutin_examples/src/lib.rs b/glutin_examples/src/lib.rs index 62360fc45c..35add39dcc 100644 --- a/glutin_examples/src/lib.rs +++ b/glutin_examples/src/lib.rs @@ -5,15 +5,18 @@ use std::ops::Deref; use gl::types::GLfloat; use raw_window_handle::HasWindowHandle; -use winit::event::{Event, KeyEvent, WindowEvent}; +use winit::application::ApplicationHandler; +use winit::event::{KeyEvent, WindowEvent}; use winit::keyboard::{Key, NamedKey}; -use winit::window::WindowBuilder; +use winit::window::Window; use glutin::config::{Config, ConfigTemplateBuilder}; -use glutin::context::{ContextApi, ContextAttributesBuilder, Version}; +use glutin::context::{ + ContextApi, ContextAttributesBuilder, NotCurrentContext, PossiblyCurrentContext, Version, +}; use glutin::display::GetGlDisplay; use glutin::prelude::*; -use glutin::surface::SwapInterval; +use glutin::surface::{Surface, SwapInterval, WindowSurface}; use glutin_winit::{self, DisplayBuilder, GlWindow}; @@ -25,16 +28,9 @@ pub mod gl { } pub fn main(event_loop: winit::event_loop::EventLoop<()>) -> Result<(), Box> { - // Only Windows requires the window to be present before creating the display. - // Other platforms don't really need one. - // - // XXX if you don't care about running on Android or so you can safely remove - // this condition and always pass the window builder. - let window_builder = cfg!(wgl_backend).then(|| { - WindowBuilder::new() - .with_transparent(true) - .with_title("Glutin triangle gradient example (press Escape to exit)") - }); + 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. @@ -47,140 +43,191 @@ pub fn main(event_loop: winit::event_loop::EventLoop<()>) -> Result<(), Box { - #[cfg(android_platform)] - println!("Android window available"); - - let window = window.take().unwrap_or_else(|| { - let window_builder = WindowBuilder::new() - .with_transparent(true) - .with_title("Glutin triangle gradient example (press Escape to exit)"); - glutin_winit::finalize_window(window_target, window_builder, &gl_config) - .unwrap() - }); - - let attrs = window - .build_surface_attributes(Default::default()) - .expect("Failed to build surface attributes"); - let gl_surface = unsafe { - gl_config.display().create_window_surface(&gl_config, &attrs).unwrap() - }; - - // Make it current. - let gl_context = - not_current_gl_context.take().unwrap().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. - renderer.get_or_insert_with(|| Renderer::new(&gl_display)); - - // Try setting vsync. - if let Err(res) = gl_surface - .set_swap_interval(&gl_context, SwapInterval::Wait(NonZeroU32::new(1).unwrap())) - { - eprintln!("Error setting vsync: {res:?}"); - } + let mut app = App::new(template, display_builder); + event_loop.run_app(&mut app)?; - assert!(state.replace((gl_context, gl_surface, window)).is_none()); - }, - Event::Suspended => { - // 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, ..) = state.take().unwrap(); - assert!(not_current_gl_context - .replace(gl_context.make_not_current().unwrap()) - .is_none()); - }, - Event::WindowEvent { event, .. } => match event { - WindowEvent::Resized(size) => { - if size.width != 0 && size.height != 0 { - // Some platforms like EGL require resizing GL surface to update the size - // 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((gl_context, gl_surface, _)) = &state { - gl_surface.resize( - gl_context, - NonZeroU32::new(size.width).unwrap(), - NonZeroU32::new(size.height).unwrap(), - ); - let renderer = renderer.as_ref().unwrap(); - renderer.resize(size.width as i32, size.height as i32); - } - } - }, - WindowEvent::CloseRequested - | WindowEvent::KeyboardInput { - event: KeyEvent { logical_key: Key::Named(NamedKey::Escape), .. }, - .. - } => window_target.exit(), - _ => (), + app.exit_state +} + +impl ApplicationHandler for App { + fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { + let (mut window, gl_config) = match self.display_builder.clone().build( + event_loop, + self.template.clone(), + gl_config_picker, + ) { + Ok(ok) => ok, + Err(e) => { + self.exit_state = Err(e); + event_loop.exit(); + return; }, - Event::AboutToWait => { - if let Some((gl_context, gl_surface, window)) = &state { - let renderer = renderer.as_ref().unwrap(); - renderer.draw(); - window.request_redraw(); + }; + + 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); + + self.not_current_gl_context.replace(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") + }, + ) + }) + }); + + #[cfg(android_platform)] + println!("Android window available"); + + 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() + }); + + let attrs = window + .build_surface_attributes(Default::default()) + .expect("Failed to build surface attributes"); + let gl_surface = + unsafe { gl_config.display().create_window_surface(&gl_config, &attrs).unwrap() }; + + // Make it current. + let gl_context = + self.not_current_gl_context.take().unwrap().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)); + + // Try setting vsync. + if let Err(res) = gl_surface + .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()); + } - gl_surface.swap_buffers(gl_context).unwrap(); + fn suspended(&mut self, _event_loop: &winit::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()); + } + + fn window_event( + &mut self, + event_loop: &winit::event_loop::ActiveEventLoop, + _window_id: winit::window::WindowId, + event: WindowEvent, + ) { + match event { + WindowEvent::Resized(size) => { + if size.width != 0 && size.height != 0 { + // Some platforms like EGL require resizing GL surface to update the size + // 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() + { + 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); + } } }, + WindowEvent::CloseRequested + | WindowEvent::KeyboardInput { + event: KeyEvent { logical_key: Key::Named(NamedKey::Escape), .. }, + .. + } => event_loop.exit(), _ => (), } - })?; + } + + 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() { + let renderer = self.renderer.as_ref().unwrap(); + renderer.draw(); + window.request_redraw(); + + gl_surface.swap_buffers(gl_context).unwrap(); + } + } +} + +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, +} + +impl App { + fn new(template: ConfigTemplateBuilder, display_builder: DisplayBuilder) -> Self { + Self { + template, + display_builder, + exit_state: Ok(()), + not_current_gl_context: None, + state: None, + renderer: None, + } + } +} - Ok(()) +struct AppState { + gl_context: PossiblyCurrentContext, + gl_surface: Surface, + // NOTE: Window should be dropped after all resources created using its + // raw-window-handle. + window: Window, } // Find the config with the maximum number of samples, so our triangle will be