j2k_transcode_metal/
accelerator.rs1use j2k_transcode::{
4 DctGridToReversibleDwt53Job, DctToWaveletStageCounterEvent as CounterEvent,
5 DctToWaveletStageCounters, Dwt97BatchStageTimings, ReversibleDwt53FirstLevel,
6 TranscodeStageDispatchMode, TranscodeStageError,
7};
8
9#[cfg(target_os = "macos")]
10use crate::metal;
11#[cfg(target_os = "macos")]
12use crate::MetalTranscodeError;
13#[cfg(target_os = "macos")]
14use crate::MetalTranscodeSession;
15
16mod dispatch;
17
18const DEFAULT_AUTO_MIN_SAMPLES: usize = 224 * 224;
19const DEFAULT_AUTO_DWT97_MIN_SAMPLES: usize = usize::MAX;
23const DEFAULT_AUTO_REVERSIBLE_MIN_SAMPLES: usize = usize::MAX;
24const DEFAULT_AUTO_REVERSIBLE_BATCH_MIN_JOBS: usize = 32;
25const DEFAULT_AUTO_REVERSIBLE_BATCH_MIN_SAMPLES: usize = 224 * 224 * 32;
26const DEFAULT_AUTO_DWT97_BATCH_MIN_JOBS: usize = 32;
27const DEFAULT_AUTO_DWT97_BATCH_MIN_SAMPLES: usize = 224 * 224 * 32;
28const MAX_AUTO_DWT97_STAGED_BATCH_AXIS: usize = 1024;
31
32#[derive(Debug, Clone)]
34pub struct MetalDctToWaveletStageAccelerator {
35 mode: TranscodeStageDispatchMode,
36 min_auto_samples: usize,
37 min_auto_dwt97_samples: usize,
38 min_auto_reversible_samples: usize,
39 min_auto_reversible_batch_jobs: usize,
40 min_auto_reversible_batch_samples: usize,
41 counters: DctToWaveletStageCounters,
42 last_dwt97_batch_stage_timings: Option<Dwt97BatchStageTimings>,
43 min_auto_dwt97_batch_jobs: usize,
44 min_auto_dwt97_batch_samples: usize,
45 #[cfg(target_os = "macos")]
46 session: Option<MetalTranscodeSession>,
47}
48
49impl MetalDctToWaveletStageAccelerator {
50 #[must_use]
52 pub const fn new_explicit() -> Self {
53 Self {
54 mode: TranscodeStageDispatchMode::Explicit,
55 min_auto_samples: 0,
56 min_auto_dwt97_samples: 0,
57 min_auto_reversible_samples: 0,
58 min_auto_reversible_batch_jobs: 0,
59 min_auto_reversible_batch_samples: 0,
60 counters: DctToWaveletStageCounters::new(),
61 last_dwt97_batch_stage_timings: None,
62 min_auto_dwt97_batch_jobs: 0,
63 min_auto_dwt97_batch_samples: 0,
64 #[cfg(target_os = "macos")]
65 session: None,
66 }
67 }
68
69 #[must_use]
71 pub const fn for_auto() -> Self {
72 Self {
73 mode: TranscodeStageDispatchMode::Auto,
74 min_auto_samples: DEFAULT_AUTO_MIN_SAMPLES,
75 min_auto_dwt97_samples: DEFAULT_AUTO_DWT97_MIN_SAMPLES,
76 min_auto_reversible_samples: DEFAULT_AUTO_REVERSIBLE_MIN_SAMPLES,
77 min_auto_reversible_batch_jobs: DEFAULT_AUTO_REVERSIBLE_BATCH_MIN_JOBS,
78 min_auto_reversible_batch_samples: DEFAULT_AUTO_REVERSIBLE_BATCH_MIN_SAMPLES,
79 counters: DctToWaveletStageCounters::new(),
80 last_dwt97_batch_stage_timings: None,
81 min_auto_dwt97_batch_jobs: DEFAULT_AUTO_DWT97_BATCH_MIN_JOBS,
82 min_auto_dwt97_batch_samples: DEFAULT_AUTO_DWT97_BATCH_MIN_SAMPLES,
83 #[cfg(target_os = "macos")]
84 session: None,
85 }
86 }
87
88 #[cfg(target_os = "macos")]
90 #[must_use]
91 pub fn new_explicit_with_session(session: MetalTranscodeSession) -> Self {
92 Self::new_explicit().with_session(session)
93 }
94
95 #[cfg(target_os = "macos")]
97 #[must_use]
98 pub fn for_auto_with_session(session: MetalTranscodeSession) -> Self {
99 Self::for_auto().with_session(session)
100 }
101
102 #[cfg(target_os = "macos")]
104 #[must_use]
105 pub fn new_explicit_with_device(
106 device: objc2::rc::Retained<objc2::runtime::ProtocolObject<dyn objc2_metal::MTLDevice>>,
107 ) -> Self {
108 Self::new_explicit_with_session(MetalTranscodeSession::new(device))
109 }
110
111 #[cfg(target_os = "macos")]
113 #[must_use]
114 pub fn for_auto_with_device(
115 device: objc2::rc::Retained<objc2::runtime::ProtocolObject<dyn objc2_metal::MTLDevice>>,
116 ) -> Self {
117 Self::for_auto_with_session(MetalTranscodeSession::new(device))
118 }
119
120 #[cfg(target_os = "macos")]
122 #[must_use]
123 pub fn with_session(mut self, session: MetalTranscodeSession) -> Self {
124 self.session = Some(session);
125 self
126 }
127
128 #[cfg(target_os = "macos")]
130 #[must_use]
131 pub fn with_device(
132 self,
133 device: objc2::rc::Retained<objc2::runtime::ProtocolObject<dyn objc2_metal::MTLDevice>>,
134 ) -> Self {
135 self.with_session(MetalTranscodeSession::new(device))
136 }
137
138 #[cfg(target_os = "macos")]
139 fn metal_session(&mut self) -> &mut MetalTranscodeSession {
140 self.session
141 .get_or_insert_with(MetalTranscodeSession::default)
142 }
143
144 #[must_use]
146 pub const fn with_auto_min_samples(mut self, min_samples: usize) -> Self {
147 self.min_auto_samples = min_samples;
148 self.min_auto_dwt97_samples = min_samples;
149 self
150 }
151
152 #[must_use]
154 pub const fn with_auto_dwt97_min_samples(mut self, min_samples: usize) -> Self {
155 self.min_auto_dwt97_samples = min_samples;
156 self
157 }
158
159 #[must_use]
161 pub const fn with_auto_dwt97_batch_thresholds(
162 mut self,
163 min_jobs: usize,
164 min_samples: usize,
165 ) -> Self {
166 self.min_auto_dwt97_batch_jobs = min_jobs;
167 self.min_auto_dwt97_batch_samples = min_samples;
168 self
169 }
170
171 #[must_use]
173 pub const fn with_auto_reversible_min_samples(mut self, min_samples: usize) -> Self {
174 self.min_auto_reversible_samples = min_samples;
175 self
176 }
177
178 #[must_use]
180 pub const fn with_auto_reversible_batch_thresholds(
181 mut self,
182 min_jobs: usize,
183 min_samples: usize,
184 ) -> Self {
185 self.min_auto_reversible_batch_jobs = min_jobs;
186 self.min_auto_reversible_batch_samples = min_samples;
187 self
188 }
189
190 #[must_use]
192 pub const fn reversible_dwt53_attempts(&self) -> usize {
193 self.counters.reversible_dwt53_attempts()
194 }
195
196 #[must_use]
198 pub const fn reversible_dwt53_dispatches(&self) -> usize {
199 self.counters.reversible_dwt53_dispatches()
200 }
201
202 #[must_use]
204 pub const fn reversible_dwt53_batch_attempts(&self) -> usize {
205 self.counters.reversible_dwt53_batch_attempts()
206 }
207
208 #[must_use]
210 pub const fn reversible_dwt53_batch_dispatches(&self) -> usize {
211 self.counters.reversible_dwt53_batch_dispatches()
212 }
213
214 #[must_use]
216 pub const fn dwt53_attempts(&self) -> usize {
217 self.counters.dwt53_attempts()
218 }
219
220 #[must_use]
222 pub const fn dwt53_dispatches(&self) -> usize {
223 self.counters.dwt53_dispatches()
224 }
225
226 #[must_use]
228 pub const fn dwt97_attempts(&self) -> usize {
229 self.counters.dwt97_attempts()
230 }
231
232 #[must_use]
234 pub const fn dwt97_dispatches(&self) -> usize {
235 self.counters.dwt97_dispatches()
236 }
237
238 #[must_use]
240 pub const fn dwt97_batch_attempts(&self) -> usize {
241 self.counters.dwt97_batch_attempts()
242 }
243
244 #[must_use]
246 pub const fn dwt97_batch_dispatches(&self) -> usize {
247 self.counters.dwt97_batch_dispatches()
248 }
249
250 #[must_use]
252 pub const fn htj2k97_codeblock_batch_attempts(&self) -> usize {
253 self.counters.htj2k97_codeblock_batch_attempts()
254 }
255
256 #[must_use]
258 pub const fn htj2k97_codeblock_batch_dispatches(&self) -> usize {
259 self.counters.htj2k97_codeblock_batch_dispatches()
260 }
261
262 #[must_use]
264 pub const fn last_dwt97_batch_stage_timings(&self) -> Option<Dwt97BatchStageTimings> {
265 self.last_dwt97_batch_stage_timings
266 }
267
268 #[cfg(target_os = "macos")]
269 fn recover<T>(&self, error: MetalTranscodeError) -> Result<Option<T>, TranscodeStageError> {
270 self.mode
271 .recover(error, MetalTranscodeError::is_recoverable)
272 }
273
274 pub fn dct_grid_to_reversible_dwt53_batch(
276 &mut self,
277 jobs: &[DctGridToReversibleDwt53Job<'_>],
278 ) -> Result<Option<Vec<ReversibleDwt53FirstLevel>>, TranscodeStageError> {
279 self.dispatch_reversible_dwt53_batch(jobs)
280 }
281
282 fn dispatch_reversible_dwt53_batch(
283 &mut self,
284 jobs: &[DctGridToReversibleDwt53Job<'_>],
285 ) -> Result<Option<Vec<ReversibleDwt53FirstLevel>>, TranscodeStageError> {
286 self.counters
287 .record(CounterEvent::ReversibleDwt53BatchAttempt, 1);
288 if jobs.is_empty() {
289 return Ok(Some(Vec::new()));
290 }
291 let total_samples = jobs.iter().fold(0usize, |total, job| {
292 total.saturating_add(job.width.saturating_mul(job.height))
293 });
294 if self.mode.is_auto()
295 && (jobs.len() < self.min_auto_reversible_batch_jobs
296 || total_samples < self.min_auto_reversible_batch_samples)
297 {
298 return Ok(None);
299 }
300
301 #[cfg(not(target_os = "macos"))]
302 {
303 self.mode.unavailable()
304 }
305 #[cfg(target_os = "macos")]
306 {
307 match metal::dispatch_dct_grid_to_reversible_dwt53_batch(self.metal_session(), jobs) {
308 Ok(output) => {
309 self.counters
310 .record(CounterEvent::ReversibleDwt53BatchDispatch, 1);
311 Ok(Some(output))
312 }
313 Err(error) => self.recover(error),
314 }
315 }
316 }
317}
318
319impl Default for MetalDctToWaveletStageAccelerator {
320 fn default() -> Self {
321 Self::for_auto()
322 }
323}