1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
// SPDX-License-Identifier: MIT OR Apache-2.0
#[cfg(target_os = "macos")]
use crate::error::metal_kernel_support_error;
#[cfg(target_os = "macos")]
use j2k::EncodedJ2k;
#[cfg(target_os = "macos")]
use j2k_core::{BackendKind, DeviceMemoryRange};
#[cfg(target_os = "macos")]
use metal::{foreign_types::ForeignType, Buffer};
#[cfg(target_os = "macos")]
use std::ops::Range;
#[cfg(target_os = "macos")]
use std::time::Duration;
#[cfg(target_os = "macos")]
use std::time::Instant;
#[cfg(target_os = "macos")]
/// JPEG 2000 codestream bytes owned by a Metal buffer.
///
/// The buffer is CPU-readable for the current padded resident encode API.
/// `codestream_bytes()` returns an owned snapshot. Access to the backing Metal
/// handle is unsafe because Metal synchronization cannot be represented by a
/// Rust borrow.
pub struct MetalEncodedJ2k {
pub(crate) codestream_buffer: Buffer,
pub(crate) byte_offset: usize,
pub(crate) byte_len: usize,
pub(crate) capacity: usize,
pub(crate) width: u32,
pub(crate) height: u32,
pub(crate) components: u8,
pub(crate) bit_depth: u8,
pub(crate) signed: bool,
}
#[cfg(target_os = "macos")]
impl MetalEncodedJ2k {
fn try_from_parts(
codestream_buffer: Buffer,
codestream_range: Range<usize>,
capacity: usize,
dimensions: (u32, u32),
components: u8,
bit_depth: u8,
signed: bool,
) -> Result<Self, crate::Error> {
let byte_len = codestream_range
.end
.checked_sub(codestream_range.start)
.ok_or_else(|| crate::Error::MetalKernel {
message: "J2K Metal codestream range ends before it starts".to_string(),
})?;
if byte_len > capacity {
return Err(crate::Error::MetalKernel {
message: format!(
"J2K Metal codestream length {byte_len} exceeds capacity {capacity}"
),
});
}
let capacity_end = codestream_range
.start
.checked_add(capacity)
.ok_or_else(|| crate::Error::MetalKernel {
message: "J2K Metal codestream capacity range overflows usize".to_string(),
})?;
let allocation_len =
usize::try_from(codestream_buffer.length()).map_err(|_| crate::Error::MetalKernel {
message: "J2K Metal codestream allocation length exceeds usize".to_string(),
})?;
if capacity_end > allocation_len {
return Err(crate::Error::MetalKernel {
message: format!(
"J2K Metal codestream capacity range {}..{capacity_end} exceeds allocation length {allocation_len}",
codestream_range.start
),
});
}
Ok(Self {
codestream_buffer,
byte_offset: codestream_range.start,
byte_len,
capacity,
width: dimensions.0,
height: dimensions.1,
components,
bit_depth,
signed,
})
}
/// Construct an encoded codestream from a caller-owned Metal allocation.
///
/// `codestream_range` is a half-open byte range
/// `start..end` indexing `codestream_buffer`; it identifies exactly the valid
/// codestream bytes. `capacity` also begins at `start`. Construction checks
/// `start <= end`, `end - start <= capacity`, that `start + capacity` does
/// not overflow, and that the complete capacity range is within the Metal
/// allocation.
///
/// # Safety
///
/// All CPU and Metal commands that can write the codestream capacity range
/// must have completed before this call. No CPU or GPU access may mutate
/// that range until the returned object is dropped. These obligations also
/// apply to every handle cloned from `codestream_buffer` before this call.
pub unsafe fn from_raw_parts(
codestream_buffer: Buffer,
codestream_range: Range<usize>,
capacity: usize,
dimensions: (u32, u32),
components: u8,
bit_depth: u8,
signed: bool,
) -> Result<Self, crate::Error> {
Self::try_from_parts(
codestream_buffer,
codestream_range,
capacity,
dimensions,
components,
bit_depth,
signed,
)
}
pub(crate) fn from_completed_buffer(
codestream_buffer: Buffer,
codestream_range: Range<usize>,
capacity: usize,
dimensions: (u32, u32),
components: u8,
bit_depth: u8,
signed: bool,
) -> Result<Self, crate::Error> {
Self::try_from_parts(
codestream_buffer,
codestream_range,
capacity,
dimensions,
components,
bit_depth,
signed,
)
}
/// Consume this output and return its backing Metal allocation.
///
/// Read metadata such as [`Self::byte_offset`] and [`Self::capacity`]
/// before calling this method when it is needed for the handoff.
///
/// # Safety
///
/// Other encoded outputs, including sibling tiles in a batch, may share
/// this allocation even though this value is consumed. The caller must
/// ensure that no CPU or GPU access through the returned handle (or a clone)
/// mutates any range while a sharing `MetalEncodedJ2k` remains alive. All
/// prior writers must complete before any sharing output is read back.
pub unsafe fn into_codestream_buffer(self) -> Buffer {
self.codestream_buffer
}
pub(crate) fn codestream_buffer_trusted(&self) -> &Buffer {
&self.codestream_buffer
}
/// Byte offset of the first valid codestream byte.
pub fn byte_offset(&self) -> usize {
self.byte_offset
}
/// Number of valid codestream bytes.
pub fn byte_len(&self) -> usize {
self.byte_len
}
/// Codestream capacity in bytes, beginning at [`Self::byte_offset`].
pub fn capacity(&self) -> usize {
self.capacity
}
/// Encoded image dimensions in pixels.
pub fn dimensions(&self) -> (u32, u32) {
(self.width, self.height)
}
/// Encoded image width in pixels.
pub fn width(&self) -> u32 {
self.width
}
/// Encoded image height in pixels.
pub fn height(&self) -> u32 {
self.height
}
/// Number of encoded components.
pub fn components(&self) -> u8 {
self.components
}
/// Component bit depth.
pub fn bit_depth(&self) -> u8 {
self.bit_depth
}
/// Whether component samples are signed.
pub fn is_signed(&self) -> bool {
self.signed
}
/// Backend-visible memory range for the valid codestream capacity.
pub fn codestream_memory_range(&self) -> Option<DeviceMemoryRange> {
Some(DeviceMemoryRange::new(
BackendKind::Metal,
u64::try_from(self.codestream_buffer_trusted().as_ptr() as usize).ok()?,
self.byte_offset,
self.capacity,
))
}
/// Backing Metal allocation length in bytes.
pub fn codestream_allocation_len(&self) -> Option<usize> {
usize::try_from(self.codestream_buffer_trusted().length()).ok()
}
/// Materialize the finished codestream bytes from the backing Metal buffer.
pub fn codestream_bytes(&self) -> Result<Vec<u8>, crate::Error> {
// SAFETY: Resident encode construction waits for the producing command
// buffer before returning this value. Any external handle can only be
// obtained through an unsafe API whose contract excludes overlapping
// mutation.
match unsafe {
j2k_metal_support::checked_buffer_read_vec::<u8>(
self.codestream_buffer_trusted(),
self.byte_offset,
self.byte_len,
)
} {
Ok(bytes) => Ok(bytes),
Err(error @ j2k_metal_support::MetalSupportError::BufferContentsUnavailable) => {
Err(metal_kernel_support_error(
"J2K Metal codestream buffer is not CPU-readable",
error,
))
}
Err(error) => Err(metal_kernel_support_error(
format!("J2K Metal codestream byte range invalid: {error}"),
error,
)),
}
}
/// Materialize the buffer-backed codestream into the compatibility `Vec` API shape.
pub fn to_encoded_j2k(&self) -> Result<EncodedJ2k, crate::Error> {
let (encoded, _host_readback_duration) = self.to_encoded_j2k_with_readback_duration()?;
Ok(encoded)
}
pub(super) fn to_encoded_j2k_with_readback_duration(
&self,
) -> Result<(EncodedJ2k, Duration), crate::Error> {
let readback_started = Instant::now();
let codestream = self.codestream_bytes()?;
let host_readback_duration = readback_started.elapsed();
Ok((
EncodedJ2k {
codestream,
backend: BackendKind::Metal,
dispatch_report: j2k::J2kEncodeDispatchReport::default(),
width: self.width,
height: self.height,
components: u16::from(self.components),
bit_depth: self.bit_depth,
signed: self.signed,
},
host_readback_duration,
))
}
}
#[cfg(not(target_os = "macos"))]
/// Placeholder Metal codestream type for non-macOS builds.
pub struct MetalEncodedJ2k {
_private: (),
}