1use super::{
6 BatchDecodeOptions, EncodedImage, Error, MetalBackendSession, MetalBatchDecodeResult,
7 MetalBatchGroup, MetalBatchGroupError, PreparedBatch, PreparedBatchGroup, PreparedImage,
8};
9#[cfg(target_os = "macos")]
10use super::{
11 MetalImageDestination, PixelFormat, PreparedColorPlanCache, PreparedGrayPlanCache,
12 SubmittedMetalPreparedBatch,
13};
14
15pub struct MetalBatchDecoder {
22 pub(super) backend: MetalBackendSession,
23 pub(super) options: BatchDecodeOptions,
24 submission_count: u64,
25 #[cfg(target_os = "macos")]
26 pub(super) prepared_gray_plans: PreparedGrayPlanCache,
27 #[cfg(target_os = "macos")]
28 pub(super) prepared_color_plans: PreparedColorPlanCache,
29}
30
31impl core::fmt::Debug for MetalBatchDecoder {
32 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
33 let mut debug = f.debug_struct("MetalBatchDecoder");
34 debug.field("backend", &self.backend);
35 debug.field("options", &self.options);
36 debug.field("submissions", &self.submission_count);
37 #[cfg(target_os = "macos")]
38 debug.field("prepared_gray_plans", &self.prepared_gray_plans.len());
39 #[cfg(target_os = "macos")]
40 debug.field("prepared_color_plans", &self.prepared_color_plans.len());
41 debug.finish_non_exhaustive()
42 }
43}
44
45impl MetalBatchDecoder {
46 pub fn system_default() -> Result<Self, Error> {
48 Self::system_default_with_options(BatchDecodeOptions::default())
49 }
50
51 pub fn system_default_with_options(options: BatchDecodeOptions) -> Result<Self, Error> {
53 let backend = MetalBackendSession::system_default()?;
54 #[cfg(target_os = "macos")]
55 {
56 Ok(Self::with_backend_session_and_options(backend, options))
57 }
58 #[cfg(not(target_os = "macos"))]
59 {
60 let _ = backend;
61 let _ = options;
62 Err(Error::MetalUnavailable)
63 }
64 }
65
66 #[cfg(target_os = "macos")]
68 pub fn with_backend_session(backend: MetalBackendSession) -> Self {
69 Self::with_backend_session_and_options(backend, BatchDecodeOptions::default())
70 }
71
72 #[cfg(target_os = "macos")]
75 pub fn with_backend_session_and_options(
76 backend: MetalBackendSession,
77 options: BatchDecodeOptions,
78 ) -> Self {
79 Self {
80 backend,
81 options,
82 submission_count: 0,
83 prepared_gray_plans: PreparedGrayPlanCache::new(
84 super::plan_cache::PREPARED_BATCH_PLAN_CACHE_CAP,
85 ),
86 prepared_color_plans: PreparedColorPlanCache::new(
87 super::plan_cache::PREPARED_BATCH_PLAN_CACHE_CAP,
88 ),
89 }
90 }
91
92 #[must_use]
94 pub const fn options(&self) -> BatchDecodeOptions {
95 self.options
96 }
97
98 #[cfg(target_os = "macos")]
100 pub fn backend_session(&self) -> &MetalBackendSession {
101 &self.backend
102 }
103
104 pub fn submissions(&self) -> Result<u64, Error> {
106 Ok(self.submission_count)
107 }
108
109 #[cfg(target_os = "macos")]
110 pub(super) fn record_submission(&mut self) {
111 self.submission_count = self.submission_count.saturating_add(1);
112 }
113
114 pub fn prepare(&self, inputs: Vec<EncodedImage>) -> Result<PreparedBatch, Error> {
116 j2k::prepare_batch(inputs, self.options).map_err(Error::from)
117 }
118
119 pub fn prepare_prepared_images(
122 &self,
123 images: Vec<PreparedImage>,
124 ) -> Result<PreparedBatch, Error> {
125 j2k::prepare_batch_from_images(images, self.options).map_err(Error::from)
126 }
127
128 pub fn decode_batch(
130 &mut self,
131 inputs: Vec<EncodedImage>,
132 ) -> Result<MetalBatchDecodeResult, Error> {
133 let prepared = self.prepare(inputs)?;
134 self.decode_prepared(&prepared)
135 }
136
137 pub fn decode_prepared_images(
140 &mut self,
141 images: Vec<PreparedImage>,
142 ) -> Result<MetalBatchDecodeResult, Error> {
143 let prepared = self.prepare_prepared_images(images)?;
144 self.decode_prepared(&prepared)
145 }
146
147 pub fn decode_prepared(
149 &mut self,
150 prepared: &PreparedBatch,
151 ) -> Result<MetalBatchDecodeResult, Error> {
152 #[cfg(target_os = "macos")]
153 {
154 self.submit_prepared(prepared)?.wait()
155 }
156 #[cfg(not(target_os = "macos"))]
157 {
158 let mut budget = crate::batch_allocation::BatchMetadataBudget::new(
159 "J2K persistent Metal prepared batch output",
160 );
161 let mut groups = budget.try_vec(
162 prepared.groups().len(),
163 "J2K persistent Metal prepared output groups",
164 )?;
165 let mut group_errors = budget.try_vec(
166 prepared.groups().len(),
167 "J2K persistent Metal prepared group execution failures",
168 )?;
169 let mut errors = budget.try_vec(
170 prepared.errors().len(),
171 "J2K persistent Metal prepared indexed errors",
172 )?;
173 errors.extend_from_slice(prepared.errors());
174 for group in prepared.groups() {
175 match self.decode_prepared_group_with_options(group, prepared.options()) {
176 Ok(decoded) => groups.push(decoded),
177 Err(source) if source.session_is_unusable() => return Err(source),
178 Err(source) => group_errors.push(MetalBatchGroupError::new(group, source)),
179 }
180 }
181 Ok(MetalBatchDecodeResult {
182 groups,
183 errors,
184 group_errors,
185 })
186 }
187 }
188
189 #[cfg(target_os = "macos")]
197 pub fn submit_prepared(
198 &mut self,
199 prepared: &PreparedBatch,
200 ) -> Result<SubmittedMetalPreparedBatch, Error> {
201 let mut budget = crate::batch_allocation::BatchMetadataBudget::new(
202 "J2K submitted shared prepared Metal batch",
203 );
204 let mut pending_groups = budget.try_vec(
205 prepared.groups().len(),
206 "J2K submitted shared prepared Metal groups",
207 )?;
208 let mut errors = budget.try_vec(
209 prepared.errors().len(),
210 "J2K submitted shared prepared indexed errors",
211 )?;
212 errors.extend_from_slice(prepared.errors());
213 let mut group_errors = budget.try_vec(
214 prepared.groups().len(),
215 "J2K submitted shared prepared group errors",
216 )?;
217
218 for group in prepared.groups() {
219 match self.submit_prepared_resident_group(group, prepared.options()) {
220 Ok(pending) => pending_groups.push(pending),
221 Err(source) if source.session_is_unusable() => return Err(source),
222 Err(source) => group_errors.push(MetalBatchGroupError::new(group, source)),
223 }
224 }
225 Ok(SubmittedMetalPreparedBatch {
226 pending_groups,
227 errors,
228 group_errors,
229 })
230 }
231
232 #[cfg(target_os = "macos")]
235 pub fn submit_batch(
236 &mut self,
237 inputs: Vec<EncodedImage>,
238 ) -> Result<SubmittedMetalPreparedBatch, Error> {
239 let prepared = self.prepare(inputs)?;
240 self.submit_prepared(&prepared)
241 }
242
243 pub fn decode_prepared_group(
245 &mut self,
246 group: &PreparedBatchGroup,
247 ) -> Result<MetalBatchGroup, Error> {
248 self.decode_prepared_group_with_options(group, group.options())
249 }
250 #[cfg(target_os = "macos")]
255 pub fn validate_destination(
256 &self,
257 destination: &MetalImageDestination,
258 dimensions: (u32, u32),
259 pixel_format: PixelFormat,
260 ) -> Result<(), Error> {
261 destination
262 .validate_device(self.backend_session().device())
263 .and_then(|()| destination.validate_image(dimensions, pixel_format))
264 .map_err(|source| {
265 crate::error::metal_kernel_support_error(
266 "J2K Metal external decode destination validation failed",
267 source,
268 )
269 })
270 }
271}
272
273impl j2k::BatchDecoder for MetalBatchDecoder {
274 type Output = MetalBatchDecodeResult;
275 type Error = Error;
276
277 fn options(&self) -> BatchDecodeOptions {
278 self.options
279 }
280
281 fn decode_prepared(&mut self, prepared: &PreparedBatch) -> Result<Self::Output, Self::Error> {
282 MetalBatchDecoder::decode_prepared(self, prepared)
283 }
284}