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}