#[cfg(target_os = "macos")]
use std::sync::{Arc, Mutex};
#[cfg(target_os = "macos")]
use j2k_core::BackendKind;
#[cfg(target_os = "macos")]
use j2k_metal_support::{MetalRuntimeSession, MetalSupportError};
#[cfg(target_os = "macos")]
use metal::{
foreign_types::{ForeignType, ForeignTypeRef},
Device,
};
use crate::{batch, Error};
#[cfg(any(test, target_os = "macos"))]
mod cache;
#[cfg(target_os = "macos")]
pub(crate) mod direct_plan_cache;
#[cfg(target_os = "macos")]
pub(crate) use cache::{
PreparedPlanCache, PreparedPlanCacheKey, PreparedPlanCacheValue,
PREPARED_PLAN_CACHE_MAX_DEVICE_BYTES, PREPARED_PLAN_CACHE_MAX_HOST_BYTES,
};
#[cfg(target_os = "macos")]
pub(crate) struct MetalConsumerEventTimeline {
pub(crate) event: Option<metal::Event>,
pub(crate) next_value: u64,
}
#[cfg(target_os = "macos")]
impl MetalConsumerEventTimeline {
const fn new() -> Self {
Self {
event: None,
next_value: 0,
}
}
}
#[cfg(target_os = "macos")]
#[derive(Clone)]
pub struct MetalBackendSession {
runtime_session: MetalRuntimeSession<Arc<crate::compute::MetalRuntime>, MetalSupportError>,
command_queue: Option<metal::CommandQueue>,
direct_plan_caches: direct_plan_cache::DirectPlanCaches,
consumer_event_timeline: Arc<Mutex<MetalConsumerEventTimeline>>,
}
#[cfg(target_os = "macos")]
impl MetalBackendSession {
pub fn new(device: Device) -> Self {
Self::with_runtime_session(MetalRuntimeSession::new(device), None)
}
pub fn with_command_queue(
device: Device,
command_queue: metal::CommandQueue,
) -> Result<Self, Error> {
if command_queue.device().as_ptr() != device.as_ptr() {
return Err(Error::UnsupportedMetalRequest {
reason: "command queue belongs to a different Metal device",
});
}
Ok(Self::with_runtime_session(
MetalRuntimeSession::new(device),
Some(command_queue),
))
}
fn with_runtime_session(
runtime_session: MetalRuntimeSession<Arc<crate::compute::MetalRuntime>, MetalSupportError>,
command_queue: Option<metal::CommandQueue>,
) -> Self {
Self {
runtime_session,
command_queue,
direct_plan_caches: direct_plan_cache::DirectPlanCaches::new(),
consumer_event_timeline: Arc::new(Mutex::new(MetalConsumerEventTimeline::new())),
}
}
pub fn system_default() -> Result<Self, Error> {
MetalRuntimeSession::system_default()
.map(|runtime_session| Self::with_runtime_session(runtime_session, None))
.map_err(|error| crate::compute::runtime_initialization_error(&error))
}
pub fn device(&self) -> &metal::DeviceRef {
self.runtime_session.device()
}
pub(crate) fn device_handle(&self) -> &Device {
self.runtime_session.device_handle()
}
pub(crate) fn runtime(&self) -> Result<Arc<crate::compute::MetalRuntime>, Error> {
let command_queue = self.command_queue.clone();
match self.runtime_session.get_or_init_runtime(move |device| {
match command_queue {
Some(queue) => {
crate::compute::MetalRuntime::new_with_device_and_queue(device, queue)
}
None => crate::compute::MetalRuntime::new_with_device(device),
}
.map(Arc::new)
}) {
Ok(runtime) => Ok(runtime.clone()),
Err(error) => Err(crate::compute::runtime_initialization_error(error)),
}
}
pub fn buffer_pool_diagnostics(&self) -> Result<crate::MetalBufferPoolsDiagnostics, Error> {
self.runtime()?.buffer_pool_diagnostics()
}
#[doc(hidden)]
pub fn uses_command_queue(
&self,
command_queue: &metal::CommandQueueRef,
) -> Result<bool, Error> {
Ok(self.runtime()?.queue.as_ptr() == command_queue.as_ptr())
}
pub(crate) fn consumer_event_timeline(&self) -> Arc<Mutex<MetalConsumerEventTimeline>> {
self.consumer_event_timeline.clone()
}
}
#[cfg(target_os = "macos")]
#[doc(hidden)]
impl j2k_core::AcceleratorSession for MetalBackendSession {
fn backend_kind(&self) -> BackendKind {
BackendKind::Metal
}
}
#[cfg(target_os = "macos")]
impl core::fmt::Debug for MetalBackendSession {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("MetalBackendSession")
.field("device", &self.runtime_session.device_handle().name())
.finish_non_exhaustive()
}
}
#[cfg(not(target_os = "macos"))]
#[derive(Clone, Copy, Debug, Default)]
pub struct MetalBackendSession {
_private: (),
}
#[cfg(not(target_os = "macos"))]
impl MetalBackendSession {
pub fn system_default() -> Result<Self, Error> {
Err(Error::MetalUnavailable)
}
}
#[derive(Clone, Default)]
pub struct MetalSession {
pub(crate) shared: batch::SharedSession,
}
impl MetalSession {
#[cfg(target_os = "macos")]
pub fn with_backend_session(backend: MetalBackendSession) -> Self {
Self {
shared: batch::SharedSession::with_backend_session(backend),
}
}
#[cfg(target_os = "macos")]
pub fn backend_session(&self) -> Option<&MetalBackendSession> {
self.shared.backend_session()
}
pub fn submissions(&self) -> Result<u64, Error> {
Ok(self.shared.lock()?.submissions)
}
pub(crate) fn record_submit(&mut self) -> Result<(), Error> {
let mut session = self.shared.lock()?;
session.submissions = session.submissions.saturating_add(1);
Ok(())
}
}
impl core::fmt::Debug for MetalSession {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("MetalSession")
.field("submissions", &self.submissions())
.finish()
}
}