use objc2::{
AnyThread, DefinedClass, MainThreadMarker, MainThreadOnly, Message, define_class, msg_send,
rc::Retained,
runtime::{AnyClass, ProtocolObject},
};
use objc2_foundation::{NSObject, NSObjectProtocol, NSRunLoop, NSRunLoopCommonModes};
use objc2_quartz_core::{
CAMetalDisplayLink, CAMetalDisplayLinkDelegate, CAMetalDisplayLinkUpdate, CAMetalLayer,
};
struct MetalDisplayLinkDelegateIvars {
callback: Box<dyn Fn(&CAMetalDisplayLinkUpdate)>,
}
define_class!(
#[unsafe(super(NSObject))]
#[thread_kind = MainThreadOnly]
#[ivars = MetalDisplayLinkDelegateIvars]
struct MetalDisplayLinkDelegate;
unsafe impl NSObjectProtocol for MetalDisplayLinkDelegate {}
unsafe impl CAMetalDisplayLinkDelegate for MetalDisplayLinkDelegate {
#[unsafe(method(metalDisplayLink:needsUpdate:))]
fn needs_update(&self, _link: &CAMetalDisplayLink, update: &CAMetalDisplayLinkUpdate) {
let delegate = self.retain();
(delegate.ivars().callback)(update);
}
}
);
impl MetalDisplayLinkDelegate {
fn new(
main_thread: MainThreadMarker,
callback: impl Fn(&CAMetalDisplayLinkUpdate) + 'static,
) -> Retained<Self> {
let this = Self::alloc(main_thread).set_ivars(MetalDisplayLinkDelegateIvars {
callback: Box::new(callback),
});
unsafe { msg_send![super(this), init] }
}
}
pub struct MetalFrameSource {
link: Retained<CAMetalDisplayLink>,
_delegate: Retained<MetalDisplayLinkDelegate>,
_main_thread: MainThreadMarker,
}
impl MetalFrameSource {
pub fn is_available() -> bool {
AnyClass::get(c"CAMetalDisplayLink").is_some()
}
pub fn new(
layer: &CAMetalLayer,
main_thread: MainThreadMarker,
callback: impl Fn(&CAMetalDisplayLinkUpdate) + 'static,
) -> Option<Self> {
if !Self::is_available() {
return None;
}
let delegate = MetalDisplayLinkDelegate::new(main_thread, callback);
let link = CAMetalDisplayLink::initWithMetalLayer(CAMetalDisplayLink::alloc(), layer);
link.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));
unsafe { link.addToRunLoop_forMode(&NSRunLoop::mainRunLoop(), NSRunLoopCommonModes) };
Some(Self {
link,
_delegate: delegate,
_main_thread: main_thread,
})
}
pub fn set_paused(&self, paused: bool) {
self.link.setPaused(paused);
}
}
impl Drop for MetalFrameSource {
fn drop(&mut self) {
self.link.invalidate();
}
}