use std::ffi::c_void;
use std::ptr::NonNull;
use std::sync::Arc;
use block2::RcBlock;
use objc2::msg_send;
use objc2::rc::Retained;
use objc2::runtime::{AnyObject, ProtocolObject};
use objc2_metal::{MTLCommandBuffer, MTLDrawable};
use super::PresentSink;
pub struct AcquiredDrawable {
drawable: Retained<ProtocolObject<dyn MTLDrawable>>,
texture: NonNull<AnyObject>,
}
pub unsafe fn acquire_drawable(layer: NonNull<c_void>) -> Option<AcquiredDrawable> {
unsafe {
let layer_obj: *mut AnyObject = layer.as_ptr().cast();
let raw: *mut AnyObject = msg_send![layer_obj, nextDrawable];
if raw.is_null() {
return None;
}
let texture: *mut AnyObject = msg_send![raw, texture];
let texture = NonNull::new(texture)?;
let drawable: Retained<ProtocolObject<dyn MTLDrawable>> = Retained::retain(raw.cast())?;
static CHECKED: std::sync::Once = std::sync::Once::new();
CHECKED.call_once(|| {
let id = drawable.drawableID();
log::info!("MetalFX dual presentation: drawable cast OK — drawableID {id}");
});
Some(AcquiredDrawable { drawable, texture })
}
}
#[allow(clippy::too_many_arguments)]
pub unsafe fn present_pair_deferred(
cmd_buf_ptr: *mut c_void,
layer: NonNull<c_void>,
queue: NonNull<c_void>,
interp_tex: *mut c_void,
real_tex: *mut c_void,
refresh_interval: f64,
sink: &Arc<PresentSink>,
single: bool,
) {
if cmd_buf_ptr.is_null() || interp_tex.is_null() || real_tex.is_null() {
return;
}
unsafe {
let cmd_buf: &ProtocolObject<dyn MTLCommandBuffer> =
&*(cmd_buf_ptr as *const ProtocolObject<dyn MTLCommandBuffer>);
static FORMAT_SET: std::sync::Once = std::sync::Once::new();
FORMAT_SET.call_once(|| {
let layer_obj: *mut AnyObject = layer.as_ptr().cast();
let _: () = msg_send![layer_obj, setPixelFormat: 81usize];
log::info!("MetalFX dual presentation: owned layer pinned to BGRA8Unorm_sRGB");
});
let _: *mut AnyObject = msg_send![interp_tex.cast::<AnyObject>(), retain];
let _: *mut AnyObject = msg_send![real_tex.cast::<AnyObject>(), retain];
let layer_addr = layer.as_ptr() as usize;
let queue_addr = queue.as_ptr() as usize;
let interp_addr = interp_tex as usize;
let real_addr = real_tex as usize;
let sink = Arc::clone(sink);
let handler = RcBlock::new(
move |_done: NonNull<ProtocolObject<dyn MTLCommandBuffer>>| {
sink.push_committed();
let layer_obj = layer_addr as *mut AnyObject;
let interp_drawable = if single {
None
} else {
match acquire_drawable(NonNull::new_unchecked(layer_addr as *mut c_void)) {
Some(d) => Some(d),
None => {
sink.push_dropped();
return;
}
}
};
let Some(real_drawable) =
acquire_drawable(NonNull::new_unchecked(layer_addr as *mut c_void))
else {
sink.push_dropped();
return;
};
let _ = layer_obj;
let queue_obj = queue_addr as *mut AnyObject;
let cb: *mut AnyObject = msg_send![queue_obj, commandBuffer];
if cb.is_null() {
sink.push_dropped();
return;
}
let blit: *mut AnyObject = msg_send![cb, blitCommandEncoder];
if blit.is_null() {
sink.push_dropped();
return;
}
if let Some(d) = &interp_drawable {
let _: () = msg_send![
blit,
copyFromTexture: interp_addr as *mut AnyObject,
toTexture: d.texture.as_ptr()
];
}
let _: () = msg_send![
blit,
copyFromTexture: real_addr as *mut AnyObject,
toTexture: real_drawable.texture.as_ptr()
];
let _: () = msg_send![blit, endEncoding];
let block_ptr = sink.presented_handler_block() as *mut _;
if let Some(d) = &interp_drawable {
d.drawable.addPresentedHandler(block_ptr);
}
real_drawable.drawable.addPresentedHandler(block_ptr);
let cb_obj: &ProtocolObject<dyn MTLCommandBuffer> =
&*(cb as *const ProtocolObject<dyn MTLCommandBuffer>);
if let Some(d) = &interp_drawable {
cb_obj.presentDrawable(&d.drawable);
cb_obj.presentDrawable_afterMinimumDuration(
&real_drawable.drawable,
refresh_interval,
);
sink.push_encoded();
} else {
cb_obj.presentDrawable(&real_drawable.drawable);
}
cb_obj.commit();
sink.push_encoded();
},
);
cmd_buf.addCompletedHandler(&*handler as *const _ as *mut _);
core::mem::forget(handler);
}
}