diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 515c125a4e..d24ef46c1c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,18 +18,23 @@ jobs: name: Check formatting runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: hecrj/setup-rust-action@v1 + - uses: taiki-e/checkout-action@v1 + - uses: dtolnay/rust-toolchain@nightly with: - rust-version: nightly components: rustfmt - uses: taiki-e/install-action@v2 with: tool: typos-cli - name: Check Formatting - run: cargo +nightly fmt --all -- --check - - name: Run Typos + run: cargo fmt --all -- --check + - name: run typos run: typos + - name: Typos info + if: failure() + run: | + echo 'To fix typos, please run `typos -w`' + echo 'To check for a diff, run `typos`' + echo 'You can find typos here: https://crates.io/crates/typos' tests: name: Tests diff --git a/CHANGELOG.md b/CHANGELOG.md index 18b0938cd6..a3fe94f63b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Unreleased +# Version 0.32.1 + +- Fixed EGL's `Device::query_devices()` being too strict about required extensions. +- Fixed crash in `EglGetProcAddress` on Win32-x86 platform due to wrong calling convention. +- Fixed EGL's `Display::device()` always returning an error due to invalid pointer-argument passing inside. +- Fixed EGL's `Display::new()` making an `EGLDisplay::Khr` when the EGL version for the display is 1.4 or lower. +- Added `Device::drm_device_node_path()` and `Device::drm_render_device_node_path()` getters to EGL via `EGL_EXT_device_drm`. +- Added support for `DrmDisplayHandle` in EGL's `Display::with_device()` using `EGL_DRM_MASTER_FD_EXT` from `EGL_EXT_device_drm`. +- Properly set up OpenGL-specific stuff on the `NSView`, instead of relying on Winit to do it. + # Version 0.32.0 - **Breaking:** updated `raw-window-handle` dependency to `0.6`. diff --git a/glutin/Cargo.toml b/glutin/Cargo.toml index ecc94998cb..0ed20244c5 100644 --- a/glutin/Cargo.toml +++ b/glutin/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "glutin" -version = "0.32.0" +version = "0.32.1" authors = ["Kirill Chibisov "] description = "Cross-platform OpenGL context provider." keywords = ["windowing", "opengl", "egl"] @@ -66,10 +66,12 @@ features = [ [target.'cfg(any(target_os = "macos"))'.dependencies.objc2-app-kit] version = "0.2.0" features = [ + "NSApplication", "NSResponder", "NSView", "NSWindow", "NSOpenGL", + "NSOpenGLView", ] [build-dependencies] diff --git a/glutin/src/api/cgl/surface.rs b/glutin/src/api/cgl/surface.rs index 26044ea77c..65b87b4243 100644 --- a/glutin/src/api/cgl/surface.rs +++ b/glutin/src/api/cgl/surface.rs @@ -5,7 +5,7 @@ use std::marker::PhantomData; use std::num::NonZeroU32; use objc2::rc::Id; -use objc2_app_kit::NSView; +use objc2_app_kit::{NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSView}; use objc2_foundation::{run_on_main, MainThreadBound, MainThreadMarker}; use raw_window_handle::RawWindowHandle; @@ -60,12 +60,29 @@ impl Display { // SAFETY: Validity of the view and window is ensured by caller // This function makes sure the window is non null. let ns_view = if let Some(ns_view) = - unsafe { Id::retain(native_window.ns_view.as_ptr().cast()) } + unsafe { Id::retain(native_window.ns_view.as_ptr().cast::()) } { ns_view } else { return Err(ErrorKind::NotSupported("ns_view of provided native window is nil").into()); }; + + // The default value of `wantsBestResolutionOpenGLSurface` is `false` when + // linked with the macOS 10.14 SDK and `true` if linked with a macOS 10.15 SDK + // or newer. We always set it to `true` because we want High DPI surfaces, and + // we want to avoid this confusing default system value. + #[allow(deprecated)] + ns_view.setWantsBestResolutionOpenGLSurface(true); + + // On Mojave, views apparently automatically become layer-backed shortly after + // being added to a window. Changing the layer-backedness of a view breaks the + // association between the view and its associated OpenGL context. To work + // around this, we explicitly make the view layer-backed up front so that AppKit + // doesn't do it itself and break the association with its context. + if unsafe { NSAppKitVersionNumber }.floor() > NSAppKitVersionNumber10_12 { + ns_view.setWantsLayer(true); + } + let ns_view = MainThreadBound::new(ns_view, mtm); let surface = Surface { diff --git a/glutin/src/api/egl/device.rs b/glutin/src/api/egl/device.rs index 498651dca7..71d9476ee3 100644 --- a/glutin/src/api/egl/device.rs +++ b/glutin/src/api/egl/device.rs @@ -1,7 +1,8 @@ //! Everything related to `EGLDevice`. use std::collections::HashSet; -use std::ffi::{c_void, CStr}; +use std::ffi::CStr; +use std::path::Path; use std::ptr; use glutin_egl_sys::egl; @@ -17,10 +18,15 @@ use super::{Egl, EGL}; pub struct Device { inner: EGLDeviceEXT, extensions: HashSet<&'static str>, - name: Option, - vendor: Option, + name: Option<&'static str>, + vendor: Option<&'static str>, } +// SAFETY: An EGLDevice is immutable and valid for the lifetime of the EGL +// library. +unsafe impl Send for Device {} +unsafe impl Sync for Device {} + impl Device { /// Query the available devices. /// @@ -33,19 +39,25 @@ impl Device { None => return Err(ErrorKind::NotFound.into()), }; - let no_display_extensions = + let client_extensions = CLIENT_EXTENSIONS.get_or_init(|| get_extensions(egl, egl::NO_DISPLAY)); - // Querying devices requires EGL_EXT_device_enumeration and - // EGL_EXT_device_query. - // - // Or we can check for the EGL_EXT_device_base extension since it contains both - // extensions. - if (!no_display_extensions.contains("EGL_EXT_device_enumeration") - && !no_display_extensions.contains("EGL_EXT_device_query")) - || !no_display_extensions.contains("EGL_EXT_device_base") - { - return Err(ErrorKind::NotSupported("EGL does not support EGL_EXT_device_base").into()); + // Querying devices requires EGL_EXT_device_enumeration or EGL_EXT_device_base. + if !client_extensions.contains("EGL_EXT_device_base") { + if !client_extensions.contains("EGL_EXT_device_enumeration") { + return Err(ErrorKind::NotSupported( + "Enumerating devices is not supported by the EGL instance", + ) + .into()); + } + // EGL_EXT_device_enumeration depends on EGL_EXT_device_query, + // so also check that just in case. + if !client_extensions.contains("EGL_EXT_device_query") { + return Err(ErrorKind::NotSupported( + "EGL_EXT_device_enumeration without EGL_EXT_device_query, buggy driver?", + ) + .into()); + } } let mut device_count = 0; @@ -87,7 +99,7 @@ impl Device { /// /// These extensions are distinct from the display extensions and should not /// be used interchangeably. - pub fn extensions(&self) -> &HashSet<&str> { + pub fn extensions(&self) -> &HashSet<&'static str> { &self.extensions } @@ -95,31 +107,79 @@ impl Device { /// /// This function will return [`None`] if the `EGL_EXT_device_query_name` /// device extension is not available. - pub fn name(&self) -> Option<&str> { - self.name.as_deref() + pub fn name(&self) -> Option<&'static str> { + self.name } /// Get the vendor of the device. /// /// This function will return [`None`] if the `EGL_EXT_device_query_name` /// device extension is not available. - pub fn vendor(&self) -> Option<&str> { - self.vendor.as_deref() + pub fn vendor(&self) -> Option<&'static str> { + self.vendor } /// Get a raw handle to the `EGLDevice`. - pub fn raw_device(&self) -> *const c_void { + pub fn raw_device(&self) -> EGLDeviceEXT { self.inner } -} -// SAFETY: An EGLDevice is immutable and valid for the lifetime of the EGL -// library. -unsafe impl Send for Device {} -unsafe impl Sync for Device {} + /// Get the DRM primary or render device node path for this + /// [`EGLDeviceEXT`]. + /// + /// Requires the [`EGL_EXT_device_drm`] extension. + /// + /// If the [`EGL_EXT_device_drm_render_node`] extension is supported, this + /// is guaranteed to return the **primary** device node path, or [`None`]. + /// Consult [`Self::drm_render_device_node_path()`] to retrieve the + /// **render** device node path. + /// + /// [`EGL_EXT_device_drm`]: https://registry.khronos.org/EGL/extensions/EXT/EGL_EXT_device_drm.txt + /// [`EGL_EXT_device_drm_render_node`]: https://registry.khronos.org/EGL/extensions/EXT/EGL_EXT_device_drm_render_node.txt + pub fn drm_device_node_path(&self) -> Option<&'static Path> { + if !self.extensions.contains("EGL_EXT_device_drm") { + return None; + } -impl Device { - unsafe fn query_string(egl_device: *const c_void, name: egl::types::EGLenum) -> Option { + // SAFETY: We pass a valid EGLDevice pointer, and validated that the enum name + // is valid because the extension is present. + unsafe { Self::query_string(self.raw_device(), egl::DRM_DEVICE_FILE_EXT) }.map(Path::new) + } + + /// Get the DRM render device node path for this [`EGLDeviceEXT`]. + /// + /// Requires the [`EGL_EXT_device_drm_render_node`] extension. + /// + /// If the [`EGL_EXT_device_drm`] extension is supported in addition to + /// [`EGL_EXT_device_drm_render_node`], + /// consult [`Self::drm_device_node_path()`] to retrieve the **primary** + /// device node path. + /// + /// [`EGL_EXT_device_drm`]: https://registry.khronos.org/EGL/extensions/EXT/EGL_EXT_device_drm.txt + /// [`EGL_EXT_device_drm_render_node`]: https://registry.khronos.org/EGL/extensions/EXT/EGL_EXT_device_drm_render_node.txt + pub fn drm_render_device_node_path(&self) -> Option<&'static Path> { + if !self.extensions.contains("EGL_EXT_device_drm_render_node") { + return None; + } + + const EGL_DRM_RENDER_NODE_PATH_EXT: egl::types::EGLenum = 0x3377; + // SAFETY: We pass a valid EGLDevice pointer, and validated that the enum name + // is valid because the extension is present. + unsafe { Self::query_string(self.raw_device(), EGL_DRM_RENDER_NODE_PATH_EXT) } + .map(Path::new) + } + + /// # Safety + /// The caller must pass a valid `egl_device` pointer and must ensure that + /// `name` is valid for this device, i.e. by guaranteeing that the + /// extension that introduces it is present. + /// + /// The returned string is `'static` for the lifetime of the globally loaded + /// EGL library in [`EGL`]. + unsafe fn query_string( + egl_device: EGLDeviceEXT, + name: egl::types::EGLenum, + ) -> Option<&'static str> { let egl = super::EGL.as_ref().unwrap(); // SAFETY: The caller has ensured the name is valid. @@ -129,10 +189,10 @@ impl Device { return None; } - unsafe { CStr::from_ptr(ptr) }.to_str().ok().map(String::from) + unsafe { CStr::from_ptr(ptr) }.to_str().ok() } - pub(crate) fn from_ptr(egl: &Egl, ptr: *const c_void) -> Result { + pub(crate) fn from_ptr(egl: &Egl, ptr: EGLDeviceEXT) -> Result { // SAFETY: The EGL specification guarantees the returned string is // static and null terminated: // diff --git a/glutin/src/api/egl/display.rs b/glutin/src/api/egl/display.rs index 0cae10d9b4..3cc47653bb 100644 --- a/glutin/src/api/egl/display.rs +++ b/glutin/src/api/egl/display.rs @@ -2,20 +2,18 @@ use std::collections::HashSet; use std::ffi::{self, CStr}; +use std::fmt; use std::mem::MaybeUninit; use std::ops::Deref; use std::os::raw::c_char; use std::sync::Arc; -use std::{fmt, ptr}; use glutin_egl_sys::egl; use glutin_egl_sys::egl::types::{EGLAttrib, EGLDisplay, EGLint}; use once_cell::sync::OnceCell; -use raw_window_handle::RawDisplayHandle; -#[cfg(x11_platform)] -use raw_window_handle::XlibDisplayHandle; +use raw_window_handle::{RawDisplayHandle, XlibDisplayHandle}; use crate::config::ConfigTemplate; use crate::context::Version; @@ -91,8 +89,10 @@ impl Display { /// /// # Safety /// - /// If `raw_display` is [`Some`], `raw_display` must point to a valid system - /// display. + /// If `raw_display` is [`Some`], `raw_display` must point to a valid + /// [`RawDisplayHandle::Drm`]. The provided + /// [`raw_display_handle::DrmDisplayHandle.fd`] may be closed after calling + /// this function. pub unsafe fn with_device( device: &Device, raw_display: Option, @@ -102,13 +102,6 @@ impl Display { None => return Err(ErrorKind::NotFound.into()), }; - if raw_display.is_some() { - return Err(ErrorKind::NotSupported( - "Display::with_device does not support a `raw_display` argument yet", - ) - .into()); - } - if !egl.GetPlatformDisplayEXT.is_loaded() { return Err(ErrorKind::NotSupported("eglGetPlatformDisplayEXT is not supported").into()); } @@ -129,9 +122,22 @@ impl Display { let mut attrs = Vec::::with_capacity(3); - // TODO: Some extensions exist like EGL_EXT_device_drm which allow specifying - // which DRM master fd to use under the hood by the implementation. This would - // mean there would need to be an unsafe equivalent to this function. + match raw_display { + Some(RawDisplayHandle::Drm(handle)) + if device.extensions().contains("EGL_EXT_device_drm") => + { + attrs.push(egl::DRM_MASTER_FD_EXT as EGLint); + attrs.push(handle.fd as EGLint); + }, + Some(_) => { + return Err(ErrorKind::NotSupported( + "`egl::display::Display::with_device()` does not support \ + non-`DrmDisplayHandle` `RawDisplayHandle`s", + ) + .into()) + }, + None => {}, + }; // Push at the end so we can pop it on failure let mut has_display_reference = extensions.contains("EGL_KHR_display_reference"); @@ -193,12 +199,12 @@ impl Display { .into()); } - let device = ptr::null_mut(); + let mut device = MaybeUninit::uninit(); if unsafe { self.inner.egl.QueryDisplayAttribEXT( *self.inner.raw, egl::DEVICE_EXT as EGLint, - device as *mut _, + device.as_mut_ptr(), ) } == egl::FALSE { @@ -211,6 +217,13 @@ impl Display { })); } + let device = unsafe { device.assume_init() } as egl::types::EGLDeviceEXT; + debug_assert_ne!( + device, + egl::NO_DEVICE_EXT, + "eglQueryDisplayAttribEXT(EGL_DEVICE_EXT) should never return EGL_NO_DEVICE_EXT on \ + success" + ); Device::from_ptr(self.inner.egl, device) } @@ -247,13 +260,11 @@ impl Display { let mut attrs = Vec::::with_capacity(5); let (platform, display) = match display { - #[cfg(wayland_platform)] RawDisplayHandle::Wayland(handle) if extensions.contains("EGL_KHR_platform_wayland") => { (egl::PLATFORM_WAYLAND_KHR, handle.display.as_ptr()) }, - #[cfg(x11_platform)] RawDisplayHandle::Xlib(handle) if extensions.contains("EGL_KHR_platform_x11") => { attrs.push(egl::PLATFORM_X11_SCREEN_KHR as EGLAttrib); attrs.push(handle.screen as EGLAttrib); @@ -265,6 +276,12 @@ impl Display { RawDisplayHandle::Gbm(handle) if extensions.contains("EGL_KHR_platform_gbm") => { (egl::PLATFORM_GBM_KHR, handle.gbm_device.as_ptr()) }, + RawDisplayHandle::Drm(_) => { + return Err(ErrorKind::NotSupported( + "`DrmDisplayHandle` must be used with `egl::display::Display::with_device()`", + ) + .into()) + }, RawDisplayHandle::Android(_) if extensions.contains("EGL_KHR_platform_android") => { (egl::PLATFORM_ANDROID_KHR, egl::DEFAULT_DISPLAY as *mut _) }, @@ -321,13 +338,11 @@ impl Display { let mut attrs = Vec::::with_capacity(5); let mut legacy = false; let (platform, display) = match display { - #[cfg(wayland_platform)] RawDisplayHandle::Wayland(handle) if extensions.contains("EGL_EXT_platform_wayland") => { (egl::PLATFORM_WAYLAND_EXT, handle.display.as_ptr()) }, - #[cfg(x11_platform)] RawDisplayHandle::Xlib(handle) if extensions.contains("EGL_EXT_platform_x11") => { attrs.push(egl::PLATFORM_X11_SCREEN_EXT as EGLint); attrs.push(handle.screen as EGLint); @@ -336,7 +351,6 @@ impl Display { handle.display.map_or(egl::DEFAULT_DISPLAY as *mut _, |d| d.as_ptr()), ) }, - #[cfg(x11_platform)] RawDisplayHandle::Xcb(handle) if extensions.contains("EGL_MESA_platform_xcb") || extensions.contains("EGL_EXT_platform_xcb") => @@ -351,6 +365,12 @@ impl Display { RawDisplayHandle::Gbm(handle) if extensions.contains("EGL_MESA_platform_gbm") => { (egl::PLATFORM_GBM_MESA, handle.gbm_device.as_ptr()) }, + RawDisplayHandle::Drm(_) => { + return Err(ErrorKind::NotSupported( + "`DrmDisplayHandle` must be used with `egl::display::Display::with_device()`", + ) + .into()) + }, RawDisplayHandle::Windows(..) if extensions.contains("EGL_ANGLE_platform_angle") => { // Only CreateWindowSurface appears to work with Angle. legacy = true; @@ -412,7 +432,12 @@ impl Display { fn get_display(egl: &Egl, display: RawDisplayHandle) -> Result { let display = match display { RawDisplayHandle::Gbm(handle) => handle.gbm_device.as_ptr(), - #[cfg(x11_platform)] + RawDisplayHandle::Drm(_) => { + return Err(ErrorKind::NotSupported( + "`DrmDisplayHandle` must be used with `egl::display::Display::with_device()`", + ) + .into()) + }, RawDisplayHandle::Xlib(XlibDisplayHandle { display, .. }) => { display.map_or(egl::DEFAULT_DISPLAY as *mut _, |d| d.as_ptr()) }, @@ -485,6 +510,26 @@ impl Display { Version::new(major as u8, minor as u8) }; + let display = match display { + // `eglGetPlatformDisplay` and `GetPlatformDisplayEXT` aren't really differentiated, + // we must check if the version of the initialized display is not sensible for the + // EglDisplay type and downgrade it if so. + EglDisplay::Khr(display) if version <= Version { major: 1, minor: 4 } => { + let client_extensions = CLIENT_EXTENSIONS.get().unwrap(); + if client_extensions.contains("EGL_EXT_platform_base") + && (version == Version { major: 1, minor: 4 }) + { + // `EGL_EXT_platform_base` requires EGL 1.4 per specification; we cannot safely + // presume that an `Ext` display would be valid for older versions. + EglDisplay::Ext(display) + } else { + EglDisplay::Legacy(display) + } + }, + // We do not do anything otherwise. + display => display, + }; + // Load extensions. let display_extensions = get_extensions(egl, *display); let features = Self::extract_display_features(&display_extensions, version); diff --git a/glutin/src/api/egl/mod.rs b/glutin/src/api/egl/mod.rs index a8c9e940f5..356a1a40f5 100644 --- a/glutin/src/api/egl/mod.rs +++ b/glutin/src/api/egl/mod.rs @@ -28,6 +28,9 @@ pub mod device; pub mod display; pub mod surface; +// WARNING: If this implementation is ever changed to unload or replace the +// library, note that public API functions currently retirm `&'static str`ings +// out of it, which would become invalid. pub(crate) static EGL: Lazy> = Lazy::new(|| { #[cfg(windows)] let paths = ["libEGL.dll", "atioglxx.dll"]; @@ -38,7 +41,7 @@ pub(crate) static EGL: Lazy> = Lazy::new(|| { unsafe { SymWrapper::new(&paths).map(Egl).ok() } }); -type EglGetProcAddress = unsafe extern "C" fn(*const ffi::c_void) -> *const ffi::c_void; +type EglGetProcAddress = unsafe extern "system" fn(*const ffi::c_void) -> *const ffi::c_void; static EGL_GET_PROC_ADDRESS: OnceCell> = OnceCell::new(); /// EGL interface. diff --git a/glutin_examples/Cargo.toml b/glutin_examples/Cargo.toml index 281e1bdbaa..4ff9927a18 100644 --- a/glutin_examples/Cargo.toml +++ b/glutin_examples/Cargo.toml @@ -24,6 +24,7 @@ glutin-winit = { path = "../glutin-winit", default-features = false } png = { version = "0.17.6", optional = true } raw-window-handle = "0.6" winit = { version = "0.30.0", default-features = false, features = ["rwh_06"] } +drm = { version = "0.12", optional = true } [target.'cfg(target_os = "android")'.dependencies] winit = { version = "0.30.0", default-features = false, features = ["android-native-activity", "rwh_06"] } @@ -39,3 +40,7 @@ crate-type = ["cdylib"] [[example]] name = "egl_device" required-features = ["egl"] + +[[example]] +name = "drm" +required-features = ["egl", "drm"] diff --git a/glutin_examples/examples/drm.rs b/glutin_examples/examples/drm.rs new file mode 100644 index 0000000000..ce1bc9bdc8 --- /dev/null +++ b/glutin_examples/examples/drm.rs @@ -0,0 +1,30 @@ +use std::fs::OpenOptions; +use std::os::fd::AsRawFd; + +use glutin::api::egl; +use raw_window_handle::{DrmDisplayHandle, RawDisplayHandle}; + +fn main() { + let devices = egl::device::Device::query_devices().expect("Query EGL devices"); + for egl_device in devices { + dbg!(&egl_device); + dbg!(egl_device.drm_render_device_node_path()); + let Some(drm) = dbg!(egl_device.drm_device_node_path()) else { + continue; + }; + let fd = OpenOptions::new() + .read(true) + .write(true) + .open(drm) + .expect("Open DRM device with Read/Write permissions"); + + // https://registry.khronos.org/EGL/extensions/EXT/EGL_EXT_device_drm.txt: + // Providing DRM_MASTER_FD is only to cover cases where EGL might fail to open + // it itself. + let rdh = RawDisplayHandle::Drm(DrmDisplayHandle::new(fd.as_raw_fd())); + + let egl_display = unsafe { egl::display::Display::with_device(&egl_device, Some(rdh)) } + .expect("Create EGL Display"); + dbg!(&egl_display); + } +} diff --git a/glutin_examples/examples/egl_device.rs b/glutin_examples/examples/egl_device.rs index 191eb657eb..c615c31b94 100644 --- a/glutin_examples/examples/egl_device.rs +++ b/glutin_examples/examples/egl_device.rs @@ -112,7 +112,7 @@ mod example { } let path = Path::new(IMG_PATH); - let file = OpenOptions::new().write(true).truncate(true).open(path).unwrap(); + let file = OpenOptions::new().create(true).write(true).truncate(true).open(path).unwrap(); let mut encoder = png::Encoder::new(file, 1280, 720); encoder.set_depth(png::BitDepth::Eight); diff --git a/glutin_examples/src/lib.rs b/glutin_examples/src/lib.rs index 4763cf670e..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); - - 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") + 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()) @@ -120,42 +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 = - 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)); + 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, ) { @@ -165,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); } @@ -184,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(); @@ -195,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, } @@ -219,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.