use std::sync::atomic::{AtomicU64, Ordering};
use objc2::rc::Retained;
use objc2::runtime::ProtocolObject;
use objc2_metal::{MTLDevice, MTLSharedEvent, MTLSharedEventHandle};
#[cfg(all(
feature = "wgpu-28",
not(feature = "wgpu-29"),
not(feature = "wgpu-30")
))]
use foreign_types_shared::ForeignType;
use crate::{
HostWgpuContext, ImportedTexture, InteropBackend, InteropError, InteropSynchronizer,
NativeFrame, SyncMechanism,
};
pub struct MetalSharedEventSynchronizer {
shared_event: Retained<ProtocolObject<dyn MTLSharedEvent>>,
next_value: AtomicU64,
timeout_ms: u64,
}
unsafe impl Send for MetalSharedEventSynchronizer {}
unsafe impl Sync for MetalSharedEventSynchronizer {}
impl MetalSharedEventSynchronizer {
pub const DEFAULT_TIMEOUT_MS: u64 = 5_000;
pub fn new(host: &HostWgpuContext) -> Result<Self, InteropError> {
Self::with_timeout(host, Self::DEFAULT_TIMEOUT_MS)
}
pub fn with_timeout(host: &HostWgpuContext, timeout_ms: u64) -> Result<Self, InteropError> {
if host.backend != InteropBackend::Metal {
return Err(InteropError::BackendMismatch {
expected: "Metal",
actual: "non-Metal",
});
}
let shared_event = unsafe {
let hal_device = host.device.as_hal::<wgpu::wgc::api::Metal>().ok_or(
InteropError::BackendMismatch {
expected: "Metal",
actual: "non-Metal",
},
)?;
#[cfg(all(
feature = "wgpu-28",
not(feature = "wgpu-29"),
not(feature = "wgpu-30")
))]
let device = Retained::retain(
hal_device.raw_device().as_ptr().cast::<ProtocolObject<dyn MTLDevice>>(),
)
.ok_or_else(|| InteropError::Metal("failed to retain Metal device".into()))?;
#[cfg(any(feature = "wgpu-29", feature = "wgpu-30"))]
let device = hal_device.raw_device().clone();
drop(hal_device);
device
.newSharedEvent()
.ok_or_else(|| InteropError::Metal("newSharedEvent returned nil".into()))?
};
Ok(Self {
shared_event,
next_value: AtomicU64::new(0),
timeout_ms,
})
}
pub fn new_shared_event_handle(&self) -> Retained<MTLSharedEventHandle> {
self.shared_event.newSharedEventHandle()
}
pub fn shared_event(&self) -> &ProtocolObject<dyn MTLSharedEvent> {
&self.shared_event
}
pub fn advance(&self) -> u64 {
self.next_value.fetch_add(1, Ordering::SeqCst) + 1
}
pub fn current_value(&self) -> u64 {
self.next_value.load(Ordering::SeqCst)
}
}
impl InteropSynchronizer for MetalSharedEventSynchronizer {
fn producer_complete(
&self,
_frame: &NativeFrame,
mechanism: SyncMechanism,
) -> Result<(), InteropError> {
match mechanism {
SyncMechanism::ExplicitFence => {
let target = self.next_value.load(Ordering::SeqCst);
if target == 0 {
return Ok(());
}
let signaled = self
.shared_event
.waitUntilSignaledValue_timeoutMS(target, self.timeout_ms);
if !signaled {
return Err(InteropError::Metal(format!(
"MTLSharedEvent wait timed out at value {} after {}ms",
target, self.timeout_ms
)));
}
Ok(())
}
SyncMechanism::None | SyncMechanism::ImplicitGlFlush => Ok(()),
other => Err(InteropError::UnsupportedSynchronization(other)),
}
}
fn consumer_ready(
&self,
_texture: &ImportedTexture,
mechanism: SyncMechanism,
) -> Result<(), InteropError> {
match mechanism {
SyncMechanism::None
| SyncMechanism::ImplicitGlFlush
| SyncMechanism::ExplicitFence => Ok(()),
other => Err(InteropError::UnsupportedSynchronization(other)),
}
}
}