Skip to content

Commit

Permalink
egl: Add wrapper for EGLSync
Browse files Browse the repository at this point in the history
  • Loading branch information
i509VCB committed Nov 12, 2023
1 parent 056a45b commit 9e5997e
Show file tree
Hide file tree
Showing 5 changed files with 345 additions and 0 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
85 changes: 85 additions & 0 deletions glutin/src/api/egl/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<super::sync::Sync> {
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<super::sync::Sync> {
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<EglDisplay> {
if !egl.GetPlatformDisplay.is_loaded() {
return Err(ErrorKind::NotSupported("eglGetPlatformDisplay is not supported").into());
Expand Down
4 changes: 4 additions & 0 deletions glutin/src/api/egl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand All @@ -27,6 +28,7 @@ pub mod context;
pub mod device;
pub mod display;
pub mod surface;
pub mod sync;

pub(crate) static EGL: Lazy<Option<Egl>> = Lazy::new(|| {
#[cfg(windows)]
Expand All @@ -41,6 +43,8 @@ pub(crate) static EGL: Lazy<Option<Egl>> = Lazy::new(|| {
type EglGetProcAddress = unsafe extern "C" fn(*const ffi::c_void) -> *const ffi::c_void;
static EGL_GET_PROC_ADDRESS: OnceCell<libloading_os::Symbol<EglGetProcAddress>> = OnceCell::new();

const VERSION_1_5: Version = Version { major: 1, minor: 5 };

pub(crate) struct Egl(pub SymWrapper<egl::Egl>);

unsafe impl Sync for Egl {}
Expand Down
166 changes: 166 additions & 0 deletions glutin/src/api/egl/sync.rs
Original file line number Diff line number Diff line change
@@ -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<Inner>);

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<bool> {
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<Duration>, flush: bool) -> Result<bool> {
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<std::os::unix::prelude::OwnedFd> {
// 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<EGLint> {
let mut result = MaybeUninit::<EGLint>::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<DisplayInner>,
}

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 {}
87 changes: 87 additions & 0 deletions glutin_examples/examples/egl_sync.rs
Original file line number Diff line number Diff line change
@@ -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::fd::AsFd;

Check failure on line 67 in glutin_examples/examples/egl_sync.rs

View workflow job for this annotation

GitHub Actions / Tests (1.65.0, x86_64-unknown-linux-gnu, ubuntu-latest, --no-default-features, egl,wayland,x11)

unresolved import `std::os::fd::AsFd`

Check failure on line 67 in glutin_examples/examples/egl_sync.rs

View workflow job for this annotation

GitHub Actions / Tests (1.65.0, x86_64-unknown-linux-gnu, ubuntu-latest, --no-default-features, egl,wayland,x11)

module `fd` is private

Check failure on line 67 in glutin_examples/examples/egl_sync.rs

View workflow job for this annotation

GitHub Actions / Tests (1.65.0, x86_64-unknown-linux-gnu, ubuntu-latest)

unresolved import `std::os::fd::AsFd`

Check failure on line 67 in glutin_examples/examples/egl_sync.rs

View workflow job for this annotation

GitHub Actions / Tests (1.65.0, x86_64-unknown-linux-gnu, ubuntu-latest)

module `fd` is private

Check failure on line 67 in glutin_examples/examples/egl_sync.rs

View workflow job for this annotation

GitHub Actions / Tests (1.65.0, x86_64-unknown-linux-gnu, ubuntu-latest, --no-default-features, egl,wayland)

unresolved import `std::os::fd::AsFd`

Check failure on line 67 in glutin_examples/examples/egl_sync.rs

View workflow job for this annotation

GitHub Actions / Tests (1.65.0, x86_64-unknown-linux-gnu, ubuntu-latest, --no-default-features, egl,wayland)

module `fd` is private

Check failure on line 67 in glutin_examples/examples/egl_sync.rs

View workflow job for this annotation

GitHub Actions / Tests (1.65.0, x86_64-unknown-linux-gnu, ubuntu-latest, --no-default-features, egl,x11)

unresolved import `std::os::fd::AsFd`

Check failure on line 67 in glutin_examples/examples/egl_sync.rs

View workflow job for this annotation

GitHub Actions / Tests (1.65.0, x86_64-unknown-linux-gnu, ubuntu-latest, --no-default-features, egl,x11)

module `fd` is private

Check failure on line 67 in glutin_examples/examples/egl_sync.rs

View workflow job for this annotation

GitHub Actions / Tests (1.65.0, i686-unknown-linux-gnu, ubuntu-latest)

unresolved import `std::os::fd::AsFd`

Check failure on line 67 in glutin_examples/examples/egl_sync.rs

View workflow job for this annotation

GitHub Actions / Tests (1.65.0, i686-unknown-linux-gnu, ubuntu-latest)

module `fd` is private

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");

Check failure on line 83 in glutin_examples/examples/egl_sync.rs

View workflow job for this annotation

GitHub Actions / Tests (1.65.0, x86_64-unknown-linux-gnu, ubuntu-latest, --no-default-features, egl,wayland,x11)

no method named `as_fd` found for struct `OwnedFd` in the current scope

Check failure on line 83 in glutin_examples/examples/egl_sync.rs

View workflow job for this annotation

GitHub Actions / Tests (1.65.0, x86_64-unknown-linux-gnu, ubuntu-latest)

no method named `as_fd` found for struct `OwnedFd` in the current scope

Check failure on line 83 in glutin_examples/examples/egl_sync.rs

View workflow job for this annotation

GitHub Actions / Tests (1.65.0, x86_64-unknown-linux-gnu, ubuntu-latest, --no-default-features, egl,wayland)

no method named `as_fd` found for struct `OwnedFd` in the current scope

Check failure on line 83 in glutin_examples/examples/egl_sync.rs

View workflow job for this annotation

GitHub Actions / Tests (1.65.0, x86_64-unknown-linux-gnu, ubuntu-latest, --no-default-features, egl,x11)

no method named `as_fd` found for struct `OwnedFd` in the current scope

Check failure on line 83 in glutin_examples/examples/egl_sync.rs

View workflow job for this annotation

GitHub Actions / Tests (1.65.0, i686-unknown-linux-gnu, ubuntu-latest)

no method named `as_fd` found for struct `OwnedFd` in the current scope
}
}
}
}

0 comments on commit 9e5997e

Please sign in to comment.