diff --git a/CHANGELOG.md b/CHANGELOG.md index 368aa0a386..a7b25cd449 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Unreleased +- Add `api::egl::sync::Sync` to wrap `EGLSync` +- Add ability to import and export an a sync fd from a `api::egl::sync::Sync` + # Version 0.31.1 - Fixed `CGLContextObj` having an invalid encoding on newer macOS versions. diff --git a/glutin/src/api/egl/display.rs b/glutin/src/api/egl/display.rs index 1481eb4478..c9b28921c3 100644 --- a/glutin/src/api/egl/display.rs +++ b/glutin/src/api/egl/display.rs @@ -231,6 +231,91 @@ impl Display { } } + /// Create a sync. + /// + /// Creating a sync fence requires that an EGL context is currently bound. + /// Otherwise [`ErrorKind::BadMatch`] is returned. + /// + /// This function returns [`Err`] if the EGL version is not at least 1.5 + /// or `EGL_KHR_fence_sync` is not available. An error is also returned if + /// native fences are not supported. + pub fn create_sync(&self, native: bool) -> Result { + if self.inner.version < super::VERSION_1_5 + && !self.inner.display_extensions.contains("EGL_KHR_fence_sync") + { + return Err(ErrorKind::NotSupported("Sync objects are not supported").into()); + } + + if native && !self.inner.display_extensions.contains("EGL_ANDROID_native_fence_sync") { + return Err(ErrorKind::NotSupported("Native fences are not supported").into()); + } + + let ty = if native { egl::SYNC_NATIVE_FENCE_ANDROID } else { egl::SYNC_FENCE_KHR }; + let sync = unsafe { self.inner.egl.CreateSyncKHR(*self.inner.raw, ty, ptr::null()) }; + + if sync == egl::NO_SYNC { + return Err(super::check_error().err().unwrap()); + } + + Ok(super::sync::Sync(Arc::new(super::sync::Inner { + inner: sync, + display: self.inner.clone(), + }))) + } + + /// Import a sync fd into EGL. + /// + /// Glutin will duplicate the sync fd being imported since EGL assumes + /// ownership of the file descriptor. If the file descriptor could not + /// be cloned, then [`ErrorKind::BadParameter`] is returned. + /// + /// This function returns [`ErrorKind::NotSupported`] if the + /// `EGL_ANDROID_native_fence_sync` extension is not available. + #[cfg(unix)] + pub fn import_sync( + &self, + fd: std::os::unix::prelude::BorrowedFd<'_>, + ) -> Result { + use std::mem; + use std::os::unix::prelude::AsRawFd; + + // The specification states that EGL_KHR_fence_sync must be available, + // and therefore does not need to be tested. + if !self.inner.display_extensions.contains("EGL_ANDROID_native_fence_sync") { + return Err(ErrorKind::NotSupported("Importing a sync fd is not supported").into()); + } + + let import = fd.try_clone_to_owned().map_err(|_| ErrorKind::BadParameter)?; + + let attrs: [EGLint; 3] = + [egl::SYNC_NATIVE_FENCE_FD_ANDROID as EGLint, import.as_raw_fd(), egl::NONE as EGLint]; + + // SAFETY: + // - The EGL implementation advertised EGL_ANDROID_native_fence_sync + // - The fd being imported is an OwnedFd, meaning ownership is transfered to + // EGL. + let sync = unsafe { + self.inner.egl.CreateSyncKHR( + *self.inner.raw, + egl::SYNC_NATIVE_FENCE_ANDROID, + attrs.as_ptr(), + ) + }; + + if sync == egl::NO_SYNC { + // Drop will implicitly close the duplicated file descriptor. + return Err(super::check_error().err().unwrap()); + } + + // Successful import means EGL assumes ownership of the file descriptor. + mem::forget(import); + + Ok(super::sync::Sync(Arc::new(super::sync::Inner { + inner: sync, + display: self.inner.clone(), + }))) + } + fn get_platform_display(egl: &Egl, display: RawDisplayHandle) -> Result { if !egl.GetPlatformDisplay.is_loaded() { return Err(ErrorKind::NotSupported("eglGetPlatformDisplay is not supported").into()); diff --git a/glutin/src/api/egl/mod.rs b/glutin/src/api/egl/mod.rs index c39cc16ed1..694e1cc7e8 100644 --- a/glutin/src/api/egl/mod.rs +++ b/glutin/src/api/egl/mod.rs @@ -19,6 +19,7 @@ use libloading::os::unix as libloading_os; #[cfg(windows)] use libloading::os::windows as libloading_os; +use crate::context::Version; use crate::error::{Error, ErrorKind, Result}; use crate::lib_loading::{SymLoading, SymWrapper}; @@ -27,6 +28,7 @@ pub mod context; pub mod device; pub mod display; pub mod surface; +pub mod sync; pub(crate) static EGL: Lazy> = Lazy::new(|| { #[cfg(windows)] @@ -41,6 +43,8 @@ pub(crate) static EGL: Lazy> = Lazy::new(|| { type EglGetProcAddress = unsafe extern "C" fn(*const ffi::c_void) -> *const ffi::c_void; static EGL_GET_PROC_ADDRESS: OnceCell> = OnceCell::new(); +const VERSION_1_5: Version = Version { major: 1, minor: 5 }; + pub(crate) struct Egl(pub SymWrapper); unsafe impl Sync for Egl {} diff --git a/glutin/src/api/egl/sync.rs b/glutin/src/api/egl/sync.rs new file mode 100644 index 0000000000..8142f5c1dd --- /dev/null +++ b/glutin/src/api/egl/sync.rs @@ -0,0 +1,166 @@ +//! EGL Sync Fences. + +use std::ffi::c_void; +use std::mem::MaybeUninit; +use std::sync::Arc; +use std::time::Duration; + +use glutin_egl_sys::egl::types::{EGLenum, EGLint}; + +use super::display::DisplayInner; +use super::{egl, ErrorKind, VERSION_1_5}; +use crate::error::Result; + +/// EGL sync object. +#[derive(Debug, Clone)] +pub struct Sync(pub(super) Arc); + +impl Sync { + /// Insert this sync into the currently bound context. + /// + /// If the EGL version is not at least 1.5 or the `EGL_KHR_wait_sync` + /// extension is not available, this returns [`ErrorKind::NotSupported`]. + /// + /// This will return [`ErrorKind::BadParameter`] if there is no currently + /// bound context. + pub fn wait(&self) -> Result<()> { + if self.0.display.version < VERSION_1_5 + && !self.0.display.display_extensions.contains("EGL_KHR_wait_sync") + { + return Err(ErrorKind::NotSupported( + "Sync::wait is not supported if EGL_KHR_wait_sync isn't available", + ) + .into()); + } + + if unsafe { self.0.display.egl.WaitSyncKHR(*self.0.display.raw, self.0.inner, 0) } + == egl::FALSE as EGLint + { + return Err(super::check_error().err().unwrap()); + } + + Ok(()) + } + + /// Query if the sync is already + pub fn is_signalled(&self) -> Result { + let status = unsafe { self.get_attrib(egl::SYNC_STATUS) }? as EGLenum; + Ok(status == egl::SIGNALED) + } + + /// Block and wait for the sync object to be signalled. + /// + /// A timeout of [`None`] will wait forever. If the timeout is [`Some`], the + /// maximum timeout is [`u64::MAX`] - 1 nanoseconds. Anything larger will be + /// truncated. If the timeout is reached this function returns [`false`]. + /// + /// If `flush` is [`true`], the currently bound context is flushed. + pub fn client_wait(&self, timeout: Option, flush: bool) -> Result { + let flags = if flush { egl::SYNC_FLUSH_COMMANDS_BIT } else { 0 }; + let timeout = timeout + .as_ref() + .map(Duration::as_nanos) + .map(|nanos| nanos.max(u128::from(u64::MAX)) as u64) + .unwrap_or(egl::FOREVER); + + let result = unsafe { + self.0.display.egl.ClientWaitSyncKHR( + *self.0.display.raw, + self.0.inner, + flags as EGLint, + timeout, + ) + } as EGLenum; + + match result { + egl::FALSE => Err(super::check_error().err().unwrap()), + egl::TIMEOUT_EXPIRED => Ok(false), + egl::CONDITION_SATISFIED => Ok(true), + _ => unreachable!(), + } + } + + /// Export the fence's underlying sync fd. + /// + /// Returns [`ErrorKind::NotSupported`] if the sync is not a native fence. + /// + /// # Availability + /// + /// This is available on Android and Linux when the + /// `EGL_ANDROID_native_fence_sync` extension is available. + #[cfg(unix)] + pub fn export_sync_fd(&self) -> Result { + // Invariants: + // - EGL_KHR_fence_sync must be supported if a Sync is creatable. + use std::os::unix::prelude::FromRawFd; + + // Check the type of the fence to see if it can be exported. + let ty = unsafe { self.get_attrib(egl::SYNC_TYPE) }?; + + // SAFETY: GetSyncAttribKHR was successful. + if ty as EGLenum != egl::SYNC_NATIVE_FENCE_ANDROID { + return Err(ErrorKind::NotSupported("The sync is not a native fence").into()); + } + + // SAFETY: The fence type is SYNC_NATIVE_FENCE_ANDROID, making it possible to + // export the native fence. + let fd = unsafe { + self.0.display.egl.DupNativeFenceFDANDROID(*self.0.display.raw, self.0.inner) + }; + + if fd == egl::NO_NATIVE_FENCE_FD_ANDROID { + return Err(super::check_error().err().unwrap()); + } + + // SAFETY: + // - The file descriptor from EGL is valid if the return value is not + // NO_NATIVE_FENCE_FD_ANDROID. + // - The EGL implemention duplicates the underlying file descriptor and + // transfers ownership to the application. + Ok(unsafe { std::os::unix::prelude::OwnedFd::from_raw_fd(fd) }) + } + + /// Get a raw handle to the `EGLSync`. + pub fn raw_device(&self) -> *const c_void { + self.0.inner + } + + unsafe fn get_attrib(&self, attrib: EGLenum) -> Result { + let mut result = MaybeUninit::::uninit(); + + if unsafe { + self.0.display.egl.GetSyncAttribKHR( + *self.0.display.raw, + self.0.inner, + attrib as EGLint, + result.as_mut_ptr().cast(), + ) + } == egl::FALSE + { + return Err(super::check_error().err().unwrap()); + }; + + Ok(unsafe { result.assume_init() }) + } +} + +#[derive(Debug)] +pub(super) struct Inner { + pub(super) inner: egl::types::EGLSyncKHR, + pub(super) display: Arc, +} + +impl Drop for Inner { + fn drop(&mut self) { + // SAFETY: The Sync owns the underlying EGLSyncKHR + if unsafe { self.display.egl.DestroySyncKHR(*self.display.raw, self.inner) } == egl::FALSE { + // If this fails we can't do much in Drop. At least drain the error. + let _ = super::check_error(); + } + } +} + +// SAFETY: The Inner owns the sync and the display is valid. +unsafe impl Send for Inner {} +// SAFETY: EGL allows destroying the sync on any thread. +unsafe impl std::marker::Sync for Inner {} diff --git a/glutin_examples/examples/egl_sync.rs b/glutin_examples/examples/egl_sync.rs new file mode 100644 index 0000000000..d3e44bd6f3 --- /dev/null +++ b/glutin_examples/examples/egl_sync.rs @@ -0,0 +1,87 @@ +fn main() { + #[cfg(all(egl_backend))] + example::run(); +} + +#[cfg(all(egl_backend))] +mod example { + use glutin::api::egl::display::Display; + use glutin::config::ConfigTemplate; + use glutin::context::{ContextApi, ContextAttributesBuilder}; + use glutin::display::{GetDisplayExtensions, GlDisplay}; + use glutin::prelude::GlConfig; + use raw_window_handle::HasRawDisplayHandle; + use winit::event_loop::EventLoop; + + pub fn run() { + // We won't be displaying anything, but we will use winit to get + // access to some sort of platform display. + let event_loop = EventLoop::new().unwrap(); + + // Create the display for the platform. + let display = unsafe { Display::new(event_loop.raw_display_handle()) }.unwrap(); + + if !display.extensions().contains("EGL_KHR_fence_sync") { + eprintln!("EGL implementation does not support fence sync objects"); + return; + } + + // Now we need a context to draw to create a sync object. + let template = ConfigTemplate::default(); + let config = unsafe { display.find_configs(template) } + .unwrap() + .reduce( + |config, acc| { + if config.num_samples() > acc.num_samples() { + config + } else { + acc + } + }, + ) + .expect("No available configs"); + + println!("Picked a config with {} samples", config.num_samples()); + + let context_attributes = + ContextAttributesBuilder::new().with_context_api(ContextApi::Gles(None)).build(None); + let not_current = unsafe { display.create_context(&config, &context_attributes) }.unwrap(); + + // Make the context current, since we are not rendering we can do a surfaceless + // bind. + let _context = not_current.make_current_surfaceless().unwrap(); + + // Now a sync object can be created. + let sync = display.create_sync(false).unwrap(); + + // The sync object at this point is inserted into the command stream for the GL + // context. + // + // However we aren't recording any commands so the fence would already be + // signalled. Effecitvely it isn't useful to test the signalled value here. + sync.is_signalled().unwrap(); + + #[cfg(unix)] + { + if display.extensions().contains("EGL_ANDROID_native_fence_sync") { + use std::os::unix::prelude::AsFd; + + println!("EGL Android native fence sync is supported"); + + // Glutin also supports exporting a sync fence. + // Firstly the sync must be a native fence. + let sync = display.create_sync(true).unwrap(); + + // An exported Sync FD can then be used in many ways, including: + // - Send the Sync FD to another processe to synchronize rendering + // - Import the Sync FD into another EGL Display + // - Import the Sync FD into Vulkan using VK_KHR_external_fence_fd. + let sync_fd = sync.export_sync_fd().expect("Export failed"); + + // To show that an exported sync fd can be imported, we will reimport the sync + // fd we just exported. + let _imported_sync = display.import_sync(sync_fd.as_fd()).expect("Import failed"); + } + } + } +}