1use core::fmt;
4
5use j2k_core::{BackendKind, BackendRequest};
6use j2k_transcode::{
7 BatchTranscodeReport, EncodedTranscode, EncodedTranscodeBatch, JpegTileBatchInput,
8 JpegToHtj2kError, JpegToHtj2kOptions, JpegToHtj2kTranscoder, TranscodePipelineMap,
9 TranscodeStageError, TranscodeTimingReport,
10};
11#[cfg(target_os = "macos")]
12use j2k_transcode::{ResidentBufferRef, ResidentCodestreamBuffer, ResidentHandoffError};
13
14use crate::MetalDctToWaveletStageAccelerator;
15
16const CUDA_REQUESTED_THROUGH_METAL_ADAPTER: &str = "CUDA transcode requested through Metal adapter";
17const STRICT_METAL_TRANSCODE_NO_DISPATCH: &str =
18 "strict Metal transcode produced no Metal dispatch";
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum MetalTranscodeFallbackReason {
23 CpuRequested,
25 AutoNoEligibleTransformStage,
27 AutoAllTransformJobsFellBackToCpu,
29 AutoPartialTransformFallback,
31}
32
33impl MetalTranscodeFallbackReason {
34 #[must_use]
36 pub const fn as_str(self) -> &'static str {
37 match self {
38 Self::CpuRequested => "cpu_requested",
39 Self::AutoNoEligibleTransformStage => "auto_no_eligible_transform_stage",
40 Self::AutoAllTransformJobsFellBackToCpu => "auto_all_transform_jobs_fell_back_to_cpu",
41 Self::AutoPartialTransformFallback => "auto_partial_transform_fallback",
42 }
43 }
44}
45
46impl fmt::Display for MetalTranscodeFallbackReason {
47 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48 f.write_str(self.as_str())
49 }
50}
51
52#[derive(Debug, Clone, PartialEq, Eq)]
54pub struct MetalTranscodeRouteReport {
55 pub request: BackendRequest,
57 pub selected_transform_backend: BackendKind,
59 pub output_backend: BackendKind,
61 pub fallback_reason: Option<MetalTranscodeFallbackReason>,
63 pub pipeline_map: TranscodePipelineMap,
65}
66
67pub struct MetalEncodedTranscode {
69 pub encoded: EncodedTranscode,
71 pub route: MetalTranscodeRouteReport,
73}
74
75pub struct MetalEncodedTranscodeBatch {
77 pub batch: EncodedTranscodeBatch,
79 pub route: MetalTranscodeRouteReport,
81}
82
83#[cfg(target_os = "macos")]
85pub fn resident_codestream_buffer_from_metal_encoded_j2k(
86 encoded: &j2k_metal::MetalEncodedJ2k,
87) -> Result<ResidentCodestreamBuffer<'_>, ResidentHandoffError> {
88 let memory = encoded
89 .codestream_memory_range()
90 .ok_or(ResidentHandoffError::OffsetOverflow)?;
91 let allocation_len = encoded
92 .codestream_allocation_len()
93 .ok_or(ResidentHandoffError::RangeExceedsAllocation)?;
94 let buffer = ResidentBufferRef::with_allocation_len(memory, allocation_len)?;
95 ResidentCodestreamBuffer::new(buffer, encoded.byte_len(), encoded.capacity())?
96 .require_backend(BackendKind::Metal)
97}
98
99pub fn jpeg_to_htj2k_with_metal_route(
105 bytes: &[u8],
106 options: &JpegToHtj2kOptions,
107 request: BackendRequest,
108) -> Result<MetalEncodedTranscode, JpegToHtj2kError> {
109 let mut transcoder = JpegToHtj2kTranscoder::default();
110 let encoded = match request {
111 BackendRequest::Cpu => transcoder.transcode(bytes, options)?,
112 BackendRequest::Auto => {
113 let mut accelerator = MetalDctToWaveletStageAccelerator::for_auto();
114 transcoder.transcode_with_accelerator(bytes, options, &mut accelerator)?
115 }
116 BackendRequest::Metal => {
117 let mut accelerator = MetalDctToWaveletStageAccelerator::new_explicit();
118 let encoded =
119 transcoder.transcode_with_accelerator(bytes, options, &mut accelerator)?;
120 ensure_strict_metal_dispatched(&encoded.report.timings)?;
121 encoded
122 }
123 BackendRequest::Cuda => {
124 return Err(JpegToHtj2kError::Unsupported(
125 CUDA_REQUESTED_THROUGH_METAL_ADAPTER,
126 ));
127 }
128 };
129 let route = route_report(request, &encoded.report.timings);
130 Ok(MetalEncodedTranscode { encoded, route })
131}
132
133pub fn jpeg_to_htj2k_batch_with_metal_route(
135 tiles: &[JpegTileBatchInput<'_>],
136 options: &JpegToHtj2kOptions,
137 request: BackendRequest,
138) -> Result<MetalEncodedTranscodeBatch, JpegToHtj2kError> {
139 let mut transcoder = JpegToHtj2kTranscoder::default();
140 let batch = match request {
141 BackendRequest::Cpu => transcoder.transcode_batch(tiles, options)?,
142 BackendRequest::Auto => {
143 let mut accelerator = MetalDctToWaveletStageAccelerator::for_auto();
144 transcoder.transcode_batch_with_accelerator(tiles, options, &mut accelerator)?
145 }
146 BackendRequest::Metal => {
147 let mut accelerator = MetalDctToWaveletStageAccelerator::new_explicit();
148 let batch =
149 transcoder.transcode_batch_with_accelerator(tiles, options, &mut accelerator)?;
150 ensure_strict_metal_batch_dispatched(&batch.report)?;
151 batch
152 }
153 BackendRequest::Cuda => {
154 return Err(JpegToHtj2kError::Unsupported(
155 CUDA_REQUESTED_THROUGH_METAL_ADAPTER,
156 ));
157 }
158 };
159 let route = route_report(request, &batch.report.timings);
160 Ok(MetalEncodedTranscodeBatch { batch, route })
161}
162
163pub(crate) fn route_report(
164 request: BackendRequest,
165 timings: &TranscodeTimingReport,
166) -> MetalTranscodeRouteReport {
167 let selected_transform_backend = selected_transform_backend(timings);
168 MetalTranscodeRouteReport {
169 request,
170 selected_transform_backend,
171 output_backend: BackendKind::Cpu,
172 fallback_reason: fallback_reason(request, selected_transform_backend, timings),
173 pipeline_map: timings.pipeline_map(),
174 }
175}
176
177fn selected_transform_backend(timings: &TranscodeTimingReport) -> BackendKind {
178 if timings.accelerator_work_observed() {
179 BackendKind::Metal
180 } else {
181 BackendKind::Cpu
182 }
183}
184
185fn fallback_reason(
186 request: BackendRequest,
187 selected_transform_backend: BackendKind,
188 timings: &TranscodeTimingReport,
189) -> Option<MetalTranscodeFallbackReason> {
190 match request {
191 BackendRequest::Cpu => Some(MetalTranscodeFallbackReason::CpuRequested),
192 BackendRequest::Auto
193 if selected_transform_backend == BackendKind::Cpu
194 && timings.accelerator_attempts == 0 =>
195 {
196 Some(MetalTranscodeFallbackReason::AutoNoEligibleTransformStage)
197 }
198 BackendRequest::Auto if selected_transform_backend == BackendKind::Cpu => {
199 Some(MetalTranscodeFallbackReason::AutoAllTransformJobsFellBackToCpu)
200 }
201 BackendRequest::Auto if timings.cpu_fallback_jobs > 0 => {
202 Some(MetalTranscodeFallbackReason::AutoPartialTransformFallback)
203 }
204 BackendRequest::Metal | BackendRequest::Cuda | BackendRequest::Auto => None,
205 }
206}
207
208pub(crate) fn ensure_strict_metal_dispatched(
209 timings: &TranscodeTimingReport,
210) -> Result<(), JpegToHtj2kError> {
211 if timings.accelerator_work_observed() {
212 Ok(())
213 } else {
214 Err(JpegToHtj2kError::Accelerator(
215 TranscodeStageError::Unsupported(STRICT_METAL_TRANSCODE_NO_DISPATCH),
216 ))
217 }
218}
219
220pub(crate) fn ensure_strict_metal_batch_dispatched(
221 report: &BatchTranscodeReport,
222) -> Result<(), JpegToHtj2kError> {
223 if report.successful_tiles == 0 || report.timings.accelerator_work_observed() {
224 Ok(())
225 } else {
226 Err(JpegToHtj2kError::Accelerator(
227 TranscodeStageError::Unsupported(STRICT_METAL_TRANSCODE_NO_DISPATCH),
228 ))
229 }
230}