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
//! The EGL of the system, and the platform graphics over it.
//!
//! The reference calls `EglPlatformGraphics.TryCreate()` of the OpenGL
//! project, whose display loads `libEGL.so` by name. The EGL of the port
//! takes a loader function, and its platform graphics belong to the thread
//! that created them; on Android the compositor renders on the render
//! thread. So this file has what the reference gets from the OpenGL
//! project: the loader of the library of the system, and platform graphics
//! that hold nothing of a thread. A context loads the interface and creates
//! its display object on the thread that asks for it; the display of the
//! system is one per process and counted, so every display object is the
//! same display (docs/porting/android-platform.md, section 6).
use ferroui_base::logging::{LogEventLevel, Logger};
use ferroui_base::platform::{IPlatformGraphics, IPlatformGraphicsContext};
use ferroui_base::reactive::IDisposable;
use ferroui_opengl::egl::{
EglContext, EglContextOptions, EglDisplay, EglDisplayCreationOptions, EglDisplayOptions, EglInterface,
};
use ferroui_opengl::{GetProcAddress, OpenGlException};
use std::ffi::{c_char, c_void, CString};
use std::rc::Rc;
use std::sync::OnceLock;
type EglGetProcAddress = unsafe extern "C" fn(name: *const c_char) -> *const c_void;
/// The EGL library of the system and its `eglGetProcAddress`. The library
/// is opened once and stays open.
#[derive(Clone, Copy)]
struct EglLibrary {
library: usize,
get_proc_address: EglGetProcAddress,
}
fn egl_library() -> Result<EglLibrary, OpenGlException> {
static LIBRARY: OnceLock<Option<EglLibrary>> = OnceLock::new();
LIBRARY
.get_or_init(|| {
// SAFETY: the names are terminated strings; the library is a library of the
// system, opened for the life of the process.
unsafe {
let library = libc::dlopen(c"libEGL.so".as_ptr(), libc::RTLD_NOW);
if library.is_null() {
return None;
}
let get_proc_address = libc::dlsym(library, c"eglGetProcAddress".as_ptr());
if get_proc_address.is_null() {
return None;
}
Some(EglLibrary {
library: library as usize,
// An address that is not null is the function of that name, whose
// signature is the declared one.
get_proc_address: std::mem::transmute::<*mut c_void, EglGetProcAddress>(get_proc_address),
})
}
})
.ok_or_else(|| OpenGlException::new("Unable to load libEGL.so"))
}
/// The loader of the entry points of EGL and of OpenGL ES: an entry point
/// the library exports, or else what `eglGetProcAddress` answers (which on
/// Android knows the functions of OpenGL ES, core and extensions).
fn load_egl() -> Result<GetProcAddress, OpenGlException> {
let library = egl_library()?;
Ok(Rc::new(move |name: &str| {
let Ok(name) = CString::new(name) else {
return std::ptr::null();
};
// SAFETY: the library handle is the open library, the name is terminated, and
// `eglGetProcAddress` takes a terminated name and returns an address or null.
unsafe {
let address = libc::dlsym(library.library as *mut c_void, name.as_ptr());
if !address.is_null() {
return address.cast_const();
}
(library.get_proc_address)(name.as_ptr())
}
}))
}
/// The platform graphics of the EGL of the system.
pub(crate) struct AndroidEglPlatformGraphics;
impl AndroidEglPlatformGraphics {
/// The display of the system, as an object of the calling thread.
fn create_display() -> Result<Rc<EglDisplay>, OpenGlException> {
let get_proc_address = load_egl()?;
// SAFETY: the loader returns, for a name, null or the address of the entry point of
// that name in the EGL library of the system or behind its `eglGetProcAddress`;
// the library is never closed.
let egl = Rc::new(unsafe { EglInterface::new(&get_proc_address) });
EglDisplay::new_with_creation_options(EglDisplayCreationOptions {
base: EglDisplayOptions {
egl: Some(egl),
supports_multiple_contexts: true,
supports_context_sharing: true,
..Default::default()
},
..Default::default()
})
}
/// A context of a display, which the context disposes with itself.
fn create_context_for_display(display: Rc<EglDisplay>) -> Result<Rc<EglContext>, OpenGlException> {
let dispose_display = display.clone();
let result = display.create_context(Some(EglContextOptions {
dispose_callback: Some(Rc::new(move || dispose_display.dispose())),
..Default::default()
}));
if result.is_err() {
display.dispose();
}
result
}
/// The platform graphics, when the display of the system can be
/// initialized and a context of it made current; `None`, with the
/// failure logged, when not.
pub fn try_create() -> Option<AndroidEglPlatformGraphics> {
let probe = || -> Result<(), OpenGlException> {
let context = Self::create_context_for_display(Self::create_display()?)?;
let result = match context.make_current_with_surface(None) {
Ok(current) => {
current.dispose();
Ok(())
}
Err(error) => Err(OpenGlException::new(error.to_string())),
};
IPlatformGraphicsContext::dispose(&*context);
result
};
match probe() {
Ok(()) => Some(AndroidEglPlatformGraphics),
Err(e) => {
if let Some(logger) = Logger::try_get(LogEventLevel::Error, "OpenGL") {
logger.log_with_values(None, "Unable to initialize EGL-based rendering: {0}", &[&e]);
}
None
}
}
}
}
impl IPlatformGraphics for AndroidEglPlatformGraphics {
fn uses_shared_context(&self) -> bool {
false
}
/// # Panics
/// Panics when the display or the context cannot be created (the
/// exception of the reference).
fn create_context(&self) -> Rc<dyn IPlatformGraphicsContext> {
match Self::create_display().and_then(Self::create_context_for_display) {
Ok(context) => context,
Err(error) => panic!("{error}"),
}
}
/// # Panics
/// Always: the graphics have no shared context (the
/// `NotSupportedException` of the reference).
fn get_shared_context(&self) -> Rc<dyn IPlatformGraphicsContext> {
panic!("Specified method is not supported.")
}
}