1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
//! EGL fence related structs
use std::os::unix::io::{FromRawFd, IntoRawFd, OwnedFd};
use std::sync::Arc;
use std::time::Duration;
use crate::backend::egl::{
ffi, wrap_egl_call, wrap_egl_call_bool, wrap_egl_call_ptr, EGLDisplay, EGLDisplayHandle, Error,
};
#[derive(Debug)]
struct InnerEGLFence {
display_handle: Arc<EGLDisplayHandle>,
handle: ffi::egl::types::EGLSync,
native: bool,
}
unsafe impl Send for InnerEGLFence {}
unsafe impl Sync for InnerEGLFence {}
impl Drop for InnerEGLFence {
fn drop(&mut self) {
unsafe {
// Not much we can do about when this fails
ffi::egl::DestroySync(**self.display_handle, self.handle);
}
}
}
/// A EGL fence
#[derive(Debug, Clone)]
pub struct EGLFence(Arc<InnerEGLFence>);
impl EGLFence {
/// Returns whether the display supports creating fences from native fences
pub fn supports_importing(display: &EGLDisplay) -> bool {
display.supports_native_fences
}
/// Import a native fence fd
#[profiling::function]
pub fn import(display: &EGLDisplay, native: OwnedFd) -> Result<Self, Error> {
// SAFETY: we do not have to test for EGL_KHR_fence_sync as EGL_ANDROID_native_fence_sync
// requires it already
if !display.supports_native_fences {
return Err(Error::EglExtensionNotSupported(&[
"EGL_ANDROID_native_fence_sync",
]));
}
let display_handle = display.get_display_handle();
let fd = native.into_raw_fd();
let attributes: [ffi::egl::types::EGLAttrib; 3] = [
ffi::egl::SYNC_NATIVE_FENCE_FD_ANDROID as ffi::egl::types::EGLAttrib,
fd as ffi::egl::types::EGLAttrib,
ffi::egl::NONE as ffi::egl::types::EGLAttrib,
];
let res = wrap_egl_call_ptr(|| unsafe {
ffi::egl::CreateSync(
**display_handle,
ffi::egl::SYNC_NATIVE_FENCE_ANDROID,
attributes.as_ptr(),
)
})
.map_err(Error::CreationFailed);
// In case of an error we have to close the fd to not leak it
if res.is_err() {
unsafe {
rustix::io::close(fd);
}
}
let handle = res?;
Ok(Self(Arc::new(InnerEGLFence {
display_handle,
handle,
native: true,
})))
}
/// Create a new egl fence
///
/// This will create a `EGL_SYNC_NATIVE_FENCE_ANDROID` or a `EGL_SYNC_FENCE_KHR` depending
/// on the availability of the `EGL_ANDROID_native_fence_sync` extension.
///
/// Returns `Err` if the `EGL_KHR_fence_sync` is not present or creating the fence failed.
///
/// Note: It is the callers responsibility to make sure the current bound client API supports
/// fences. For OpenglES the `GL_OES_EGL_sync` extension indicates support for fences.
#[profiling::function]
pub fn create(display: &EGLDisplay) -> Result<Self, Error> {
if !display.has_fences {
return Err(Error::EglExtensionNotSupported(&["EGL_KHR_fence_sync"]));
}
let (type_, native) = if display.supports_native_fences {
(ffi::egl::SYNC_NATIVE_FENCE_ANDROID, true)
} else {
(ffi::egl::SYNC_FENCE, false)
};
let display_handle = display.get_display_handle();
let handle =
wrap_egl_call_ptr(|| unsafe { ffi::egl::CreateSync(**display_handle, type_, std::ptr::null()) })
.map_err(Error::CreationFailed)?;
Ok(Self(Arc::new(InnerEGLFence {
display_handle,
handle,
native,
})))
}
/// Indicates if this fence represents a native fence
pub fn is_native(&self) -> bool {
self.0.native
}
/// Export this [`EGLFence`] as a native fence fd
#[profiling::function]
pub fn export(&self) -> Result<OwnedFd, Error> {
if !self.0.native {
return Err(Error::EglExtensionNotSupported(&[
"EGL_ANDROID_native_fence_sync",
]));
}
let fd = wrap_egl_call(
|| unsafe { ffi::egl::DupNativeFenceFDANDROID(**self.0.display_handle, self.0.handle) },
ffi::egl::NO_NATIVE_FENCE_FD_ANDROID,
)
.map_err(Error::CreationFailed)?;
// SAFETY: eglDupNativeFenceFDANDROID generates EGL_BAD_PARAMETER in case of an error, so fd should always
// contain a valid fd
Ok(unsafe { OwnedFd::from_raw_fd(fd) })
}
/// Tries to insert the fence in the current bound client context
///
/// Returns `Err` if the supplied display does not match the display
/// used at creation time of the fence.
#[profiling::function]
pub fn wait(&self, display: &EGLDisplay) -> Result<(), Error> {
if **display.get_display_handle() != **self.0.display_handle {
return Err(Error::DisplayNotSupported);
}
wrap_egl_call_bool(|| unsafe { ffi::egl::WaitSync(**self.0.display_handle, self.0.handle, 0) })
.map_err(Error::CreationFailed)?;
Ok(())
}
/// Blocks the current thread until the fence is signaled or the supplied
/// timeout is reached.
///
/// If the timeout is reached `false` is returned
#[profiling::function]
pub fn client_wait(&self, timeout: Option<Duration>, flush: bool) -> Result<bool, Error> {
let timeout = timeout
.map(|t| t.as_nanos() as ffi::egl::types::EGLuint64KHR)
.unwrap_or(ffi::egl::FOREVER);
let flags = if flush {
ffi::egl::SYNC_FLUSH_COMMANDS_BIT as ffi::egl::types::EGLint
} else {
0
};
// SAFETY: eglClientWaitSyncKHR only defines two errors
// 1. If <sync> is not a valid sync object for <dpy>, EGL_FALSE is returned and an EGL_BAD_PARAMETER error is generated.
// 2. If <dpy> does not match the EGLDisplay passed to eglCreateSyncKHR when <sync> was created, the behaviour is undefined.
// Both should not be possible as we own both, the handle and the display the handle was created on
let status = wrap_egl_call(
|| unsafe { ffi::egl::ClientWaitSync(**self.0.display_handle, self.0.handle, flags, timeout) },
ffi::egl::FALSE as ffi::egl::types::EGLint,
)
.map_err(Error::WaitFailed)?;
Ok(status == ffi::egl::CONDITION_SATISFIED as ffi::EGLint)
}
}