j2k-metal 0.8.1

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

use super::*;

#[cfg(target_os = "macos")]
#[test]
#[expect(
    clippy::cast_sign_loss,
    reason = "bounded synthetic pixels are nonnegative"
)]
fn classic_inflight_one_waits_before_submitting_the_next_chunk() {
    if !should_run_metal_runtime() {
        return;
    }

    let first = (0..8 * 8)
        .map(|index| ((index * 7 + 3) & 0xFF) as u8)
        .collect::<Vec<_>>();
    let second = (0..8 * 8)
        .map(|index| ((index * 13 + 5) & 0xFF) as u8)
        .collect::<Vec<_>>();
    let session = crate::MetalBackendSession::system_default().expect("Metal session");
    let first_buffer =
        crate::benchmark_private_buffer_with_bytes(&session, &first).expect("first private input");
    let second_buffer = crate::benchmark_private_buffer_with_bytes(&session, &second)
        .expect("second private input");
    let tiles =
        [&first_buffer, &second_buffer].map(|buffer| super::super::MetalLosslessEncodeTile {
            buffer,
            byte_offset: 0,
            width: 8,
            height: 8,
            pitch_bytes: 8,
            output_width: 8,
            output_height: 8,
            format: PixelFormat::Gray8,
        });
    let options = lossless_options! {
        backend: EncodeBackendPreference::RequireDevice,
        block_coding_mode: J2kBlockCodingMode::Classic,
        validation: J2kEncodeValidation::External,
    };

    compute::reset_resident_codestream_command_buffer_waits_for_test();
    super::super::reset_resident_schedule_counters_for_test();
    let outcome = super::super::encode_lossless_from_padded_metal_buffers_to_metal_batch(
        &tiles,
        &options,
        &session,
        super::super::MetalLosslessEncodeConfig {
            gpu_encode_inflight_tiles: Some(1),
            gpu_encode_memory_budget_bytes: Some(1024 * 1024 * 1024),
        },
    )
    .expect("classic resident batch");

    assert_eq!(outcome.outcomes.len(), 2);
    assert_eq!(
        compute::resident_codestream_command_buffer_waits_for_test(),
        2
    );
    assert_eq!(
        super::super::resident_schedule_counters_for_test(),
        (0, 1, 2)
    );
}

#[cfg(target_os = "macos")]
#[test]
#[expect(
    clippy::cast_sign_loss,
    reason = "bounded synthetic pixels are nonnegative"
)]
fn dropping_submission_does_not_schedule_remaining_resident_chunks() {
    if !should_run_metal_runtime() {
        return;
    }

    let inputs = (0..4)
        .map(|seed| {
            (0..8 * 8)
                .map(|index| ((index * 11 + seed) & 0xFF) as u8)
                .collect::<Vec<_>>()
        })
        .collect::<Vec<_>>();
    let session = crate::MetalBackendSession::system_default().expect("Metal session");
    let buffers = inputs
        .iter()
        .map(|bytes| {
            crate::benchmark_private_buffer_with_bytes(&session, bytes)
                .expect("private benchmark input buffer")
        })
        .collect::<Vec<_>>();
    let tiles = buffers
        .iter()
        .map(|buffer| super::super::MetalLosslessEncodeTile {
            buffer,
            byte_offset: 0,
            width: 8,
            height: 8,
            pitch_bytes: 8,
            output_width: 8,
            output_height: 8,
            format: PixelFormat::Gray8,
        })
        .collect::<Vec<_>>();
    let options = lossless_options! {
        backend: EncodeBackendPreference::RequireDevice,
        block_coding_mode: J2kBlockCodingMode::HighThroughput,
        validation: J2kEncodeValidation::External,
    };

    super::super::reset_resident_schedule_counters_for_test();
    let submitted = crate::submit_lossless_batch_to_metal(
        super::super::MetalLosslessEncodeBatchRequest {
            tiles: &tiles,
            staging: super::super::MetalEncodeInputStaging::AlreadyPaddedContiguous,
            config: super::super::MetalLosslessEncodeConfig {
                gpu_encode_inflight_tiles: Some(1),
                gpu_encode_memory_budget_bytes: Some(1024 * 1024 * 1024),
            },
        },
        &options,
        &session,
    )
    .expect("resident submission");
    assert_eq!(
        super::super::resident_schedule_counters_for_test(),
        (1, 1, 1),
        "submit must schedule exactly one active chunk"
    );

    drop(submitted);

    assert_eq!(
        super::super::resident_schedule_counters_for_test(),
        (0, 1, 1),
        "drop must release scheduler ownership without scheduling a suspended chunk"
    );
}