Skip to content

Commit c24d34a

Browse files
committed
egl: Add wrapper for EGLSync
1 parent 056a45b commit c24d34a

File tree

5 files changed

+345
-0
lines changed

5 files changed

+345
-0
lines changed

Diff for: CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Unreleased
22

3+
- Add `api::egl::sync::Sync` to wrap `EGLSync`
4+
- Add ability to import and export an a sync fd from a `api::egl::sync::Sync`
5+
36
# Version 0.31.1
47

58
- Fixed `CGLContextObj` having an invalid encoding on newer macOS versions.

Diff for: glutin/src/api/egl/display.rs

+85
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,91 @@ impl Display {
231231
}
232232
}
233233

234+
/// Create a sync.
235+
///
236+
/// Creating a sync fence requires that an EGL context is currently bound.
237+
/// Otherwise [`ErrorKind::BadMatch`] is returned.
238+
///
239+
/// This function returns [`Err`] if the EGL version is not at least 1.5
240+
/// or `EGL_KHR_fence_sync` is not available. An error is also returned if
241+
/// native fences are not supported.
242+
pub fn create_sync(&self, native: bool) -> Result<super::sync::Sync> {
243+
if self.inner.version < super::VERSION_1_5
244+
&& !self.inner.display_extensions.contains("EGL_KHR_fence_sync")
245+
{
246+
return Err(ErrorKind::NotSupported("Sync objects are not supported").into());
247+
}
248+
249+
if native && !self.inner.display_extensions.contains("EGL_ANDROID_native_fence_sync") {
250+
return Err(ErrorKind::NotSupported("Native fences are not supported").into());
251+
}
252+
253+
let ty = if native { egl::SYNC_NATIVE_FENCE_ANDROID } else { egl::SYNC_FENCE_KHR };
254+
let sync = unsafe { self.inner.egl.CreateSyncKHR(*self.inner.raw, ty, ptr::null()) };
255+
256+
if sync == egl::NO_SYNC {
257+
return Err(super::check_error().err().unwrap());
258+
}
259+
260+
Ok(super::sync::Sync(Arc::new(super::sync::Inner {
261+
inner: sync,
262+
display: self.inner.clone(),
263+
})))
264+
}
265+
266+
/// Import a sync fd into EGL.
267+
///
268+
/// Glutin will duplicate the sync fd being imported since EGL assumes
269+
/// ownership of the file descriptor. If the file descriptor could not
270+
/// be cloned, then [`ErrorKind::BadParameter`] is returned.
271+
///
272+
/// This function returns [`ErrorKind::NotSupported`] if the
273+
/// `EGL_ANDROID_native_fence_sync` extension is not available.
274+
#[cfg(unix)]
275+
pub fn import_sync(
276+
&self,
277+
fd: std::os::unix::prelude::BorrowedFd<'_>,
278+
) -> Result<super::sync::Sync> {
279+
use std::mem;
280+
use std::os::unix::prelude::AsRawFd;
281+
282+
// The specification states that EGL_KHR_fence_sync must be available,
283+
// and therefore does not need to be tested.
284+
if !self.inner.display_extensions.contains("EGL_ANDROID_native_fence_sync") {
285+
return Err(ErrorKind::NotSupported("Importing a sync fd is not supported").into());
286+
}
287+
288+
let import = fd.try_clone_to_owned().map_err(|_| ErrorKind::BadParameter)?;
289+
290+
let attrs: [EGLint; 3] =
291+
[egl::SYNC_NATIVE_FENCE_FD_ANDROID as EGLint, import.as_raw_fd(), egl::NONE as EGLint];
292+
293+
// SAFETY:
294+
// - The EGL implementation advertised EGL_ANDROID_native_fence_sync
295+
// - The fd being imported is an OwnedFd, meaning ownership is transfered to
296+
// EGL.
297+
let sync = unsafe {
298+
self.inner.egl.CreateSyncKHR(
299+
*self.inner.raw,
300+
egl::SYNC_NATIVE_FENCE_ANDROID,
301+
attrs.as_ptr(),
302+
)
303+
};
304+
305+
if sync == egl::NO_SYNC {
306+
// Drop will implicitly close the duplicated file descriptor.
307+
return Err(super::check_error().err().unwrap());
308+
}
309+
310+
// Successful import means EGL assumes ownership of the file descriptor.
311+
mem::forget(import);
312+
313+
Ok(super::sync::Sync(Arc::new(super::sync::Inner {
314+
inner: sync,
315+
display: self.inner.clone(),
316+
})))
317+
}
318+
234319
fn get_platform_display(egl: &Egl, display: RawDisplayHandle) -> Result<EglDisplay> {
235320
if !egl.GetPlatformDisplay.is_loaded() {
236321
return Err(ErrorKind::NotSupported("eglGetPlatformDisplay is not supported").into());

Diff for: glutin/src/api/egl/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use libloading::os::unix as libloading_os;
1919
#[cfg(windows)]
2020
use libloading::os::windows as libloading_os;
2121

22+
use crate::context::Version;
2223
use crate::error::{Error, ErrorKind, Result};
2324
use crate::lib_loading::{SymLoading, SymWrapper};
2425

@@ -27,6 +28,7 @@ pub mod context;
2728
pub mod device;
2829
pub mod display;
2930
pub mod surface;
31+
pub mod sync;
3032

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

46+
const VERSION_1_5: Version = Version { major: 1, minor: 5 };
47+
4448
pub(crate) struct Egl(pub SymWrapper<egl::Egl>);
4549

4650
unsafe impl Sync for Egl {}

Diff for: glutin/src/api/egl/sync.rs

+166
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
//! EGL Sync Fences.
2+
3+
use std::ffi::c_void;
4+
use std::mem::MaybeUninit;
5+
use std::sync::Arc;
6+
use std::time::Duration;
7+
8+
use glutin_egl_sys::egl::types::{EGLenum, EGLint};
9+
10+
use super::display::DisplayInner;
11+
use super::{egl, ErrorKind, VERSION_1_5};
12+
use crate::error::Result;
13+
14+
/// EGL sync object.
15+
#[derive(Debug, Clone)]
16+
pub struct Sync(pub(super) Arc<Inner>);
17+
18+
impl Sync {
19+
/// Insert this sync into the currently bound context.
20+
///
21+
/// If the EGL version is not at least 1.5 or the `EGL_KHR_wait_sync`
22+
/// extension is not available, this returns [`ErrorKind::NotSupported`].
23+
///
24+
/// This will return [`ErrorKind::BadParameter`] if there is no currently
25+
/// bound context.
26+
pub fn wait(&self) -> Result<()> {
27+
if self.0.display.version < VERSION_1_5
28+
&& !self.0.display.display_extensions.contains("EGL_KHR_wait_sync")
29+
{
30+
return Err(ErrorKind::NotSupported(
31+
"Sync::wait is not supported if EGL_KHR_wait_sync isn't available",
32+
)
33+
.into());
34+
}
35+
36+
if unsafe { self.0.display.egl.WaitSyncKHR(*self.0.display.raw, self.0.inner, 0) }
37+
== egl::FALSE as EGLint
38+
{
39+
return Err(super::check_error().err().unwrap());
40+
}
41+
42+
Ok(())
43+
}
44+
45+
/// Query if the sync is already
46+
pub fn is_signalled(&self) -> Result<bool> {
47+
let status = unsafe { self.get_attrib(egl::SYNC_STATUS) }? as EGLenum;
48+
Ok(status == egl::SIGNALED)
49+
}
50+
51+
/// Block and wait for the sync object to be signalled.
52+
///
53+
/// A timeout of [`None`] will wait forever. If the timeout is [`Some`], the
54+
/// maximum timeout is [`u64::MAX`] - 1 nanoseconds. Anything larger will be
55+
/// truncated. If the timeout is reached this function returns [`false`].
56+
///
57+
/// If `flush` is [`true`], the currently bound context is flushed.
58+
pub fn client_wait(&self, timeout: Option<Duration>, flush: bool) -> Result<bool> {
59+
let flags = if flush { egl::SYNC_FLUSH_COMMANDS_BIT } else { 0 };
60+
let timeout = timeout
61+
.as_ref()
62+
.map(Duration::as_nanos)
63+
.map(|nanos| nanos.max(u128::from(u64::MAX)) as u64)
64+
.unwrap_or(egl::FOREVER);
65+
66+
let result = unsafe {
67+
self.0.display.egl.ClientWaitSyncKHR(
68+
*self.0.display.raw,
69+
self.0.inner,
70+
flags as EGLint,
71+
timeout,
72+
)
73+
} as EGLenum;
74+
75+
match result {
76+
egl::FALSE => Err(super::check_error().err().unwrap()),
77+
egl::TIMEOUT_EXPIRED => Ok(false),
78+
egl::CONDITION_SATISFIED => Ok(true),
79+
_ => unreachable!(),
80+
}
81+
}
82+
83+
/// Export the fence's underlying sync fd.
84+
///
85+
/// Returns [`ErrorKind::NotSupported`] if the sync is not a native fence.
86+
///
87+
/// # Availability
88+
///
89+
/// This is available on Android and Linux when the
90+
/// `EGL_ANDROID_native_fence_sync` extension is available.
91+
#[cfg(unix)]
92+
pub fn export_sync_fd(&self) -> Result<std::os::unix::prelude::OwnedFd> {
93+
// Invariants:
94+
// - EGL_KHR_fence_sync must be supported if a Sync is creatable.
95+
use std::os::unix::prelude::FromRawFd;
96+
97+
// Check the type of the fence to see if it can be exported.
98+
let ty = unsafe { self.get_attrib(egl::SYNC_TYPE) }?;
99+
100+
// SAFETY: GetSyncAttribKHR was successful.
101+
if ty as EGLenum != egl::SYNC_NATIVE_FENCE_ANDROID {
102+
return Err(ErrorKind::NotSupported("The sync is not a native fence").into());
103+
}
104+
105+
// SAFETY: The fence type is SYNC_NATIVE_FENCE_ANDROID, making it possible to
106+
// export the native fence.
107+
let fd = unsafe {
108+
self.0.display.egl.DupNativeFenceFDANDROID(*self.0.display.raw, self.0.inner)
109+
};
110+
111+
if fd == egl::NO_NATIVE_FENCE_FD_ANDROID {
112+
return Err(super::check_error().err().unwrap());
113+
}
114+
115+
// SAFETY:
116+
// - The file descriptor from EGL is valid if the return value is not
117+
// NO_NATIVE_FENCE_FD_ANDROID.
118+
// - The EGL implemention duplicates the underlying file descriptor and
119+
// transfers ownership to the application.
120+
Ok(unsafe { std::os::unix::prelude::OwnedFd::from_raw_fd(fd) })
121+
}
122+
123+
/// Get a raw handle to the `EGLSync`.
124+
pub fn raw_device(&self) -> *const c_void {
125+
self.0.inner
126+
}
127+
128+
unsafe fn get_attrib(&self, attrib: EGLenum) -> Result<EGLint> {
129+
let mut result = MaybeUninit::<EGLint>::uninit();
130+
131+
if unsafe {
132+
self.0.display.egl.GetSyncAttribKHR(
133+
*self.0.display.raw,
134+
self.0.inner,
135+
attrib as EGLint,
136+
result.as_mut_ptr().cast(),
137+
)
138+
} == egl::FALSE
139+
{
140+
return Err(super::check_error().err().unwrap());
141+
};
142+
143+
Ok(unsafe { result.assume_init() })
144+
}
145+
}
146+
147+
#[derive(Debug)]
148+
pub(super) struct Inner {
149+
pub(super) inner: egl::types::EGLSyncKHR,
150+
pub(super) display: Arc<DisplayInner>,
151+
}
152+
153+
impl Drop for Inner {
154+
fn drop(&mut self) {
155+
// SAFETY: The Sync owns the underlying EGLSyncKHR
156+
if unsafe { self.display.egl.DestroySyncKHR(*self.display.raw, self.inner) } == egl::FALSE {
157+
// If this fails we can't do much in Drop. At least drain the error.
158+
let _ = super::check_error();
159+
}
160+
}
161+
}
162+
163+
// SAFETY: The Inner owns the sync and the display is valid.
164+
unsafe impl Send for Inner {}
165+
// SAFETY: EGL allows destroying the sync on any thread.
166+
unsafe impl std::marker::Sync for Inner {}

Diff for: glutin_examples/examples/egl_sync.rs

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
fn main() {
2+
#[cfg(all(egl_backend))]
3+
example::run();
4+
}
5+
6+
#[cfg(all(egl_backend))]
7+
mod example {
8+
use glutin::api::egl::display::Display;
9+
use glutin::config::ConfigTemplate;
10+
use glutin::context::{ContextApi, ContextAttributesBuilder};
11+
use glutin::display::{GetDisplayExtensions, GlDisplay};
12+
use glutin::prelude::GlConfig;
13+
use raw_window_handle::HasRawDisplayHandle;
14+
use winit::event_loop::EventLoop;
15+
16+
pub fn run() {
17+
// We won't be displaying anything, but we will use winit to get
18+
// access to some sort of platform display.
19+
let event_loop = EventLoop::new().unwrap();
20+
21+
// Create the display for the platform.
22+
let display = unsafe { Display::new(event_loop.raw_display_handle()) }.unwrap();
23+
24+
if !display.extensions().contains("EGL_KHR_fence_sync") {
25+
eprintln!("EGL implementation does not support fence sync objects");
26+
return;
27+
}
28+
29+
// Now we need a context to draw to create a sync object.
30+
let template = ConfigTemplate::default();
31+
let config = unsafe { display.find_configs(template) }
32+
.unwrap()
33+
.reduce(
34+
|config, acc| {
35+
if config.num_samples() > acc.num_samples() {
36+
config
37+
} else {
38+
acc
39+
}
40+
},
41+
)
42+
.expect("No available configs");
43+
44+
println!("Picked a config with {} samples", config.num_samples());
45+
46+
let context_attributes =
47+
ContextAttributesBuilder::new().with_context_api(ContextApi::Gles(None)).build(None);
48+
let not_current = unsafe { display.create_context(&config, &context_attributes) }.unwrap();
49+
50+
// Make the context current, since we are not rendering we can do a surfaceless
51+
// bind.
52+
let _context = not_current.make_current_surfaceless().unwrap();
53+
54+
// Now a sync object can be created.
55+
let sync = display.create_sync(false).unwrap();
56+
57+
// The sync object at this point is inserted into the command stream for the GL
58+
// context.
59+
//
60+
// However we aren't recording any commands so the fence would already be
61+
// signalled. Effecitvely it isn't useful to test the signalled value here.
62+
sync.is_signalled().unwrap();
63+
64+
#[cfg(unix)]
65+
{
66+
if display.extensions().contains("EGL_ANDROID_native_fence_sync") {
67+
use std::os::unix::prelude::AsFd;
68+
69+
println!("EGL Android native fence sync is supported");
70+
71+
// Glutin also supports exporting a sync fence.
72+
// Firstly the sync must be a native fence.
73+
let sync = display.create_sync(true).unwrap();
74+
75+
// An exported Sync FD can then be used in many ways, including:
76+
// - Send the Sync FD to another processe to synchronize rendering
77+
// - Import the Sync FD into another EGL Display
78+
// - Import the Sync FD into Vulkan using VK_KHR_external_fence_fd.
79+
let sync_fd = sync.export_sync_fd().expect("Export failed");
80+
81+
// To show that an exported sync fd can be imported, we will reimport the sync
82+
// fd we just exported.
83+
let _imported_sync = display.import_sync(sync_fd.as_fd()).expect("Import failed");
84+
}
85+
}
86+
}
87+
}

0 commit comments

Comments
 (0)