use super::{checked_size, Block, BlockPlan};
use crate::{ffi::*, MemError};
use core::ffi::c_void;
use core::ptr::{self, NonNull};
#[cfg(test)]
use super::tests::{self, Call, Event};
macro_rules! native_call {
($call:ident, $failure:expr, $native:expr) => {{
#[cfg(test)]
let result = if tests::fail(Call::$call) {
$failure
} else {
$native
};
#[cfg(not(test))]
let result = $native;
result
}};
}
pub(super) use native_call;
pub(super) struct OwnedCf(NonNull<c_void>);
impl OwnedCf {
pub(super) fn from_create(raw: CFTypeRef) -> Option<Self> {
let raw = NonNull::new(raw.cast_mut())?;
let owner = Self(raw);
#[cfg(test)]
tests::record(Event::Acquire(raw.as_ptr() as usize));
Some(owner)
}
pub(super) fn raw(&self) -> CFTypeRef {
self.0.as_ptr()
}
}
impl Drop for OwnedCf {
fn drop(&mut self) {
unsafe { release(self.raw()) }
}
}
pub(super) fn number(value: i64) -> Result<OwnedCf, MemError> {
OwnedCf::from_create(native_call!(Number, ptr::null(), unsafe {
CFNumberCreate(
ptr::null(),
kCFNumberSInt64Type,
ptr::from_ref(&value).cast(),
)
}))
.ok_or(MemError::BlockPropertiesFailed)
}
fn properties(plan: &BlockPlan) -> Result<OwnedCf, MemError> {
let numbers = [
number(checked_size(plan.requested)?)?,
number(1)?,
number(1)?,
number(checked_size(plan.row_bytes)?)?,
number(checked_size(plan.allocation_size)?)?,
number(0)?,
];
let keys = unsafe {
[
kIOSurfaceWidth,
kIOSurfaceHeight,
kIOSurfaceBytesPerElement,
kIOSurfaceBytesPerRow,
kIOSurfaceAllocSize,
kIOSurfacePixelFormat,
]
};
let values = numbers.each_ref().map(OwnedCf::raw);
let dictionary = OwnedCf::from_create(native_call!(Dictionary, ptr::null(), unsafe {
CFDictionaryCreate(
ptr::null(),
keys.as_ptr(),
values.as_ptr(),
6,
ptr::addr_of!(kCFTypeDictionaryKeyCallBacks),
ptr::addr_of!(kCFTypeDictionaryValueCallBacks),
)
}))
.ok_or(MemError::BlockPropertiesFailed)?;
Ok(dictionary)
}
pub(super) struct Surface {
pub(super) owner: OwnedCf,
pub(super) locked: bool,
}
impl Surface {
pub(super) fn raw(&self) -> IOSurfaceRef {
self.owner.raw().cast_mut()
}
pub(super) fn into_raw(self) -> IOSurfaceRef {
let raw = self.raw();
core::mem::forget(self);
raw
}
}
impl Drop for Surface {
fn drop(&mut self) {
if self.locked {
unsafe { unlock(self.raw()) }
}
}
}
pub(super) fn open(plan: &BlockPlan) -> Result<Block, MemError> {
let dictionary = properties(plan)?;
let owner = OwnedCf::from_create(native_call!(Surface, ptr::null_mut(), unsafe {
IOSurfaceCreate(dictionary.raw())
}))
.ok_or(MemError::BlockCreateFailed)?;
let mut surface = Surface {
owner,
locked: false,
};
drop(dictionary);
let actual = unsafe { IOSurfaceGetAllocSize(surface.raw()) };
#[cfg(test)]
let actual = tests::extent(actual);
if actual != plan.allocation_size {
return Err(MemError::BlockExtentMismatch {
expected: plan.allocation_size,
actual,
});
}
let result = native_call!(Lock, tests::LOCK_ERROR, unsafe {
IOSurfaceLock(surface.raw(), 0, ptr::null_mut())
});
if result != KERN_SUCCESS {
return Err(MemError::BlockLockFailed(result));
}
surface.locked = true;
#[cfg(test)]
tests::record(Event::Locked);
let base = unsafe { IOSurfaceGetBaseAddress(surface.raw()).cast::<u8>() };
#[cfg(test)]
let base = tests::base(base);
let va = NonNull::new(base).ok_or(MemError::BlockAddressInvalid)?;
let address = base as usize;
let alignment = core::mem::align_of::<f32>().max(core::mem::align_of::<u16>());
if !address.is_multiple_of(alignment) || address.checked_add(actual).is_none() {
return Err(MemError::BlockAddressInvalid);
}
#[cfg(test)]
tests::record(Event::Call(Call::Id));
let id = unsafe { IOSurfaceGetID(surface.raw()) };
Ok(Block {
raw: surface.into_raw(),
va,
size: actual,
id,
})
}
unsafe fn release(raw: CFTypeRef) {
unsafe { CFRelease(raw) }
#[cfg(test)]
tests::record(Event::Release(raw as usize));
}
unsafe fn unlock(raw: IOSurfaceRef) {
unsafe { IOSurfaceUnlock(raw, 0, ptr::null_mut()) };
#[cfg(test)]
tests::record(Event::Unlocked);
}
pub(super) unsafe fn unlock_and_release(raw: IOSurfaceRef) {
unsafe {
unlock(raw);
release(raw);
}
}