use crate::{Error, SurfaceAccess, SurfaceID, SurfaceType, SystemSurfaceInfo};
use super::device::Device;
use super::ffi::{IOSurfaceGetAllocSize, IOSurfaceGetBaseAddress, IOSurfaceGetBytesPerRow};
use super::ffi::{IOSurfaceLock, IOSurfaceUnlock, kCVPixelFormatType_32BGRA, kIOMapDefaultCache};
use super::ffi::{kIOMapWriteCombineCache, kCVReturnSuccess};
use cocoa::appkit::{NSScreen, NSView as NSViewMethods, NSWindow};
use cocoa::base::{YES, id};
use cocoa::foundation::{NSPoint, NSRect, NSSize};
use cocoa::quartzcore::{CALayer, CATransform3D, transaction};
use core_foundation::base::TCFType;
use core_foundation::dictionary::CFDictionary;
use core_foundation::number::CFNumber;
use core_foundation::string::CFString;
use core_graphics::geometry::{CGRect, CGSize, CG_ZERO_POINT};
use display_link::macos::cvdisplaylink::{CVDisplayLink, CVTimeStamp, DisplayLink};
use euclid::default::Size2D;
use io_surface::{self, IOSurface, IOSurfaceRef, kIOSurfaceBytesPerElement, kIOSurfaceBytesPerRow};
use io_surface::{kIOSurfaceCacheMode, kIOSurfaceHeight, kIOSurfacePixelFormat, kIOSurfaceWidth};
use mach::kern_return::KERN_SUCCESS;
use std::fmt::{self, Debug, Formatter};
use std::mem;
use std::os::raw::c_void;
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: IOSurface,
pub(crate) size: Size2D<i32>,
access: SurfaceAccess,
pub(crate) destroyed: bool,
pub(crate) view_info: Option<ViewInfo>,
}
#[derive(Clone)]
pub struct NativeSurface(pub 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: NSView,
layer: CALayer,
superlayer: CALayer,
front_surface: IOSurface,
logical_size: NSSize,
display_link: DisplayLink,
next_vblank: Arc<VblankCond>,
}
struct VblankCond {
mutex: Mutex<()>,
cond: Condvar,
}
#[derive(Clone)]
pub struct NSView(pub(crate) id);
#[derive(Clone)]
pub struct NativeWidget {
pub view: NSView,
}
pub struct SurfaceDataGuard<'a> {
surface: &'a mut Surface,
stride: usize,
ptr: *mut u8,
len: usize,
}
impl Device {
pub fn create_surface(&mut 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: id = msg_send![native_widget.view.0, window];
let bounds = window.convertRectToBacking(native_widget.view.0.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::from_scale(1.0, scale_y, 1.0).translate(0.0, translate_y, 0.0);
view_info.superlayer.set_sublayer_transform(sublayer_transform);
}
}
unsafe fn create_view_info(&mut self,
size: &Size2D<i32>,
surface_access: SurfaceAccess,
native_widget: &NativeWidget)
-> ViewInfo {
let front_surface = self.create_io_surface(&size, surface_access);
let window: id = msg_send![native_widget.view.0, window];
let device_description: CFDictionary<CFString, CFNumber> =
CFDictionary::wrap_under_get_rule(window.screen().deviceDescription() as *const _);
let description_key: CFString = CFString::from("NSScreenNumber");
let display_id = device_description.get(description_key).to_i64().unwrap() as u32;
let mut display_link = DisplayLink::on_display(display_id).unwrap();
let next_vblank = Arc::new(VblankCond { mutex: Mutex::new(()), cond: Condvar::new() });
display_link.set_output_callback(display_link_output_callback,
mem::transmute(next_vblank.clone()));
display_link.start();
transaction::begin();
transaction::set_disable_actions(true);
let superlayer = CALayer::new();
native_widget.view.0.setLayer(superlayer.id());
native_widget.view.0.setWantsLayer(YES);
let window: id = msg_send![native_widget.view.0, window];
let logical_rect: NSRect = msg_send![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 = CALayer::new();
let layer_size = CGSize::new(logical_size.width as f64, logical_size.height as f64);
layer.set_frame(&CGRect::new(&CG_ZERO_POINT, &layer_size));
layer.set_contents(front_surface.obj as id);
layer.set_opaque(true);
layer.set_contents_opaque(true);
superlayer.add_sublayer(&layer);
let view = native_widget.view.clone();
transaction::commit();
ViewInfo {
view,
layer,
superlayer,
front_surface,
logical_size,
display_link,
next_vblank,
}
}
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, size: Size2D<i32>) -> Result<(), Error> {
let view_info = match surface.view_info {
None => return Err(Error::NoWidgetAttached),
Some(ref mut view_info) => view_info,
};
transaction::begin();
transaction::set_disable_actions(true);
unsafe {
let window: id = msg_send![view_info.view.0, window];
let logical_rect: NSRect = msg_send![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::from_scale(1.0, -1.0, 1.0).translate(0.0, -logical_size.height, 0.0);
view_info.superlayer.set_sublayer_transform(sublayer_transform);
view_info.front_surface = self.create_io_surface(&size, surface.access);
view_info.layer.set_frame(&CGRect::new(&CG_ZERO_POINT, &layer_size));
view_info.layer.set_contents(view_info.front_surface.obj as id);
view_info.layer.set_opaque(true);
view_info.layer.set_contents_opaque(true);
surface.io_surface = self.create_io_surface(&size, surface.access);
surface.size = size;
}
transaction::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) -> IOSurface {
let cache_mode = match access {
SurfaceAccess::GPUCPUWriteCombined => kIOMapWriteCombineCache,
SurfaceAccess::GPUOnly | SurfaceAccess::GPUCPU => kIOMapDefaultCache,
};
unsafe {
let properties = CFDictionary::from_CFType_pairs(&[
(CFString::wrap_under_get_rule(kIOSurfaceWidth),
CFNumber::from(size.width).as_CFType()),
(CFString::wrap_under_get_rule(kIOSurfaceHeight),
CFNumber::from(size.height).as_CFType()),
(CFString::wrap_under_get_rule(kIOSurfaceBytesPerElement),
CFNumber::from(BYTES_PER_PIXEL).as_CFType()),
(CFString::wrap_under_get_rule(kIOSurfaceBytesPerRow),
CFNumber::from(size.width * BYTES_PER_PIXEL).as_CFType()),
(CFString::wrap_under_get_rule(kIOSurfacePixelFormat),
CFNumber::from(kCVPixelFormatType_32BGRA).as_CFType()),
(CFString::wrap_under_get_rule(kIOSurfaceCacheMode),
CFNumber::from(cache_mode).as_CFType()),
]);
io_surface::new(&properties)
}
}
#[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();
let io_surface_ref = io_surface.as_concrete_TypeRef();
mem::forget(io_surface);
NativeSurface(io_surface_ref)
}
}
impl Surface {
#[inline]
fn id(&self) -> SurfaceID {
SurfaceID(self.io_surface.as_concrete_TypeRef() as usize)
}
fn present(&mut self) -> Result<(), Error> {
unsafe {
transaction::begin();
transaction::set_disable_actions(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.set_contents(view_info.front_surface.obj as id);
transaction::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(&mut self) -> Result<SurfaceDataGuard, Error> {
if !self.access.cpu_access_allowed() {
return Err(Error::SurfaceDataInaccessible);
}
unsafe {
let mut seed = 0;
let result = IOSurfaceLock(self.io_surface.as_concrete_TypeRef(), 0, &mut seed);
if result != KERN_SUCCESS {
return Err(Error::SurfaceLockFailed);
}
let ptr = IOSurfaceGetBaseAddress(self.io_surface.as_concrete_TypeRef()) as *mut u8;
let len = IOSurfaceGetAllocSize(self.io_surface.as_concrete_TypeRef());
let stride = IOSurfaceGetBytesPerRow(self.io_surface.as_concrete_TypeRef());
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, self.len)
}
}
}
impl<'a> Drop for SurfaceDataGuard<'a> {
#[inline]
fn drop(&mut self) {
unsafe {
let mut seed = 0;
IOSurfaceUnlock(self.surface.io_surface.as_concrete_TypeRef(), 0, &mut seed);
}
}
}
impl Drop for NativeWidget {
#[inline]
fn drop(&mut self) {
unsafe {
let () = msg_send![self.view.0, release];
}
}
}
impl Drop for ViewInfo {
#[inline]
fn drop(&mut self) {
unsafe {
self.display_link.stop();
mem::transmute_copy::<Arc<VblankCond>, Arc<VblankCond>>(&self.next_vblank);
}
}
}
unsafe extern "C" fn display_link_output_callback(_: *mut CVDisplayLink,
_: *const CVTimeStamp,
_: *const CVTimeStamp,
_: i64,
_: *mut i64,
user_data: *mut c_void)
-> i32 {
let next_vblank: Arc<VblankCond> = mem::transmute(user_data);
{
let _guard = next_vblank.mutex.lock().unwrap();
next_vblank.cond.notify_all();
}
mem::forget(next_vblank);
kCVReturnSuccess
}