use super::super::Egl;
use crate::Error;
use edgefirst_egl as egl;
use log::debug;
#[cfg(target_os = "macos")]
use log::warn;
use std::sync::OnceLock;
const EGL_PLATFORM_ANGLE_ANGLE: u32 = 0x3202;
const EGL_PLATFORM_ANGLE_TYPE_ANGLE: i32 = 0x3203;
const EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE: i32 = 0x3489;
static EGL_LIB: OnceLock<&'static libloading::Library> = OnceLock::new();
const ANGLE_SEARCH_PATHS: &[&str] = &[
"/opt/homebrew/opt/angle/lib/libEGL.dylib",
"/usr/local/opt/angle/lib/libEGL.dylib",
"@loader_path/libEGL.dylib",
"@loader_path/../Frameworks/libEGL.dylib",
"@executable_path/libEGL.dylib",
"@executable_path/../Frameworks/libEGL.dylib",
"libEGL.dylib",
];
pub(in super::super) struct ApplePlatform;
impl ApplePlatform {
pub(in super::super) fn load_egl_lib() -> Result<&'static libloading::Library, Error> {
if let Some(lib) = EGL_LIB.get() {
return Ok(lib);
}
let lib = Self::load_egl_lib_inner()?;
let leaked: &'static libloading::Library = Box::leak(Box::new(lib));
Ok(EGL_LIB.get_or_init(|| leaked))
}
#[cfg(target_os = "ios")]
fn load_egl_lib_inner() -> Result<libloading::Library, Error> {
unsafe extern "C" {
fn _dyld_image_count() -> u32;
fn _dyld_get_image_name(image_index: u32) -> *const std::os::raw::c_char;
}
let count = unsafe { _dyld_image_count() };
for index in 0..count {
let name = unsafe { _dyld_get_image_name(index) };
if name.is_null() {
continue;
}
let path = unsafe { std::ffi::CStr::from_ptr(name) }.to_string_lossy();
if path.ends_with("/libEGL.framework/libEGL") || path.ends_with("/libEGL.dylib") {
debug!("ApplePlatform: dlopen already-loaded ANGLE libEGL at {path}");
return unsafe { libloading::Library::new(path.as_ref()) }.map_err(|e| {
Error::Io(std::io::Error::other(format!(
"dlopen of loaded ANGLE libEGL image {path} failed: {e}"
)))
});
}
}
Err(Error::Io(std::io::Error::other(
"ANGLE libEGL is not loaded in this process: link AND embed \
EGL.xcframework + GLESv2.xcframework into the app or test bundle \
(an embed-only copy, or an unused-framework strip at link time, \
leaves the image list without libEGL)",
)))
}
#[cfg(target_os = "macos")]
fn load_egl_lib_inner() -> Result<libloading::Library, Error> {
let candidates: Vec<String> = std::env::var("EDGEFIRST_ANGLE_PATH")
.ok()
.map(|p| {
vec![format!("{p}/libEGL.dylib")]
})
.unwrap_or_default()
.into_iter()
.chain(ANGLE_SEARCH_PATHS.iter().map(|s| s.to_string()))
.collect();
let mut last_err: Option<libloading::Error> = None;
for path in &candidates {
match unsafe { libloading::Library::new(path) } {
Ok(lib) => {
debug!("ApplePlatform: loaded ANGLE libEGL from {path}");
return Ok(lib);
}
Err(e) => last_err = Some(e),
}
}
warn!(
"ApplePlatform: failed to load ANGLE libEGL from any search path. \
Install ANGLE via `scripts/fetch-angle.sh` (the signed \
EdgeFirst angle-package release) or `brew install \
startergo/angle/angle` (re-sign the dylibs — see README.md § \
macOS GPU Acceleration). Set EDGEFIRST_ANGLE_PATH to the \
directory containing libEGL.dylib to override the search."
);
Err(Error::Io(std::io::Error::other(format!(
"ANGLE libEGL not found in any of {candidates:?}: {last_err:?}"
))))
}
pub(in super::super) fn create_display(egl: &Egl) -> Result<egl::Display, Error> {
type FnGetPlatformDisplayEXT = unsafe extern "C" fn(
platform: u32,
native: *mut std::ffi::c_void,
attribs: *const i32,
) -> egl::EGLDisplay;
let get_platform_display_ptr = egl
.get_proc_address("eglGetPlatformDisplayEXT")
.ok_or_else(|| {
Error::Io(std::io::Error::other(
"eglGetPlatformDisplayEXT not exported by ANGLE libEGL",
))
})?;
let get_platform_display: FnGetPlatformDisplayEXT =
unsafe { std::mem::transmute(get_platform_display_ptr) };
let attribs = [
EGL_PLATFORM_ANGLE_TYPE_ANGLE,
EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE,
egl::NONE,
];
let raw = unsafe {
get_platform_display(
EGL_PLATFORM_ANGLE_ANGLE,
std::ptr::null_mut(),
attribs.as_ptr(),
)
};
if raw.is_null() {
return Err(Error::Io(std::io::Error::other(
"eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE) returned NO_DISPLAY",
)));
}
Ok(unsafe { egl::Display::from_ptr(raw) })
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn load_egl_lib_finds_homebrew_angle_or_skips() {
let exists_at_any = ANGLE_SEARCH_PATHS
.iter()
.any(|p| std::path::Path::new(p).exists());
if !exists_at_any {
eprintln!(
"ANGLE not installed at any default path — skipping. \
Run `brew install startergo/angle/angle` to enable this test."
);
return;
}
if std::env::var_os("HAL_TEST_ALLOW_DLOPEN_ANGLE").is_none() {
eprintln!(
"HAL_TEST_ALLOW_DLOPEN_ANGLE unset — skipping ANGLE dlopen \
probe (run via scripts/test-macos.sh to exercise it)."
);
return;
}
let _ = ApplePlatform::load_egl_lib();
}
}