Skip to main content

j2k_metal/encode/
types.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3use j2k::EncodedJ2k;
4#[cfg(target_os = "macos")]
5use j2k_core::PixelFormat;
6#[cfg(target_os = "macos")]
7use j2k_metal_support::ResidentMetalImage;
8#[cfg(target_os = "macos")]
9use metal::Buffer;
10use std::time::Duration;
11
12use super::MetalEncodedJ2k;
13
14#[cfg(target_os = "macos")]
15#[derive(Debug, Clone, Copy)]
16/// Metal buffer and layout metadata for one lossless J2K encode tile.
17pub struct MetalLosslessEncodeTile<'a> {
18    pub(super) buffer: &'a Buffer,
19    pub(super) byte_offset: usize,
20    pub(super) width: u32,
21    pub(super) height: u32,
22    pub(super) pitch_bytes: usize,
23    pub(super) output_width: u32,
24    pub(super) output_height: u32,
25    pub(super) format: PixelFormat,
26}
27
28#[cfg(target_os = "macos")]
29impl<'a> MetalLosslessEncodeTile<'a> {
30    /// Describe an immutable Metal-buffer region to the lossless encoder.
31    ///
32    /// Geometry and allocation bounds are validated by the encode operation.
33    ///
34    /// # Safety
35    ///
36    /// All CPU and Metal commands that can write the described source region
37    /// must have completed before this call. The caller must prevent CPU and GPU
38    /// mutation of that region from this call until every encode submission
39    /// derived from the tile has actually completed, including deferred
40    /// submissions that outlive the tile value. Dropping a submitted operation
41    /// without waiting does not end this obligation unless completion is
42    /// established independently. The obligation includes handles cloned before
43    /// this call and outlives copies of the tile. The buffer must belong to the
44    /// same Metal device as every [`crate::MetalBackendSession`] later used to
45    /// encode, submit, or validate this tile; a buffer from another device is
46    /// not compatible even when its layout and storage mode otherwise match.
47    pub unsafe fn from_buffer(
48        buffer: &'a Buffer,
49        byte_offset: usize,
50        dimensions: (u32, u32),
51        pitch_bytes: usize,
52        output_dimensions: (u32, u32),
53        format: PixelFormat,
54    ) -> Self {
55        Self::from_trusted_buffer(
56            buffer,
57            byte_offset,
58            dimensions,
59            pitch_bytes,
60            output_dimensions,
61            format,
62        )
63    }
64
65    pub(crate) fn from_trusted_buffer(
66        buffer: &'a Buffer,
67        byte_offset: usize,
68        dimensions: (u32, u32),
69        pitch_bytes: usize,
70        output_dimensions: (u32, u32),
71        format: PixelFormat,
72    ) -> Self {
73        Self {
74            buffer,
75            byte_offset,
76            width: dimensions.0,
77            height: dimensions.1,
78            pitch_bytes,
79            output_width: output_dimensions.0,
80            output_height: output_dimensions.1,
81            format,
82        }
83    }
84
85    /// Describe a validated resident image to the lossless encoder.
86    ///
87    /// Device identity is checked against the encode session before Metal work
88    /// is submitted. The resident owner is retained by deferred submissions.
89    #[must_use]
90    pub fn from_resident(image: &'a ResidentMetalImage, output_dimensions: (u32, u32)) -> Self {
91        let layout = image.layout();
92        Self {
93            // SAFETY: the resident image remains borrowed by this tile and the
94            // handle is used only for read-only backend binding.
95            buffer: unsafe { image.raw_buffer() },
96            byte_offset: layout.byte_offset(),
97            width: layout.dimensions().0,
98            height: layout.dimensions().1,
99            pitch_bytes: layout.pitch_bytes(),
100            output_width: output_dimensions.0,
101            output_height: output_dimensions.1,
102            format: layout.pixel_format(),
103        }
104    }
105
106    pub(super) fn validate_device(self, device: &metal::DeviceRef) -> Result<(), crate::Error> {
107        let image_registry_id = self.buffer.device().registry_id();
108        let requested_registry_id = device.registry_id();
109        if image_registry_id == requested_registry_id {
110            Ok(())
111        } else {
112            Err(crate::error::metal_kernel_support_error(
113                "J2K input belongs to a different Metal device",
114                j2k_metal_support::MetalSupportError::MetalImageDeviceMismatch {
115                    image_registry_id,
116                    requested_registry_id,
117                },
118            ))
119        }
120    }
121
122    /// Byte offset of the first source pixel.
123    pub fn byte_offset(self) -> usize {
124        self.byte_offset
125    }
126
127    /// Dimensions of the valid source region.
128    pub fn dimensions(self) -> (u32, u32) {
129        (self.width, self.height)
130    }
131
132    /// Number of bytes between consecutive source rows.
133    pub fn pitch_bytes(self) -> usize {
134        self.pitch_bytes
135    }
136
137    /// Encoded output dimensions.
138    pub fn output_dimensions(self) -> (u32, u32) {
139        (self.output_width, self.output_height)
140    }
141
142    /// Pixel format of the source region.
143    pub fn pixel_format(self) -> PixelFormat {
144        self.format
145    }
146}
147
148#[cfg(not(target_os = "macos"))]
149#[derive(Debug, Clone, Copy)]
150/// Placeholder lossless encode tile type for non-macOS builds.
151pub struct MetalLosslessEncodeTile<'a> {
152    _private: core::marker::PhantomData<&'a ()>,
153}
154
155#[derive(Debug, Clone, PartialEq, Eq)]
156/// Residency decisions used by a lossless Metal encode.
157pub struct MetalLosslessEncodeResidency {
158    /// Whether coefficient preparation ran on Metal.
159    pub coefficient_prep_used: bool,
160    /// Whether packetization ran on Metal.
161    pub packetization_used: bool,
162    /// Whether codestream assembly stayed resident on Metal.
163    pub codestream_assembly_used: bool,
164}
165
166#[derive(Debug, Clone, PartialEq, Eq)]
167/// Lossless Metal encode output with host codestream bytes and timings.
168///
169/// API note: this diagnostic report is constructed by this crate. It is not
170/// `#[non_exhaustive]`, but adapter releases may add diagnostic fields as the
171/// resident encode path gains more profiling detail.
172#[doc(hidden)]
173pub struct MetalLosslessEncodeOutcome {
174    /// Encoded J2K codestream.
175    pub encoded: EncodedJ2k,
176    /// Whether the input buffer had to be copied or padded.
177    pub input_copy_used: bool,
178    /// Residency decisions for the encode stages.
179    pub resident: MetalLosslessEncodeResidency,
180    /// Time spent copying or padding the input.
181    pub input_copy_duration: Duration,
182    /// End-to-end encode duration for this tile.
183    pub encode_duration: Duration,
184    /// GPU-only duration when timestamp data is available.
185    pub gpu_duration: Option<Duration>,
186    /// Time spent validating the encoded output.
187    pub validation_duration: Duration,
188    /// Time spent materializing buffer-backed codestream bytes into host bytes.
189    pub host_readback_duration: Duration,
190}
191
192/// Metal lossless encode report for buffer-backed codestream output.
193#[doc(hidden)]
194pub struct MetalLosslessBufferEncodeOutcome {
195    /// Encoded codestream stored in a Metal buffer.
196    pub encoded: MetalEncodedJ2k,
197    /// Whether the input buffer had to be copied or padded.
198    pub input_copy_used: bool,
199    /// Residency decisions for the encode stages.
200    pub resident: MetalLosslessEncodeResidency,
201    /// Time spent copying or padding the input.
202    pub input_copy_duration: Duration,
203    /// End-to-end encode duration for this tile.
204    pub encode_duration: Duration,
205    /// GPU-only duration when timestamp data is available.
206    pub gpu_duration: Option<Duration>,
207    /// Time spent validating the encoded output.
208    pub validation_duration: Duration,
209}
210
211/// Tuning knobs for resident Metal lossless J2K/HTJ2K tile batch encode.
212#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
213pub struct MetalLosslessEncodeConfig {
214    /// Requested maximum number of tiles submitted concurrently.
215    ///
216    /// `None` uses the crate default and still clamps by the memory budget.
217    pub gpu_encode_inflight_tiles: Option<usize>,
218    /// Resident encode memory budget in bytes.
219    ///
220    /// `None` uses `min(10 GiB, hw_memsize * 0.40)` when host memory can be
221    /// discovered.
222    pub gpu_encode_memory_budget_bytes: Option<usize>,
223}
224
225/// Batched lossless encode request over Metal-resident tiles.
226///
227/// Collapses the former per-permutation entry points: pick the input
228/// staging mode and batch tuning here, then submit through
229/// [`crate::submit_lossless_batch`], [`crate::submit_lossless_batch_to_metal`],
230/// or [`crate::encode_lossless_batch_with_report`].
231#[derive(Clone, Copy)]
232pub struct MetalLosslessEncodeBatchRequest<'a, 'b> {
233    /// Metal-resident tiles to encode.
234    pub tiles: &'a [MetalLosslessEncodeTile<'b>],
235    /// How tile samples reach the encoder's padded staging layout.
236    pub staging: MetalEncodeInputStaging,
237    /// Batch tuning knobs (inflight tiles, memory budget).
238    pub config: MetalLosslessEncodeConfig,
239}
240
241/// How tile samples reach the encoder's padded staging layout.
242#[derive(Debug, Clone, Copy, PartialEq, Eq)]
243pub enum MetalEncodeInputStaging {
244    /// Copy the tile into freshly padded staging storage.
245    CopyAndPad,
246    /// The tile is already padded and contiguous; encode it in place.
247    AlreadyPaddedContiguous,
248}