-
-
Notifications
You must be signed in to change notification settings - Fork 476
Commit
- Loading branch information
There are no files selected for viewing
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 {} |
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 GitHub Actions / Tests (1.65.0, x86_64-unknown-linux-gnu, ubuntu-latest, --no-default-features, egl,wayland,x11)
Check failure on line 67 in glutin_examples/examples/egl_sync.rs GitHub Actions / Tests (1.65.0, x86_64-unknown-linux-gnu, ubuntu-latest, --no-default-features, egl,wayland,x11)
Check failure on line 67 in glutin_examples/examples/egl_sync.rs GitHub Actions / Tests (1.65.0, x86_64-unknown-linux-gnu, ubuntu-latest)
Check failure on line 67 in glutin_examples/examples/egl_sync.rs GitHub Actions / Tests (1.65.0, x86_64-unknown-linux-gnu, ubuntu-latest)
Check failure on line 67 in glutin_examples/examples/egl_sync.rs GitHub Actions / Tests (1.65.0, x86_64-unknown-linux-gnu, ubuntu-latest, --no-default-features, egl,wayland)
Check failure on line 67 in glutin_examples/examples/egl_sync.rs GitHub Actions / Tests (1.65.0, x86_64-unknown-linux-gnu, ubuntu-latest, --no-default-features, egl,wayland)
Check failure on line 67 in glutin_examples/examples/egl_sync.rs GitHub Actions / Tests (1.65.0, x86_64-unknown-linux-gnu, ubuntu-latest, --no-default-features, egl,x11)
Check failure on line 67 in glutin_examples/examples/egl_sync.rs GitHub Actions / Tests (1.65.0, x86_64-unknown-linux-gnu, ubuntu-latest, --no-default-features, egl,x11)
Check failure on line 67 in glutin_examples/examples/egl_sync.rs GitHub Actions / Tests (1.65.0, i686-unknown-linux-gnu, ubuntu-latest)
|
||
|
||
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 GitHub Actions / Tests (1.65.0, x86_64-unknown-linux-gnu, ubuntu-latest, --no-default-features, egl,wayland,x11)
Check failure on line 83 in glutin_examples/examples/egl_sync.rs GitHub Actions / Tests (1.65.0, x86_64-unknown-linux-gnu, ubuntu-latest)
Check failure on line 83 in glutin_examples/examples/egl_sync.rs GitHub Actions / Tests (1.65.0, x86_64-unknown-linux-gnu, ubuntu-latest, --no-default-features, egl,wayland)
Check failure on line 83 in glutin_examples/examples/egl_sync.rs GitHub Actions / Tests (1.65.0, x86_64-unknown-linux-gnu, ubuntu-latest, --no-default-features, egl,x11)
|
||
} | ||
} | ||
} | ||
} |