j2k-metal 0.8.0

Metal decoder and encode-stage adapter for j2k
Documentation
// SPDX-License-Identifier: MIT OR Apache-2.0

use super::{
    should_use_resident_htj2k_host_shape_for_auto, Buffer, EncodeBackendPreference,
    J2kBlockCodingMode, J2kEncodeValidation, J2kLosslessEncodeOptions, MetalEncodeInputStaging,
    MetalLosslessEncodeTile, PixelFormat, ReversibleTransform,
};

#[cfg(target_os = "macos")]
const AUTO_HIGH_THROUGHPUT_RESIDENT_HOST_OUTPUT_RGB8_MIN_PIXELS: usize = 1024 * 1024;

#[cfg(target_os = "macos")]
pub(super) fn should_try_resident_lossless_host_encode(options: J2kLosslessEncodeOptions) -> bool {
    options.backend == EncodeBackendPreference::RequireDevice
}

#[cfg(target_os = "macos")]
pub(super) fn should_try_resident_lossless_host_encode_for_tiles(
    tiles: &[MetalLosslessEncodeTile<'_>],
    options: J2kLosslessEncodeOptions,
    staging: MetalEncodeInputStaging,
) -> bool {
    if should_try_resident_lossless_host_encode(options) {
        return true;
    }
    options.backend == EncodeBackendPreference::Auto
        && !tiles.is_empty()
        && tiles.iter().all(|&tile| {
            should_try_auto_resident_lossless_host_encode(tile, options, staging, tiles.len())
        })
}

#[cfg(target_os = "macos")]
pub(super) fn should_try_auto_resident_lossless_host_encode(
    tile: MetalLosslessEncodeTile<'_>,
    options: J2kLosslessEncodeOptions,
    staging: MetalEncodeInputStaging,
    batch_size: usize,
) -> bool {
    options.backend == EncodeBackendPreference::Auto
        && options.block_coding_mode == J2kBlockCodingMode::HighThroughput
        && matches!(staging, MetalEncodeInputStaging::AlreadyPaddedContiguous)
        && should_try_auto_resident_lossless_host_format(
            tile.format,
            options.reversible_transform,
            batch_size,
            tile.output_width,
            tile.output_height,
        )
}

#[cfg(target_os = "macos")]
pub(super) fn should_try_auto_resident_lossless_host_format(
    format: PixelFormat,
    reversible_transform: ReversibleTransform,
    batch_size: usize,
    output_width: u32,
    output_height: u32,
) -> bool {
    let pixels = (output_width as usize).saturating_mul(output_height as usize);
    match format {
        PixelFormat::Gray8 => {
            batch_size > 1
                && should_use_resident_htj2k_host_shape_for_auto(output_width, output_height)
        }
        PixelFormat::Rgb8 => {
            batch_size > 1
                && reversible_transform == ReversibleTransform::Rct53
                && pixels >= AUTO_HIGH_THROUGHPUT_RESIDENT_HOST_OUTPUT_RGB8_MIN_PIXELS
        }
        _ => false,
    }
}

#[cfg(target_os = "macos")]
pub(super) fn host_output_encode_options(
    mut options: J2kLosslessEncodeOptions,
) -> J2kLosslessEncodeOptions {
    options.validation = J2kEncodeValidation::External;
    options
}

#[cfg(target_os = "macos")]
pub(super) fn copy_padded_metal_buffer_from_bytes(
    session: &crate::MetalBackendSession,
    bytes: &[u8],
) -> Result<Buffer, crate::Error> {
    if bytes.is_empty() {
        return Err(crate::Error::MetalKernel {
            message: "J2K Metal hybrid encode input is empty".to_string(),
        });
    }
    j2k_metal_support::checked_shared_buffer_with_slice(session.device(), bytes).map_err(|source| {
        crate::error::metal_kernel_support_error("J2K Metal hybrid encode input", source)
    })
}