diff --git a/CHANGELOG.md b/CHANGELOG.md index 200dabea20..2e259fd1a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ - On GLX, fixed handling of errors not directly requested by glutin. - Added `GlConfig::hardware_accelerated` to check if the config is hardware accelerated. - Added `GlContext::context_api` to get the `ContextApi` used by the context. +- Added missing implementations of `Surface::{width,height}` for WGL/CGL +- Fixed crash when accessing context from the off-thread on macOS. +- **Clarified that `make_{,not}_current()`, `GlSurface::width()`, `GlSurface::height()`, and `GlSurface::resize()` could block on macOS.** # Version 0.30.3 diff --git a/glutin/Cargo.toml b/glutin/Cargo.toml index 38ed094c54..e9697c354e 100644 --- a/glutin/Cargo.toml +++ b/glutin/Cargo.toml @@ -30,7 +30,7 @@ glutin_egl_sys = { version = "0.3.1", path = "../glutin_egl_sys", optional = tru glutin_wgl_sys = { version = "0.3.0", path = "../glutin_wgl_sys", optional = true } [target.'cfg(windows)'.dependencies.windows-sys] -version = "0.36" +version = "0.45" features = [ "Win32_Foundation", "Win32_Graphics_Gdi", @@ -53,6 +53,7 @@ x11-dl = { version = "2.20.0", optional = true } cgl = "0.3.2" core-foundation = "0.9.3" objc2 = "=0.3.0-beta.3" +dispatch = "0.2.0" [build-dependencies] cfg_aliases = "0.1.1" diff --git a/glutin/src/api/cgl/appkit.rs b/glutin/src/api/cgl/appkit.rs index cff1b27184..33a28da184 100644 --- a/glutin/src/api/cgl/appkit.rs +++ b/glutin/src/api/cgl/appkit.rs @@ -3,8 +3,12 @@ //! TODO: Move this to another crate. #![allow(dead_code)] #![allow(non_snake_case)] + +use std::ops::Deref; + +use dispatch::Queue; use objc2::encode::{Encoding, RefEncode}; -use objc2::foundation::{NSInteger, NSObject}; +use objc2::foundation::{is_main_thread, NSInteger, NSObject}; use objc2::rc::{Id, Shared}; use objc2::{extern_class, extern_methods, msg_send_id, ClassType}; @@ -12,6 +16,33 @@ pub type GLint = i32; pub enum CGLContextObj {} +// XXX borrowed from winit. + +// Unsafe wrapper type that allows us to dispatch things that aren't Send. +// This should *only* be used to dispatch to the main queue. +// While it is indeed not guaranteed that these types can safely be sent to +// other threads, we know that they're safe to use on the main thread. +pub(crate) struct MainThreadSafe(pub(crate) T); + +unsafe impl Send for MainThreadSafe {} + +impl Deref for MainThreadSafe { + type Target = T; + + fn deref(&self) -> &T { + &self.0 + } +} + +/// Run closure on the main thread. +pub(crate) fn run_on_main(f: impl FnOnce() -> R + Send) -> R { + if is_main_thread() { + f() + } else { + Queue::main().exec_sync(f) + } +} + unsafe impl RefEncode for CGLContextObj { const ENCODING_REF: Encoding = Encoding::Pointer(&Encoding::Struct("_CGLContextObject", &[])); } diff --git a/glutin/src/api/cgl/context.rs b/glutin/src/api/cgl/context.rs index 81c509120e..79860df8be 100644 --- a/glutin/src/api/cgl/context.rs +++ b/glutin/src/api/cgl/context.rs @@ -15,7 +15,7 @@ use crate::prelude::*; use crate::private::Sealed; use crate::surface::{SurfaceTypeTrait, SwapInterval}; -use super::appkit::{NSOpenGLCPSwapInterval, NSOpenGLContext}; +use super::appkit::{run_on_main, MainThreadSafe, NSOpenGLCPSwapInterval, NSOpenGLContext}; use super::config::Config; use super::display::Display; use super::surface::Surface; @@ -218,9 +218,15 @@ impl ContextInner { fn make_current(&self, surface: &Surface) -> Result<()> { autoreleasepool(|_| { - self.raw.update(); + self.update(); self.raw.makeCurrentContext(); - unsafe { self.raw.setView(Some(&surface.ns_view)) }; + let raw = MainThreadSafe(&self.raw); + let ns_view = MainThreadSafe(&surface.ns_view); + + run_on_main(move || unsafe { + raw.setView(Some(*ns_view)); + }); + Ok(()) }) } @@ -241,7 +247,10 @@ impl ContextInner { } pub(crate) fn update(&self) { - self.raw.update(); + let raw = MainThreadSafe(&self.raw); + run_on_main(move || { + raw.update(); + }); } pub(crate) fn flush_buffer(&self) -> Result<()> { @@ -256,7 +265,7 @@ impl ContextInner { } fn make_not_current(&self) -> Result<()> { - self.raw.update(); + self.update(); NSOpenGLContext::clearCurrentContext(); Ok(()) } diff --git a/glutin/src/api/cgl/surface.rs b/glutin/src/api/cgl/surface.rs index dc37e72200..c091d07448 100644 --- a/glutin/src/api/cgl/surface.rs +++ b/glutin/src/api/cgl/surface.rs @@ -4,7 +4,8 @@ use std::fmt; use std::marker::PhantomData; use std::num::NonZeroU32; -use objc2::foundation::NSObject; +use objc2::foundation::{CGFloat, NSObject, NSRect}; +use objc2::msg_send; use objc2::rc::{Id, Shared}; use raw_window_handle::RawWindowHandle; @@ -17,6 +18,7 @@ use crate::surface::{ SurfaceTypeTrait, SwapInterval, WindowSurface, }; +use super::appkit::{run_on_main, MainThreadSafe}; use super::config::Config; use super::context::PossiblyCurrentContext; use super::display::Display; @@ -52,11 +54,28 @@ impl Display { }, }; - // SAFETY: Validity of the view is ensured by caller - let ns_view = - unsafe { Id::retain(native_window.ns_view.cast()) }.expect("NSView to be non-null"); - let surface = - Surface { display: self.clone(), config: config.clone(), ns_view, _ty: PhantomData }; + // SAFETY: Validity of the view and window is ensured by caller + let ns_view = if let Some(ns_view) = unsafe { Id::retain(native_window.ns_view.cast()) } { + ns_view + } else { + return Err(ErrorKind::NotSupported("ns_view of provided native window is nil").into()); + }; + + let ns_window = if let Some(ns_window) = + unsafe { Id::retain(native_window.ns_window.cast()) } + { + ns_window + } else { + return Err(ErrorKind::NotSupported("ns_view of provided native window is nil").into()); + }; + + let surface = Surface { + display: self.clone(), + config: config.clone(), + ns_view, + ns_window, + _ty: PhantomData, + }; Ok(surface) } } @@ -66,6 +85,7 @@ pub struct Surface { display: Display, config: Config, pub(crate) ns_view: Id, + ns_window: Id, _ty: PhantomData, } @@ -78,11 +98,23 @@ impl GlSurface for Surface { } fn width(&self) -> Option { - None + let ns_window = MainThreadSafe(&self.ns_window); + let ns_view = MainThreadSafe(&self.ns_view); + run_on_main(move || unsafe { + let scale_factor: CGFloat = msg_send![*ns_window, backingScaleFactor]; + let frame: NSRect = msg_send![*ns_view, frame]; + Some((frame.size.width * scale_factor) as u32) + }) } fn height(&self) -> Option { - None + let ns_window = MainThreadSafe(&self.ns_window); + let ns_view = MainThreadSafe(&self.ns_view); + run_on_main(move || unsafe { + let scale_factor: CGFloat = msg_send![*ns_window, backingScaleFactor]; + let frame: NSRect = msg_send![*ns_view, frame]; + Some((frame.size.height * scale_factor) as u32) + }) } fn is_single_buffered(&self) -> bool { diff --git a/glutin/src/api/wgl/surface.rs b/glutin/src/api/wgl/surface.rs index a09ed05e66..30d2c7334d 100644 --- a/glutin/src/api/wgl/surface.rs +++ b/glutin/src/api/wgl/surface.rs @@ -1,14 +1,15 @@ //! A wrapper around `HWND` used for GL operations. -use std::fmt; use std::io::Error as IoError; use std::marker::PhantomData; use std::num::NonZeroU32; +use std::{fmt, mem}; use raw_window_handle::RawWindowHandle; -use windows_sys::Win32::Foundation::HWND; +use windows_sys::Win32::Foundation::{HWND, RECT}; use windows_sys::Win32::Graphics::Gdi::HDC; use windows_sys::Win32::Graphics::{Gdi as gdi, OpenGL as gl}; +use windows_sys::Win32::UI::WindowsAndMessaging::GetClientRect; use crate::config::GetGlConfig; use crate::display::{DisplayFeatures, GetGlDisplay}; @@ -93,11 +94,21 @@ impl GlSurface for Surface { } fn width(&self) -> Option { - None + let mut rect: RECT = unsafe { mem::zeroed() }; + if unsafe { GetClientRect(self.hwnd, &mut rect) } == false.into() { + None + } else { + Some((rect.right - rect.left) as u32) + } } fn height(&self) -> Option { - None + let mut rect: RECT = unsafe { mem::zeroed() }; + if unsafe { GetClientRect(self.hwnd, &mut rect) } == false.into() { + None + } else { + Some((rect.bottom - rect.top) as u32) + } } fn is_single_buffered(&self) -> bool { diff --git a/glutin/src/context.rs b/glutin/src/context.rs index 367ef7304c..7ed3d8e874 100644 --- a/glutin/src/context.rs +++ b/glutin/src/context.rs @@ -59,6 +59,10 @@ pub trait NotCurrentGlContextSurfaceAccessor: Sealed { /// Make [`Self::Surface`] on the calling thread producing the /// [`Self::PossiblyCurrentContext`] indicating that the context could /// be current on the theard. + /// + /// # Platform specific + /// + /// **macOS:** - **This will block if your main thread is blocked.** fn make_current(self, surface: &Self::Surface) -> Result; /// The same as [`Self::make_current`], but provides a way to set read and @@ -85,6 +89,10 @@ pub trait PossiblyCurrentGlContext: Sealed { /// Make the context not current to the current thread and returns a /// [`Self::NotCurrentContext`] to indicate that the context is a not /// current to allow sending it to the different thread. + /// + /// # Platform specific + /// + /// **macOS:** - **This will block if your main thread is blocked.** fn make_not_current(self) -> Result; } @@ -94,6 +102,10 @@ pub trait PossiblyCurrentContextGlSurfaceAccessor: Sealed { type Surface: GlSurface; /// Make [`Self::Surface`] current on the calling thread. + /// + /// # Platform specific + /// + /// **macOS:** - **This will block if your main thread is blocked.** fn make_current(&self, surface: &Self::Surface) -> Result<()>; /// The same as [`Self::make_current`] but provides a way to set read and diff --git a/glutin/src/surface.rs b/glutin/src/surface.rs index 1358636404..b2008604ba 100644 --- a/glutin/src/surface.rs +++ b/glutin/src/surface.rs @@ -32,13 +32,21 @@ pub trait GlSurface: Sealed { /// its age. In both cases you must redraw the entire buffer. fn buffer_age(&self) -> u32; - /// The width of the underlying surface. + /// The **physical** width of the underlying surface. fn width(&self) -> Option; - /// The height of the underlying surface. + /// The **physical** height of the underlying surface. + /// + /// # Platform specific + /// + /// **macOS:** - **This will block if your main thread is blocked.** fn height(&self) -> Option; /// Check whether the surface is single buffered. + /// + /// # Platform specific + /// + /// **macOS:** - **This will block if your main thread is blocked.** fn is_single_buffered(&self) -> bool; /// Swaps the underlying back buffers when the surface is not single @@ -68,7 +76,7 @@ pub trait GlSurface: Sealed { /// # Platform specific /// /// **Wayland:** - resizes the surface. - /// **macOS:** - resizes the context to fit the surface. + /// **macOS:** - **This will block if your main thread is blocked.** /// **Other:** - no op. fn resize(&self, context: &Self::Context, width: NonZeroU32, height: NonZeroU32) where diff --git a/glutin_egl_sys/Cargo.toml b/glutin_egl_sys/Cargo.toml index ad21e53ebe..d05fb1c593 100644 --- a/glutin_egl_sys/Cargo.toml +++ b/glutin_egl_sys/Cargo.toml @@ -13,7 +13,7 @@ edition = "2021" gl_generator = "0.14" [target.'cfg(windows)'.dependencies.windows-sys] -version = "0.36" +version = "0.45" features = [ "Win32_Foundation", ]