#[cfg(target_os = "macos")]
use burn_core::tensor::backend::Backend;
use burn_wgpu::{Wgpu, WgpuDevice};
use j2k::{BatchDecodeOptions, EncodedImage, PreparedBatch, PreparedImage};
use j2k_metal::MetalBatchDecoder as CodecDecoder;
#[cfg(target_os = "macos")]
use j2k_metal::{MetalBatchDecodeResult, SubmittedMetalPreparedBatch};
#[cfg(target_os = "macos")]
use crate::BurnBatchGroupError;
use crate::{BurnBatchDecode, BurnDecodeError};
#[cfg(target_os = "macos")]
#[must_use = "submitted Metal upload batches must be waited or dropped"]
pub struct SubmittedMetalBurnBatch {
pending: SubmittedMetalPreparedBatch,
device: WgpuDevice,
}
#[cfg(target_os = "macos")]
impl core::fmt::Debug for SubmittedMetalBurnBatch {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("SubmittedMetalBurnBatch")
.field("pending", &self.pending)
.field("device", &self.device)
.finish()
}
}
#[cfg(target_os = "macos")]
impl SubmittedMetalBurnBatch {
#[must_use]
pub fn len(&self) -> usize {
self.pending.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.pending.is_empty()
}
pub fn wait(self) -> Result<BurnBatchDecode<Wgpu>, BurnDecodeError> {
materialize(self.pending.wait()?, &self.device)
}
}
#[cfg(not(target_os = "macos"))]
#[derive(Debug)]
pub struct SubmittedMetalBurnBatch {
_private: (),
}
#[cfg(not(target_os = "macos"))]
impl SubmittedMetalBurnBatch {
#[must_use]
pub const fn len(&self) -> usize {
0
}
#[must_use]
pub const fn is_empty(&self) -> bool {
true
}
pub fn wait(self) -> Result<BurnBatchDecode<Wgpu>, BurnDecodeError> {
Err(unavailable())
}
}
pub struct MetalBurnDecoder {
codec: CodecDecoder,
device: WgpuDevice,
}
impl core::fmt::Debug for MetalBurnDecoder {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("MetalBurnDecoder")
.field("codec", &self.codec)
.field("device", &self.device)
.field("options", &self.codec.options())
.finish_non_exhaustive()
}
}
impl MetalBurnDecoder {
#[cfg(target_os = "macos")]
pub fn system_default(options: BatchDecodeOptions) -> Result<Self, BurnDecodeError> {
Ok(Self {
codec: CodecDecoder::system_default_with_options(options)?,
device: WgpuDevice::DefaultDevice,
})
}
#[cfg(not(target_os = "macos"))]
pub fn system_default(_options: BatchDecodeOptions) -> Result<Self, BurnDecodeError> {
Err(unavailable())
}
#[must_use]
pub const fn codec(&self) -> &CodecDecoder {
&self.codec
}
#[must_use]
pub const fn device(&self) -> &WgpuDevice {
&self.device
}
pub fn prepare(&self, inputs: Vec<EncodedImage>) -> Result<PreparedBatch, BurnDecodeError> {
Ok(self.codec.prepare(inputs)?)
}
pub fn prepare_prepared_images(
&self,
images: Vec<PreparedImage>,
) -> Result<PreparedBatch, BurnDecodeError> {
Ok(self.codec.prepare_prepared_images(images)?)
}
pub fn decode(
&mut self,
inputs: Vec<EncodedImage>,
) -> Result<BurnBatchDecode<Wgpu>, BurnDecodeError> {
self.submit(inputs)?.wait()
}
pub fn decode_prepared_images(
&mut self,
images: Vec<PreparedImage>,
) -> Result<BurnBatchDecode<Wgpu>, BurnDecodeError> {
let prepared = self.prepare_prepared_images(images)?;
self.decode_prepared(&prepared)
}
pub fn decode_prepared(
&mut self,
prepared: &PreparedBatch,
) -> Result<BurnBatchDecode<Wgpu>, BurnDecodeError> {
self.submit_prepared(prepared)?.wait()
}
#[cfg(target_os = "macos")]
pub fn submit(
&mut self,
inputs: Vec<EncodedImage>,
) -> Result<SubmittedMetalBurnBatch, BurnDecodeError> {
let prepared = self.prepare(inputs)?;
self.submit_prepared(&prepared)
}
#[cfg(not(target_os = "macos"))]
pub fn submit(
&mut self,
_inputs: Vec<EncodedImage>,
) -> Result<SubmittedMetalBurnBatch, BurnDecodeError> {
Err(unavailable())
}
#[cfg(target_os = "macos")]
pub fn submit_prepared(
&mut self,
prepared: &PreparedBatch,
) -> Result<SubmittedMetalBurnBatch, BurnDecodeError> {
ensure_dtypes::<Wgpu>(prepared, &self.device)?;
Ok(SubmittedMetalBurnBatch {
pending: self.codec.submit_prepared(prepared)?,
device: self.device.clone(),
})
}
#[cfg(not(target_os = "macos"))]
pub fn submit_prepared(
&mut self,
_prepared: &PreparedBatch,
) -> Result<SubmittedMetalBurnBatch, BurnDecodeError> {
Err(unavailable())
}
}
#[cfg(target_os = "macos")]
fn ensure_dtypes<B: Backend>(
prepared: &PreparedBatch,
device: &B::Device,
) -> Result<(), BurnDecodeError> {
for group in prepared.groups() {
let dtype = crate::batch_contract::dtype(group.info().sample_type)?;
if !B::supports_dtype(device, dtype) {
return Err(BurnDecodeError::UnsupportedDType { dtype });
}
}
Ok(())
}
#[cfg(target_os = "macos")]
fn materialize(
decoded: MetalBatchDecodeResult,
device: &WgpuDevice,
) -> Result<BurnBatchDecode<Wgpu>, BurnDecodeError> {
let (codec_groups, errors, codec_group_errors) = decoded.into_parts();
let mut groups = Vec::new();
groups
.try_reserve_exact(codec_groups.len())
.map_err(|_| BurnDecodeError::SizeOverflow)?;
for group in codec_groups {
let resident = group
.resident_batch()
.ok_or(BurnDecodeError::UnsupportedCodecContract)?;
let bytes = unsafe {
j2k_metal_support::checked_buffer_read_vec::<u8>(
resident.metal_buffer(),
resident.byte_offset(),
resident.byte_len(),
)
}
.map_err(|source| BurnDecodeError::AcceleratorInterop {
backend: "Metal staged readback",
message: source.to_string(),
})?;
let (info, source_indices, decoded_rects, warnings, _surfaces) = group.into_parts();
groups.push(crate::staging::materialize(
info,
source_indices,
decoded_rects,
warnings,
bytes,
device,
)?);
}
let group_errors = codec_group_errors
.into_iter()
.map(|error| {
let (source_indices, source) = error.into_parts();
BurnBatchGroupError::new(source_indices, BurnDecodeError::Metal(source))
})
.collect();
Ok(BurnBatchDecode {
groups,
errors,
group_errors,
})
}
#[cfg(not(target_os = "macos"))]
fn unavailable() -> BurnDecodeError {
BurnDecodeError::AcceleratorInterop {
backend: "Metal",
message: "Metal is unavailable on this platform".to_string(),
}
}