#![cfg(target_os = "macos")]
use crate::Error;
use edgefirst_tensor::{PixelFormat, Tensor, TensorTrait};
use khronos_egl as egl;
const EGL_IOSURFACE_ANGLE: u32 = 0x3454;
const EGL_IOSURFACE_PLANE_ANGLE: i32 = 0x345A;
#[allow(dead_code)] const EGL_TEXTURE_RECTANGLE_ANGLE: i32 = 0x345B;
const EGL_TEXTURE_TYPE_ANGLE: i32 = 0x345C;
const EGL_TEXTURE_INTERNAL_FORMAT_ANGLE: i32 = 0x345D;
pub(super) const EGL_BIND_TO_TEXTURE_TARGET_ANGLE: i32 = 0x348D;
const EGL_TEXTURE_TARGET: i32 = 0x3081;
const EGL_TEXTURE_FORMAT: i32 = 0x3080;
const EGL_TEXTURE_RGBA: i32 = 0x305E;
const EGL_TEXTURE_2D: i32 = 0x305F;
const GL_RG: i32 = 0x8227;
const GL_RGBA: i32 = 0x1908;
const GL_BGRA_EXT: i32 = 0x80E1;
const GL_UNSIGNED_BYTE: i32 = 0x1401;
const FOURCC_2C08: u32 = u32::from_be_bytes(*b"2C08"); const FOURCC_RGBA: u32 = u32::from_be_bytes(*b"RGBA"); const FOURCC_BGRA: u32 = u32::from_be_bytes(*b"BGRA");
#[allow(clippy::duplicated_attributes)]
#[link(name = "IOSurface", kind = "framework")]
#[link(name = "CoreFoundation", kind = "framework")]
extern "C" {
fn IOSurfaceCreate(properties: *mut std::ffi::c_void) -> *mut std::ffi::c_void;
fn CFRelease(cf: *const std::ffi::c_void);
fn CFDictionaryCreateMutable(
allocator: *const std::ffi::c_void,
capacity: isize,
key_callbacks: *const std::ffi::c_void,
value_callbacks: *const std::ffi::c_void,
) -> *mut std::ffi::c_void;
fn CFDictionarySetValue(
dict: *mut std::ffi::c_void,
key: *const std::ffi::c_void,
value: *const std::ffi::c_void,
);
fn CFStringCreateWithCString(
allocator: *const std::ffi::c_void,
cstr: *const i8,
encoding: u32,
) -> *mut std::ffi::c_void;
fn CFNumberCreate(
allocator: *const std::ffi::c_void,
ty: i32,
value_ptr: *const std::ffi::c_void,
) -> *mut std::ffi::c_void;
static kCFTypeDictionaryKeyCallBacks: std::ffi::c_void;
static kCFTypeDictionaryValueCallBacks: std::ffi::c_void;
}
const K_CF_NUMBER_LONG_TYPE: i32 = 10;
const K_CF_STRING_ENCODING_UTF8: u32 = 0x08000100;
struct ImageLayout {
fourcc: u32,
bytes_per_element: usize,
width: usize,
height: usize,
}
impl ImageLayout {
fn for_format(fmt: PixelFormat, width: usize, height: usize) -> Result<Self, Error> {
let (fourcc, bytes_per_element) = edgefirst_tensor::image_iosurface_layout(fmt)
.ok_or_else(|| {
Error::NotImplemented(format!(
"IOSurface allocation for PixelFormat::{fmt:?} not yet supported \
(no FourCC mapping in edgefirst_tensor::image_iosurface_layout — \
multi-plane formats need separate property dictionary setup)"
))
})?;
Ok(Self {
fourcc,
bytes_per_element,
width,
height,
})
}
fn gl_type(&self) -> i32 {
GL_UNSIGNED_BYTE
}
fn gl_internal_format(&self) -> i32 {
match self.fourcc {
FOURCC_2C08 => GL_RG,
FOURCC_RGBA => GL_RGBA,
FOURCC_BGRA => GL_BGRA_EXT,
other => unreachable!(
"unsupported IOSurface FourCC 0x{other:08X} in GL import — \
add a mapping here when extending image_iosurface_layout()"
),
}
}
}
unsafe fn build_image_props(layout: &ImageLayout) -> Result<*mut std::ffi::c_void, Error> {
let bpr = (layout.width * layout.bytes_per_element + 63) & !63;
let alloc_size = bpr * layout.height;
let dict = CFDictionaryCreateMutable(
std::ptr::null(),
0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks,
);
if dict.is_null() {
return Err(Error::Io(std::io::Error::other(
"CFDictionaryCreateMutable returned null",
)));
}
let set_num = |key: &str, value: i64| -> Result<(), Error> {
let key_c =
std::ffi::CString::new(key).map_err(|e| Error::Internal(format!("CString: {e}")))?;
let key_cf =
CFStringCreateWithCString(std::ptr::null(), key_c.as_ptr(), K_CF_STRING_ENCODING_UTF8);
if key_cf.is_null() {
return Err(Error::Io(std::io::Error::other(
"CFStringCreateWithCString returned null",
)));
}
let value_cf = CFNumberCreate(
std::ptr::null(),
K_CF_NUMBER_LONG_TYPE,
&value as *const i64 as *const std::ffi::c_void,
);
if value_cf.is_null() {
CFRelease(key_cf);
return Err(Error::Io(std::io::Error::other(
"CFNumberCreate returned null",
)));
}
CFDictionarySetValue(dict, key_cf, value_cf);
CFRelease(key_cf);
CFRelease(value_cf);
Ok(())
};
let result = (|| -> Result<(), Error> {
set_num("IOSurfaceWidth", layout.width as i64)?;
set_num("IOSurfaceHeight", layout.height as i64)?;
set_num("IOSurfaceBytesPerElement", layout.bytes_per_element as i64)?;
set_num("IOSurfacePixelFormat", layout.fourcc as i64)?;
set_num("IOSurfaceBytesPerRow", bpr as i64)?;
set_num("IOSurfaceAllocSize", alloc_size as i64)?;
Ok(())
})();
if let Err(e) = result {
CFRelease(dict);
return Err(e);
}
Ok(dict)
}
pub(super) unsafe fn create_image_iosurface(
fmt: PixelFormat,
width: usize,
height: usize,
) -> Result<*mut std::ffi::c_void, Error> {
let layout = ImageLayout::for_format(fmt, width, height)?;
let dict = build_image_props(&layout)?;
let surface = IOSurfaceCreate(dict);
CFRelease(dict);
if surface.is_null() {
return Err(Error::Io(std::io::Error::other(
"IOSurfaceCreate returned null — likely memory pressure or invalid layout",
)));
}
Ok(surface)
}
type FnCreatePbufferFromClientBuffer = unsafe extern "C" fn(
dpy: egl::EGLDisplay,
buftype: u32,
buffer: egl::EGLClientBuffer,
config: egl::EGLConfig,
attrib_list: *const i32,
) -> egl::EGLSurface;
pub(super) unsafe fn create_iosurface_pbuffer(
egl: &super::Egl,
display: egl::Display,
config: egl::Config,
surface_ref: *mut std::ffi::c_void,
fmt: PixelFormat,
width: usize,
height: usize,
) -> Result<egl::Surface, Error> {
let layout = ImageLayout::for_format(fmt, width, height)?;
let create_pbuffer_ptr = egl
.get_proc_address("eglCreatePbufferFromClientBuffer")
.ok_or_else(|| {
Error::Io(std::io::Error::other(
"eglCreatePbufferFromClientBuffer not exported by ANGLE libEGL",
))
})?;
let create_pbuffer: FnCreatePbufferFromClientBuffer = std::mem::transmute(create_pbuffer_ptr);
let attribs = [
egl::WIDTH,
width as i32,
egl::HEIGHT,
height as i32,
EGL_IOSURFACE_PLANE_ANGLE,
0,
EGL_TEXTURE_TARGET,
EGL_TEXTURE_2D,
EGL_TEXTURE_INTERNAL_FORMAT_ANGLE,
layout.gl_internal_format(),
EGL_TEXTURE_FORMAT,
EGL_TEXTURE_RGBA,
EGL_TEXTURE_TYPE_ANGLE,
layout.gl_type(),
egl::NONE,
];
let raw = create_pbuffer(
display.as_ptr(),
EGL_IOSURFACE_ANGLE,
surface_ref as egl::EGLClientBuffer,
config.as_ptr(),
attribs.as_ptr(),
);
if raw.is_null() {
let egl_err = egl.get_error();
return Err(Error::Io(std::io::Error::other(format!(
"eglCreatePbufferFromClientBuffer(EGL_IOSURFACE_ANGLE) failed: {egl_err:?}"
))));
}
Ok(egl::Surface::from_ptr(raw))
}
pub(super) fn tensor_iosurface_ref(tensor: &Tensor<u8>) -> Option<*mut std::ffi::c_void> {
if !matches!(tensor.memory(), edgefirst_tensor::TensorMemory::Dma) {
return None;
}
tensor.iosurface_ref()
}