use super::super::Egl;
use crate::Error;
use khronos_egl as egl;
use log::{debug, 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 MacosPlatform;
impl MacosPlatform {
pub(in super::super) fn load_egl_lib() -> Result<&'static libloading::Library, Error> {
if let Some(lib) = EGL_LIB.get() {
return Ok(lib);
}
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!("MacosPlatform: loaded ANGLE libEGL from {path}");
let leaked: &'static libloading::Library = Box::leak(Box::new(lib));
return Ok(EGL_LIB.get_or_init(|| leaked));
}
Err(e) => last_err = Some(e),
}
}
warn!(
"MacosPlatform: failed to load libEGL.dylib from any search path. \
Install ANGLE via `brew install startergo/angle/angle` and re-sign \
the dylibs (see README.md § macOS GPU Acceleration). \
Set EDGEFIRST_ANGLE_PATH=/path/to/angle/lib to override the search."
);
Err(Error::Io(std::io::Error::other(format!(
"ANGLE libEGL.dylib 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 _ = MacosPlatform::load_egl_lib();
}
}