use std::ffi::c_void;
use std::ptr::NonNull;
use objc2::runtime::AnyObject;
use objc2::{class, msg_send};
pub unsafe fn find_metal_layer(ns_view: *mut c_void) -> Option<NonNull<c_void>> {
if ns_view.is_null() {
return None;
}
unsafe {
let view: *mut AnyObject = ns_view.cast();
let root: *mut AnyObject = msg_send![view, layer];
if root.is_null() {
return None;
}
let metal_class = class!(CAMetalLayer);
let root_is_metal: bool = msg_send![root, isKindOfClass: metal_class];
if root_is_metal {
return NonNull::new(root.cast());
}
let sublayers: *mut AnyObject = msg_send![root, sublayers];
if sublayers.is_null() {
return None;
}
let count: usize = msg_send![sublayers, count];
for i in 0..count {
let layer: *mut AnyObject = msg_send![sublayers, objectAtIndex: i];
if layer.is_null() {
continue;
}
let is_metal: bool = msg_send![layer, isKindOfClass: metal_class];
if is_metal {
log_layer_identity(layer, count);
return NonNull::new(layer.cast());
}
}
None
}
}
unsafe fn log_layer_identity(layer: *mut AnyObject, sublayer_count: usize) {
unsafe {
let size: CGSize = msg_send![layer, drawableSize];
let pixel_format: usize = msg_send![layer, pixelFormat];
let device: *mut AnyObject = msg_send![layer, device];
let max_drawables: usize = msg_send![layer, maximumDrawableCount];
let framebuffer_only: bool = msg_send![layer, framebufferOnly];
let display_sync: bool = msg_send![layer, displaySyncEnabled];
log::info!(
"MetalFX dual presentation: layer {:p} — drawableSize {}x{}, pixelFormat {}, \
maxDrawables {}, framebufferOnly {}, displaySync {}, device {}, \
{} sublayer(s) on the root",
layer,
size.width,
size.height,
pixel_format,
max_drawables,
framebuffer_only,
display_sync,
if device.is_null() { "none" } else { "set" },
sublayer_count,
);
}
}
#[repr(C)]
#[derive(Clone, Copy)]
struct CGSize {
width: f64,
height: f64,
}
#[repr(C)]
#[derive(Clone, Copy)]
struct CGPoint {
x: f64,
y: f64,
}
#[repr(C)]
#[derive(Clone, Copy)]
struct CGRect {
origin: CGPoint,
size: CGSize,
}
unsafe impl objc2::Encode for CGSize {
const ENCODING: objc2::Encoding = objc2::Encoding::Struct(
"CGSize",
&[objc2::Encoding::Double, objc2::Encoding::Double],
);
}
unsafe impl objc2::Encode for CGPoint {
const ENCODING: objc2::Encoding = objc2::Encoding::Struct(
"CGPoint",
&[objc2::Encoding::Double, objc2::Encoding::Double],
);
}
unsafe impl objc2::Encode for CGRect {
const ENCODING: objc2::Encoding =
objc2::Encoding::Struct("CGRect", &[CGPoint::ENCODING, CGSize::ENCODING]);
}
pub unsafe fn create_owned_layer(
reference: NonNull<c_void>,
ns_view: *mut c_void,
) -> Option<(NonNull<c_void>, NonNull<c_void>)> {
unsafe {
let reference: *mut AnyObject = reference.as_ptr().cast();
let superlayer: *mut AnyObject = msg_send![reference, superlayer];
if superlayer.is_null() {
log::warn!("MetalFX dual presentation: wgpu's layer has no superlayer to attach to");
return None;
}
let device: *mut AnyObject = msg_send![reference, device];
if device.is_null() {
log::warn!("MetalFX dual presentation: wgpu's layer has no MTLDevice yet");
return None;
}
let pixel_format: usize = msg_send![reference, pixelFormat];
let drawable_size: CGSize = msg_send![reference, drawableSize];
let scale: f64 = msg_send![reference, contentsScale];
let ref_frame: CGRect = msg_send![reference, frame];
let super_bounds: CGRect = msg_send![superlayer, bounds];
let frame = if ref_frame.size.width > 0.0 && ref_frame.size.height > 0.0 {
ref_frame
} else {
super_bounds
};
log::info!(
"MetalFX dual presentation: geometry — wgpu layer frame {}x{}, \
superlayer bounds {}x{}, using {}x{}",
ref_frame.size.width,
ref_frame.size.height,
super_bounds.size.width,
super_bounds.size.height,
frame.size.width,
frame.size.height,
);
let cls = class!(CAMetalLayer);
let layer: *mut AnyObject = msg_send![cls, layer];
if layer.is_null() {
return None;
}
let transaction = class!(CATransaction);
let _: () = msg_send![transaction, begin];
let _: () = msg_send![layer, setDevice: device];
let _: () = msg_send![layer, setPixelFormat: pixel_format];
let _: () = msg_send![layer, setDrawableSize: drawable_size];
let _: () = msg_send![layer, setContentsScale: scale];
let _: () = msg_send![layer, setFrame: frame];
let _: () = msg_send![layer, setDisplaySyncEnabled: true];
let _: () = msg_send![layer, setMaximumDrawableCount: 3usize];
let _: () = msg_send![layer, setFramebufferOnly: false];
let _: () = msg_send![layer, setOpaque: true];
let mut hosted = false;
if !ns_view.is_null() {
let parent: *mut AnyObject = ns_view.cast();
let view_alloc: *mut AnyObject = msg_send![class!(NSView), alloc];
let sub_view: *mut AnyObject = msg_send![view_alloc, initWithFrame: frame];
if !sub_view.is_null() {
let _: () = msg_send![sub_view, setWantsLayer: true];
let _: () = msg_send![sub_view, setLayer: layer];
let _: () = msg_send![parent, addSubview: sub_view];
hosted = true;
}
}
if !hosted {
let _: () = msg_send![superlayer, addSublayer: layer];
}
log::info!(
"MetalFX dual presentation: layer hosted on {}",
if hosted {
"its own NSView"
} else {
"a bare sublayer (fallback)"
}
);
let _: () = msg_send![transaction, commit];
let _: () = msg_send![transaction, flush];
let _: *mut AnyObject = msg_send![layer, retain];
log::info!(
"MetalFX dual presentation: owned CAMetalLayer created ({}x{}, format {}, scale {}) \
above wgpu's — presentation is no longer shared",
drawable_size.width,
drawable_size.height,
pixel_format,
scale,
);
let queue: *mut AnyObject = msg_send![device, newCommandQueue];
let queue = NonNull::new(queue.cast::<c_void>())?;
Some((NonNull::new(layer.cast())?, queue))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn null_pointers_are_rejected_rather_than_dereferenced() {
assert!(unsafe { find_metal_layer(std::ptr::null_mut()) }.is_none());
}
}