use super::super::iosurface_import;
use super::super::Egl;
use super::macos::MacosPlatform;
use super::GlPlatform;
use crate::{Error, Result};
use edgefirst_tensor::{DType, PixelFormat, Tensor};
use khronos_egl as egl;
use log::debug;
use std::ffi::c_void;
use std::sync::OnceLock;
const EGL_OPENGL_ES3_BIT: i32 = 0x0040;
const EGL_PBUFFER_BIT: i32 = 0x0001;
const EGL_RENDERABLE_TYPE: i32 = 0x3040;
const EGL_SURFACE_TYPE: i32 = 0x3033;
const EGL_RED_SIZE: i32 = 0x3024;
const EGL_GREEN_SIZE: i32 = 0x3023;
const EGL_BLUE_SIZE: i32 = 0x3022;
const EGL_ALPHA_SIZE: i32 = 0x3021;
pub(in crate::opengl_headless) const EGL_CONTEXT_CLIENT_VERSION: i32 = 0x3098;
const EGL_BACK_BUFFER: i32 = 0x3084;
static GL_LOADED: OnceLock<()> = OnceLock::new();
fn load_gl_once(egl: &Egl) {
GL_LOADED.get_or_init(|| {
gls::load_with(|name| match egl.get_proc_address(name) {
Some(ptr) => ptr as *const c_void,
None => std::ptr::null(),
});
});
}
pub(in crate::opengl_headless) struct SharedAngleDisplay {
pub(in crate::opengl_headless) egl: Egl,
pub(in crate::opengl_headless) display: egl::Display,
pub(in crate::opengl_headless) config: egl::Config,
pub(in crate::opengl_headless) context: egl::Context,
pub(in crate::opengl_headless) dummy_pbuffer: egl::Surface,
pub(in crate::opengl_headless) supports_f32_color: bool,
pub(in crate::opengl_headless) supports_f16_color: bool,
}
unsafe impl Send for SharedAngleDisplay {}
unsafe impl Sync for SharedAngleDisplay {}
static SHARED_DISPLAY: OnceLock<std::result::Result<SharedAngleDisplay, String>> = OnceLock::new();
pub(in crate::opengl_headless) fn shared_display() -> Result<&'static SharedAngleDisplay> {
SHARED_DISPLAY
.get_or_init(|| init_shared_display().map_err(|e| e.to_string()))
.as_ref()
.map_err(|s| Error::Io(std::io::Error::other(s.clone())))
}
fn init_shared_display() -> Result<SharedAngleDisplay> {
let _span =
tracing::info_span!("image.gl_init", platform = "macos", backend = "iosurface",).entered();
let egl_lib = MacosPlatform::load_egl_lib()
.map_err(|e| Error::Io(std::io::Error::other(format!("ANGLE libEGL: {e}"))))?;
let egl: Egl = unsafe {
khronos_egl::Instance::<
khronos_egl::Dynamic<&'static libloading::Library, khronos_egl::EGL1_4>,
>::load_required_from(egl_lib)
}
.map_err(|e| Error::Io(std::io::Error::other(format!("EGL load: {e:?}"))))?;
let display = MacosPlatform::create_display(&egl)?;
let (maj, min) = egl
.initialize(display)
.map_err(|e| Error::Io(std::io::Error::other(format!("eglInitialize: {e:?}"))))?;
debug!("ANGLE EGL {maj}.{min} initialised (process-global shared display)");
egl.bind_api(egl::OPENGL_ES_API)
.map_err(|e| Error::Io(std::io::Error::other(format!("eglBindAPI: {e:?}"))))?;
let cfg_attribs = [
EGL_RENDERABLE_TYPE,
EGL_OPENGL_ES3_BIT,
EGL_SURFACE_TYPE,
EGL_PBUFFER_BIT,
EGL_RED_SIZE,
8,
EGL_GREEN_SIZE,
8,
EGL_BLUE_SIZE,
8,
EGL_ALPHA_SIZE,
8,
iosurface_import::EGL_BIND_TO_TEXTURE_TARGET_ANGLE,
0x305F, egl::NONE,
];
let config = egl
.choose_first_config(display, &cfg_attribs)
.map_err(|e| Error::Io(std::io::Error::other(format!("eglChooseConfig: {e:?}"))))?
.ok_or_else(|| {
Error::NotSupported("no EGL config with GLES3+PBUFFER+TEXTURE_2D bind".into())
})?;
let ctx_attribs = [EGL_CONTEXT_CLIENT_VERSION, 3, egl::NONE];
let context = egl
.create_context(display, config, None, &ctx_attribs)
.map_err(|e| Error::Io(std::io::Error::other(format!("eglCreateContext: {e:?}"))))?;
let dummy_attribs = [egl::WIDTH, 16, egl::HEIGHT, 16, egl::NONE];
let dummy_pbuffer = egl
.create_pbuffer_surface(display, config, &dummy_attribs)
.map_err(|e| {
let _ = egl.destroy_context(display, context);
Error::Io(std::io::Error::other(format!(
"eglCreatePbufferSurface(dummy): {e:?}"
)))
})?;
if let Err(e) = egl.make_current(
display,
Some(dummy_pbuffer),
Some(dummy_pbuffer),
Some(context),
) {
let _ = egl.destroy_surface(display, dummy_pbuffer);
let _ = egl.destroy_context(display, context);
return Err(Error::Io(std::io::Error::other(format!(
"eglMakeCurrent(dummy): {e:?}"
))));
}
load_gl_once(&egl);
let extensions = unsafe {
let ptr = gls::gl::GetString(gls::gl::EXTENSIONS);
if ptr.is_null() {
String::new()
} else {
std::ffi::CStr::from_ptr(ptr as *const std::os::raw::c_char)
.to_string_lossy()
.into_owned()
}
};
let supports_f32_color = extensions
.split_ascii_whitespace()
.any(|e| e == "GL_EXT_color_buffer_float");
let supports_f16_color = extensions
.split_ascii_whitespace()
.any(|e| e == "GL_EXT_color_buffer_half_float");
debug!(
"ANGLE: GL_EXT_color_buffer_float={supports_f32_color}, \
GL_EXT_color_buffer_half_float={supports_f16_color}"
);
let _ = egl.make_current(display, None, None, None);
Ok(SharedAngleDisplay {
egl,
display,
config,
context,
dummy_pbuffer,
supports_f32_color,
supports_f16_color,
})
}
pub(in crate::opengl_headless) struct AngleDisplay {
pub(in crate::opengl_headless) shared: &'static SharedAngleDisplay,
context: egl::Context,
dummy_pbuffer: egl::Surface,
pub(in crate::opengl_headless) transfer_backend: super::super::TransferBackend,
pub(in crate::opengl_headless) has_compute: bool,
active_binds: std::cell::RefCell<Vec<egl::Surface>>,
}
impl AngleDisplay {
pub(in crate::opengl_headless) fn platform_caps(&self) -> super::PlatformCaps {
super::PlatformCaps {
transfer_backend: super::super::TransferBackend::IOSurface,
render_dtypes: crate::RenderDtypeSupport {
f32: self.shared.supports_f32_color,
f16: self.shared.supports_f16_color,
},
serialize_gl: false,
external_oes: false,
}
}
}
impl Drop for AngleDisplay {
fn drop(&mut self) {
let d = self.shared;
let _ = d.egl.make_current(d.display, None, None, None);
let _ = d.egl.destroy_surface(d.display, self.dummy_pbuffer);
let _ = d.egl.destroy_context(d.display, self.context);
}
}
pub(in crate::opengl_headless) struct IoSurfacePbuffer {
shared: &'static SharedAngleDisplay,
pub(in crate::opengl_headless) surface: egl::Surface,
}
impl Drop for IoSurfacePbuffer {
fn drop(&mut self) {
let _ = self
.shared
.egl
.destroy_surface(self.shared.display, self.surface);
}
}
pub(crate) struct AngleClientBuffer;
impl GlPlatform for AngleClientBuffer {
type Display = AngleDisplay;
type Import = IoSurfacePbuffer;
type ImportHandle = egl::Surface;
const PERSISTENT_TEX_BINDINGS: bool = false;
const EXTERNAL_OES: bool = false;
fn load_gl_once(_display: &AngleDisplay) {
}
fn import_handle(import: &IoSurfacePbuffer) -> egl::Surface {
import.surface
}
unsafe fn attach_tex_image_2d(display: &AngleDisplay, handle: egl::Surface) -> Result<()> {
display
.shared
.egl
.bind_tex_image(display.shared.display, handle, EGL_BACK_BUFFER)
.map_err(|e| Error::Io(std::io::Error::other(format!("eglBindTexImage: {e:?}"))))?;
display.active_binds.borrow_mut().push(handle);
Ok(())
}
unsafe fn attach_tex_image_external(
_display: &AngleDisplay,
_handle: egl::Surface,
) -> Result<()> {
Err(Error::NotSupported(
"GL_TEXTURE_EXTERNAL_OES is not available on ANGLE/Metal".into(),
))
}
unsafe fn attach_renderbuffer_storage(
_display: &AngleDisplay,
_handle: egl::Surface,
) -> Result<()> {
Err(Error::NotSupported(
"renderbuffer import targets are not available on ANGLE/Metal \
(EDGEFIRST_OPENGL_RENDERSURFACE has no effect on macOS)"
.into(),
))
}
fn end_gpu_pass(display: &AngleDisplay) {
for surface in display.active_binds.borrow_mut().drain(..) {
let _ = display.shared.egl.release_tex_image(
display.shared.display,
surface,
EGL_BACK_BUFFER,
);
}
}
fn import_buffer_packed<T>(
display: &AngleDisplay,
img: &Tensor<T>,
width: usize,
height: usize,
fmt: super::PackedImportFormat,
) -> Result<IoSurfacePbuffer>
where
T: num_traits::Num + Clone + std::fmt::Debug + Send + Sync,
{
let surface_ref = img.iosurface_ref().ok_or_else(|| {
Error::NotSupported("packed import: tensor is not IOSurface-backed".into())
})?;
let (pf, dt, w, h) = match fmt {
super::PackedImportFormat::Rgba8888 => (PixelFormat::Rgba, DType::U8, width, height),
super::PackedImportFormat::Rgba16161616F => {
if !height.is_multiple_of(3) {
return Err(Error::NotSupported(format!(
"packed RGBA16F surface height {height} is not a 3-plane stack"
)));
}
(PixelFormat::PlanarRgb, DType::F16, width * 4, height / 3)
}
};
let shared = display.shared;
let surface = unsafe {
iosurface_import::create_iosurface_pbuffer(
&shared.egl,
shared.display,
shared.config,
surface_ref,
pf,
dt,
w,
h,
)?
};
Ok(IoSurfacePbuffer { shared, surface })
}
fn init_display(kind: Option<super::super::EglDisplayKind>) -> Result<AngleDisplay> {
if let Some(kind) = kind {
debug!("EglDisplayKind::{kind} ignored on macOS — ANGLE/Metal is the only display");
}
let shared = shared_display()?;
let ctx_attribs = [EGL_CONTEXT_CLIENT_VERSION, 3, egl::NONE];
let context = shared
.egl
.create_context(shared.display, shared.config, None, &ctx_attribs)
.map_err(|e| {
Error::Io(std::io::Error::other(format!(
"eglCreateContext (per-processor): {e:?}"
)))
})?;
let dummy_attribs = [egl::WIDTH, 16, egl::HEIGHT, 16, egl::NONE];
let dummy_pbuffer = shared
.egl
.create_pbuffer_surface(shared.display, shared.config, &dummy_attribs)
.map_err(|e| {
let _ = shared.egl.destroy_context(shared.display, context);
Error::Io(std::io::Error::other(format!(
"eglCreatePbufferSurface (per-processor dummy): {e:?}"
)))
})?;
if let Err(e) = shared.egl.make_current(
shared.display,
Some(dummy_pbuffer),
Some(dummy_pbuffer),
Some(context),
) {
let _ = shared.egl.destroy_surface(shared.display, dummy_pbuffer);
let _ = shared.egl.destroy_context(shared.display, context);
return Err(Error::Io(std::io::Error::other(format!(
"eglMakeCurrent (per-processor): {e:?}"
))));
}
Ok(AngleDisplay {
shared,
context,
dummy_pbuffer,
transfer_backend: super::super::TransferBackend::IOSurface,
has_compute: false,
active_binds: std::cell::RefCell::new(Vec::new()),
})
}
fn import_buffer(
display: &AngleDisplay,
img: &Tensor<u8>,
fmt: PixelFormat,
for_dst: bool,
) -> Result<IoSurfacePbuffer> {
let surface_ref = img.iosurface_ref().ok_or_else(|| {
Error::NotSupported("GL convert: tensor is not IOSurface-backed".into())
})?;
if matches!(
fmt,
PixelFormat::Nv12 | PixelFormat::Nv16 | PixelFormat::Nv24
) {
return Err(Error::NotSupported(
"ANGLE IOSurface import has no multi-plane NV binding — use the R8 path".into(),
));
}
let (w, h) = match (for_dst, img.view_origin()) {
(true, Some(vo)) => (vo.parent_width, vo.parent_height),
_ => (
img.width()
.ok_or_else(|| Error::InvalidShape("import width".into()))?,
img.height()
.ok_or_else(|| Error::InvalidShape("import height".into()))?,
),
};
let shared = display.shared;
let surface = unsafe {
iosurface_import::create_iosurface_pbuffer(
&shared.egl,
shared.display,
shared.config,
surface_ref,
fmt,
DType::U8,
w,
h,
)?
};
Ok(IoSurfacePbuffer { shared, surface })
}
fn import_buffer_nv_r8(
display: &AngleDisplay,
img: &Tensor<u8>,
_fmt: PixelFormat,
) -> Result<IoSurfacePbuffer> {
let surface_ref = img.iosurface_ref().ok_or_else(|| {
Error::NotSupported("GL convert: NV source is not IOSurface-backed".into())
})?;
let (pw, ph) = img.iosurface_physical_dims().ok_or_else(|| {
Error::NotSupported("GL convert: NV source has no IOSurface physical dims".into())
})?;
let shared = display.shared;
let surface = unsafe {
iosurface_import::create_iosurface_pbuffer(
&shared.egl,
shared.display,
shared.config,
surface_ref,
PixelFormat::Grey,
DType::U8,
pw,
ph,
)?
};
Ok(IoSurfacePbuffer { shared, surface })
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::*;
use std::time::Instant;
#[test]
#[ignore = "needs ANGLE + Apple GPU; run on demand"]
fn per_processor_context_bring_up_latency() {
let t0 = Instant::now();
let first = AngleClientBuffer::init_display(None).expect("first display");
let first_ms = t0.elapsed().as_secs_f64() * 1e3;
drop(first);
let handles: Vec<_> = (0..4)
.map(|i| {
std::thread::spawn(move || {
let t0 = Instant::now();
let d = AngleClientBuffer::init_display(None).expect("display");
let ms = t0.elapsed().as_secs_f64() * 1e3;
drop(d);
(i, ms)
})
})
.collect();
for h in handles {
let (i, ms) = h.join().expect("thread");
println!("per-processor context {i}: {ms:.1} ms");
assert!(
ms < 50.0,
"context bring-up {ms:.1} ms exceeds 50 ms budget"
);
}
println!("first (incl. shared display init): {first_ms:.1} ms");
}
}