use super::device::Device;
use super::ffi::{kIOMapDefaultCache, kIOMapWriteCombineCache};
use crate::{Error, SurfaceAccess, SurfaceID, SurfaceType, SystemSurfaceInfo};
use euclid::default::Size2D;
use libc::KERN_SUCCESS;
use objc2::msg_send;
use objc2::rc::Retained;
use objc2_app_kit::NSView;
use objc2_core_foundation::{
kCFAllocatorDefault, kCFTypeDictionaryKeyCallBacks, kCFTypeDictionaryValueCallBacks,
CFDictionary, CFIndex, CFNumber, CFRetained, CFString, CGPoint, CGRect, CGSize,
};
#[allow(deprecated)]
use objc2_core_video::{
kCVPixelFormatType_32BGRA, kCVReturnSuccess, CVDisplayLink, CVDisplayLinkCreateWithCGDisplay,
CVDisplayLinkSetOutputCallback, CVDisplayLinkStart, CVDisplayLinkStop, CVOptionFlags, CVReturn,
CVTimeStamp,
};
use objc2_foundation::{ns_string, NSNumber, NSPoint, NSRect, NSSize};
use objc2_io_surface::{
kIOSurfaceBytesPerElement, kIOSurfaceBytesPerRow, kIOSurfaceCacheMode, kIOSurfaceHeight,
kIOSurfacePixelFormat, kIOSurfaceWidth, IOSurfaceLockOptions, IOSurfaceRef,
};
use objc2_quartz_core::{CALayer, CATransaction, CATransform3D};
use std::fmt::{self, Debug, Formatter};
use std::mem;
use std::os::raw::c_void;
use std::ptr::{self, NonNull};
use std::slice;
use std::sync::{Arc, Condvar, Mutex};
use std::thread;
const BYTES_PER_PIXEL: i32 = 4;
pub struct Surface {
pub(crate) io_surface: CFRetained<IOSurfaceRef>,
pub(crate) size: Size2D<i32>,
access: SurfaceAccess,
pub(crate) destroyed: bool,
pub(crate) view_info: Option<ViewInfo>,
}
#[derive(Clone)]
pub struct NativeSurface(pub CFRetained<IOSurfaceRef>);
unsafe impl Send for Surface {}
impl Debug for Surface {
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
write!(formatter, "Surface({:x})", self.id().0)
}
}
impl Drop for Surface {
fn drop(&mut self) {
if !self.destroyed && !thread::panicking() {
panic!("Should have destroyed the surface first with `destroy_surface()`!")
}
}
}
pub(crate) struct ViewInfo {
view: Retained<NSView>,
layer: Retained<CALayer>,
superlayer: Retained<CALayer>,
front_surface: CFRetained<IOSurfaceRef>,
logical_size: NSSize,
display_link: CFRetained<CVDisplayLink>,
next_vblank: Arc<VblankCond>,
opaque: bool,
}
struct VblankCond {
mutex: Mutex<()>,
cond: Condvar,
}
#[derive(Clone)]
pub struct NativeWidget {
pub view: Retained<NSView>,
pub opaque: bool,
}
pub struct SurfaceDataGuard<'a> {
surface: &'a mut Surface,
stride: usize,
ptr: NonNull<u8>,
len: usize,
}
impl Device {
pub fn create_surface(
&self,
access: SurfaceAccess,
surface_type: SurfaceType<NativeWidget>,
) -> Result<Surface, Error> {
unsafe {
let size = match surface_type {
SurfaceType::Generic { size } => size,
SurfaceType::Widget { ref native_widget } => {
let window = native_widget
.view
.window()
.expect("view must be installed in a window");
let bounds = window.convertRectToBacking(native_widget.view.bounds());
let mut width = bounds.size.width as i32;
let height = bounds.size.height as i32;
if width % 4 != 0 {
width += 4 - width % 4;
}
Size2D::new(width, height)
}
};
let io_surface = self.create_io_surface(&size, access);
let view_info = match surface_type {
SurfaceType::Generic { .. } => None,
SurfaceType::Widget {
ref native_widget, ..
} => Some(self.create_view_info(&size, access, native_widget)),
};
Ok(Surface {
io_surface,
size,
access,
destroyed: false,
view_info,
})
}
}
pub(crate) fn set_surface_flipped(&self, surface: &mut Surface, flipped: bool) {
if let Some(ref mut view_info) = surface.view_info {
let (scale_y, translate_y) = if flipped {
(-1.0, -view_info.logical_size.height)
} else {
(1.0, 0.0)
};
let sublayer_transform =
CATransform3D::new_scale(1.0, scale_y, 1.0).translate(0.0, translate_y, 0.0);
view_info
.superlayer
.setSublayerTransform(sublayer_transform);
}
}
#[allow(deprecated)]
unsafe fn create_view_info(
&self,
size: &Size2D<i32>,
surface_access: SurfaceAccess,
native_widget: &NativeWidget,
) -> ViewInfo {
let front_surface = self.create_io_surface(size, surface_access);
let window = native_widget
.view
.window()
.expect("view must be installed in a window");
let device_description = window.screen().unwrap().deviceDescription();
let display_id = device_description
.objectForKey(ns_string!("NSScreenNumber"))
.unwrap()
.downcast::<NSNumber>()
.unwrap()
.as_u32();
let mut display_link = ptr::null_mut();
assert_eq!(
CVDisplayLinkCreateWithCGDisplay(display_id, NonNull::from(&mut display_link)),
kCVReturnSuccess,
);
let display_link = CFRetained::from_raw(NonNull::new(display_link).unwrap());
let next_vblank = Arc::new(VblankCond {
mutex: Mutex::new(()),
cond: Condvar::new(),
});
CVDisplayLinkSetOutputCallback(
&display_link,
Some(display_link_output_callback),
mem::transmute(next_vblank.clone()),
);
CVDisplayLinkStart(&display_link);
CATransaction::begin();
CATransaction::setDisableActions(true);
let superlayer = CALayer::new();
native_widget.view.setLayer(Some(&superlayer));
native_widget.view.setWantsLayer(true);
let window = native_widget
.view
.window()
.expect("view must be installed in a window");
let logical_rect = window.convertRectFromBacking(NSRect {
origin: NSPoint { x: 0.0, y: 0.0 },
size: NSSize {
width: size.width as f64,
height: size.height as f64,
},
});
let logical_size = logical_rect.size;
let opaque = native_widget.opaque;
let layer = CALayer::new();
let layer_size = CGSize::new(logical_size.width as f64, logical_size.height as f64);
layer.setFrame(CGRect::new(CGPoint::ZERO, layer_size));
layer.setContents(Some(front_surface.as_ref()));
layer.setOpaque(opaque);
let _: () = unsafe { msg_send![&layer, setContentsOpaque: opaque] };
superlayer.addSublayer(&layer);
let view = native_widget.view.clone();
CATransaction::commit();
ViewInfo {
view,
layer,
superlayer,
front_surface,
logical_size,
display_link,
next_vblank,
opaque,
}
}
pub fn destroy_surface(&self, surface: &mut Surface) -> Result<(), Error> {
surface.destroyed = true;
Ok(())
}
pub fn present_surface(&self, surface: &mut Surface) -> Result<(), Error> {
surface.present()
}
pub fn resize_surface(
&self,
surface: &mut Surface,
mut size: Size2D<i32>,
) -> Result<(), Error> {
let width = size.width as i32;
if width % 4 != 0 {
size.width = width + 4 - width % 4;
}
let view_info = match surface.view_info {
None => return Err(Error::NoWidgetAttached),
Some(ref mut view_info) => view_info,
};
CATransaction::begin();
CATransaction::setDisableActions(true);
let window = view_info
.view
.window()
.expect("view must be installed in a window");
let logical_rect = window.convertRectFromBacking(NSRect {
origin: NSPoint { x: 0.0, y: 0.0 },
size: NSSize {
width: size.width as f64,
height: size.height as f64,
},
});
let logical_size = logical_rect.size;
let layer_size = CGSize::new(logical_size.width as f64, logical_size.height as f64);
let sublayer_transform =
CATransform3D::new_scale(1.0, -1.0, 1.0).translate(0.0, -logical_size.height, 0.0);
view_info
.superlayer
.setSublayerTransform(sublayer_transform);
view_info.front_surface = self.create_io_surface(&size, surface.access);
view_info
.layer
.setFrame(CGRect::new(CGPoint::ZERO, layer_size));
unsafe {
view_info
.layer
.setContents(Some(view_info.front_surface.as_ref()))
};
view_info.layer.setOpaque(view_info.opaque);
let _: () = unsafe { msg_send![&view_info.layer, setContentsOpaque: view_info.opaque] };
surface.io_surface = self.create_io_surface(&size, surface.access);
surface.size = size;
CATransaction::commit();
Ok(())
}
#[inline]
pub fn lock_surface_data<'s>(
&self,
surface: &'s mut Surface,
) -> Result<SurfaceDataGuard<'s>, Error> {
surface.lock_data()
}
fn create_io_surface(
&self,
size: &Size2D<i32>,
access: SurfaceAccess,
) -> CFRetained<IOSurfaceRef> {
let cache_mode = match access {
SurfaceAccess::GPUCPUWriteCombined => kIOMapWriteCombineCache,
SurfaceAccess::GPUOnly | SurfaceAccess::GPUCPU => kIOMapDefaultCache,
};
unsafe {
let bytes_per_row = IOSurfaceRef::align_property(
kIOSurfaceBytesPerRow,
(size.width * BYTES_PER_PIXEL) as usize,
) as i32;
let keys = [
kIOSurfaceWidth,
kIOSurfaceHeight,
kIOSurfaceBytesPerElement,
kIOSurfaceBytesPerRow,
kIOSurfacePixelFormat,
kIOSurfaceCacheMode,
];
let values = [
&*CFNumber::new_i32(size.width),
&*CFNumber::new_i32(size.height),
&*CFNumber::new_i32(BYTES_PER_PIXEL),
&*CFNumber::new_i32(bytes_per_row),
&*CFNumber::new_i32(kCVPixelFormatType_32BGRA as i32),
&*CFNumber::new_i32(cache_mode),
];
assert_eq!(keys.len(), values.len());
let len = keys.len() as CFIndex;
let keys: *const &CFString = keys.as_ptr();
let keys: *mut *const c_void = keys as _;
let values: *const &CFNumber = values.as_ptr();
let values: *mut *const c_void = values as _;
let properties = CFDictionary::new(
kCFAllocatorDefault,
keys,
values,
len,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks,
)
.unwrap();
IOSurfaceRef::new(&properties).unwrap()
}
}
#[inline]
pub fn surface_info(&self, surface: &Surface) -> SystemSurfaceInfo {
SystemSurfaceInfo {
size: surface.size,
id: surface.id(),
}
}
#[inline]
pub fn native_surface(&self, surface: &Surface) -> NativeSurface {
let io_surface = surface.io_surface.clone();
NativeSurface(io_surface)
}
}
impl Surface {
#[inline]
fn id(&self) -> SurfaceID {
SurfaceID(&*self.io_surface as *const IOSurfaceRef as usize)
}
fn present(&mut self) -> Result<(), Error> {
unsafe {
CATransaction::begin();
CATransaction::setDisableActions(true);
let view_info = match self.view_info {
None => return Err(Error::NoWidgetAttached),
Some(ref mut view_info) => view_info,
};
mem::swap(&mut view_info.front_surface, &mut self.io_surface);
view_info
.layer
.setContents(Some(view_info.front_surface.as_ref()));
CATransaction::commit();
let next_vblank_mutex_guard = view_info.next_vblank.mutex.lock().unwrap();
drop(
view_info
.next_vblank
.cond
.wait(next_vblank_mutex_guard)
.unwrap(),
);
Ok(())
}
}
pub(crate) fn lock_data<'a>(&'a mut self) -> Result<SurfaceDataGuard<'a>, Error> {
if !self.access.cpu_access_allowed() {
return Err(Error::SurfaceDataInaccessible);
}
unsafe {
let mut seed = 0;
let result = self
.io_surface
.lock(IOSurfaceLockOptions::empty(), &mut seed);
if result != KERN_SUCCESS {
return Err(Error::SurfaceLockFailed);
}
let ptr = self.io_surface.base_address().cast::<u8>();
let len = self.io_surface.alloc_size();
let stride = self.io_surface.bytes_per_row();
Ok(SurfaceDataGuard {
surface: &mut *self,
stride,
ptr,
len,
})
}
}
}
impl<'a> SurfaceDataGuard<'a> {
#[inline]
pub fn stride(&self) -> usize {
self.stride
}
#[inline]
pub fn data(&mut self) -> &mut [u8] {
unsafe { slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len) }
}
}
impl<'a> Drop for SurfaceDataGuard<'a> {
#[inline]
fn drop(&mut self) {
unsafe {
let mut seed = 0;
self.surface
.io_surface
.unlock(IOSurfaceLockOptions::empty(), &mut seed);
}
}
}
impl Drop for ViewInfo {
#[inline]
fn drop(&mut self) {
unsafe {
#[allow(deprecated)]
CVDisplayLinkStop(&self.display_link);
let _ = mem::transmute_copy::<Arc<VblankCond>, Arc<VblankCond>>(&self.next_vblank);
self.layer.removeFromSuperlayer();
}
}
}
unsafe extern "C-unwind" fn display_link_output_callback(
_display_link: NonNull<CVDisplayLink>,
_in_now: NonNull<CVTimeStamp>,
_in_output_time: NonNull<CVTimeStamp>,
_flags_n: CVOptionFlags,
_flags_out: NonNull<CVOptionFlags>,
display_link_context: *mut c_void,
) -> CVReturn {
let next_vblank: Arc<VblankCond> = mem::transmute(display_link_context);
{
let _guard = next_vblank.mutex.lock().unwrap();
next_vblank.cond.notify_all();
}
mem::forget(next_vblank);
kCVReturnSuccess
}