Skip to main content

j2k_jpeg_cuda/
decoder.rs

1// SPDX-License-Identifier: Apache-2.0
2
3use j2k_core::{
4    submit_ready_device, BackendRequest, DecodeOutcome, Downscale, ImageCodec, ImageDecode,
5    ImageDecodeDevice, ImageDecodeSubmit, PixelFormat, ReadySubmission, Rect,
6};
7#[cfg(feature = "cuda-runtime")]
8use j2k_jpeg::adapter::decoder_bytes;
9use j2k_jpeg::{
10    Decoder as CpuDecoder, JpegView, ScratchPool as CpuScratchPool, Warning as CpuWarning,
11};
12
13#[cfg(feature = "cuda-runtime")]
14use crate::owned_decode::decode_owned_cuda_rgb8;
15use crate::owned_decode::unsupported_owned_cuda_output_format;
16use crate::runtime::{validate_surface_request, wrap_surface};
17use crate::{CudaSession, Error, Surface};
18
19/// JPEG decoder that can return host or CUDA-resident surfaces.
20pub struct Decoder<'a> {
21    inner: CpuDecoder<'a>,
22}
23
24impl<'a> Decoder<'a> {
25    /// Parse a JPEG byte slice into a CUDA-capable decoder wrapper.
26    pub fn new(input: &'a [u8]) -> Result<Self, Error> {
27        Ok(Self {
28            inner: CpuDecoder::new(input)?,
29        })
30    }
31
32    fn decode_to_surface_impl(
33        &mut self,
34        session: &mut CudaSession,
35        fmt: PixelFormat,
36        backend: BackendRequest,
37    ) -> Result<Surface, Error> {
38        validate_surface_request(backend)?;
39        if j2k_profile::gpu_route_profile_enabled() {
40            let request_s = format!("{backend:?}");
41            let fmt_s = format!("{fmt:?}");
42            let width_s = self.inner.info().dimensions.0.to_string();
43            let height_s = self.inner.info().dimensions.1.to_string();
44            j2k_profile::emit_gpu_route_profile(
45                "jpeg",
46                "cuda",
47                &[
48                    ("op", "full"),
49                    ("request", request_s.as_str()),
50                    ("fmt", fmt_s.as_str()),
51                    ("width", width_s.as_str()),
52                    ("height", height_s.as_str()),
53                    ("decision", "begin"),
54                ],
55            );
56        }
57        if backend == BackendRequest::Cuda {
58            if fmt == PixelFormat::Rgb8 {
59                return self.decode_cuda_rgb8(session);
60            }
61            return Err(unsupported_owned_cuda_output_format());
62        }
63        let (bytes, _outcome) = self.inner.decode(fmt)?;
64        if j2k_profile::gpu_route_profile_enabled() {
65            let request_s = format!("{backend:?}");
66            let fmt_s = format!("{fmt:?}");
67            j2k_profile::emit_gpu_route_profile(
68                "jpeg",
69                "cuda",
70                &[
71                    ("op", "full"),
72                    ("request", request_s.as_str()),
73                    ("fmt", fmt_s.as_str()),
74                    ("decision", "cpu_decode_then_wrap"),
75                ],
76            );
77        }
78        wrap_surface(bytes, self.inner.info().dimensions, fmt, backend, session)
79    }
80
81    #[cfg(feature = "cuda-runtime")]
82    fn decode_cuda_rgb8(&mut self, session: &mut CudaSession) -> Result<Surface, Error> {
83        let dimensions = self.inner.info().dimensions;
84        let surface = decode_owned_cuda_rgb8(decoder_bytes(&self.inner), dimensions, session)?;
85        if j2k_profile::gpu_route_profile_enabled() {
86            let width_s = dimensions.0.to_string();
87            let height_s = dimensions.1.to_string();
88            j2k_profile::emit_gpu_route_profile(
89                "jpeg",
90                "cuda",
91                &[
92                    ("op", "full"),
93                    ("request", "Cuda"),
94                    ("fmt", "Rgb8"),
95                    ("width", width_s.as_str()),
96                    ("height", height_s.as_str()),
97                    ("decision", "owned_cuda"),
98                ],
99            );
100        }
101        Ok(surface)
102    }
103
104    #[cfg(not(feature = "cuda-runtime"))]
105    #[allow(clippy::unnecessary_wraps, clippy::unused_self)]
106    fn decode_cuda_rgb8(&mut self, _session: &mut CudaSession) -> Result<Surface, Error> {
107        if j2k_profile::gpu_route_profile_enabled() {
108            j2k_profile::emit_gpu_route_profile(
109                "jpeg",
110                "cuda",
111                &[
112                    ("op", "full"),
113                    ("request", "Cuda"),
114                    ("fmt", "Rgb8"),
115                    ("decision", "owned_cuda_unavailable"),
116                    ("reason", "cuda_runtime_feature_disabled"),
117                ],
118            );
119        }
120        Err(Error::CudaUnavailable)
121    }
122
123    fn decode_region_to_surface_impl(
124        &mut self,
125        session: &mut CudaSession,
126        fmt: PixelFormat,
127        roi: Rect,
128        backend: BackendRequest,
129    ) -> Result<Surface, Error> {
130        validate_surface_request(backend)?;
131        if backend == BackendRequest::Cuda {
132            return Err(Error::UnsupportedCudaRequest {
133                reason: "J2K CUDA JPEG owned decode does not support region output",
134            });
135        }
136        let (bytes, outcome) = self.inner.decode_region(fmt, roi.into())?;
137        wrap_surface(
138            bytes,
139            (outcome.decoded.w, outcome.decoded.h),
140            fmt,
141            backend,
142            session,
143        )
144    }
145
146    fn decode_scaled_to_surface_impl(
147        &mut self,
148        session: &mut CudaSession,
149        fmt: PixelFormat,
150        scale: Downscale,
151        backend: BackendRequest,
152    ) -> Result<Surface, Error> {
153        validate_surface_request(backend)?;
154        if backend == BackendRequest::Cuda {
155            return Err(Error::UnsupportedCudaRequest {
156                reason: "J2K CUDA JPEG owned decode does not support scaled output",
157            });
158        }
159        let (bytes, outcome) = self.inner.decode_scaled(fmt, scale)?;
160        wrap_surface(
161            bytes,
162            (outcome.decoded.w, outcome.decoded.h),
163            fmt,
164            backend,
165            session,
166        )
167    }
168
169    fn decode_region_scaled_to_surface_impl(
170        &mut self,
171        session: &mut CudaSession,
172        fmt: PixelFormat,
173        roi: Rect,
174        scale: Downscale,
175        backend: BackendRequest,
176    ) -> Result<Surface, Error> {
177        validate_surface_request(backend)?;
178        if backend == BackendRequest::Cuda {
179            return Err(Error::UnsupportedCudaRequest {
180                reason: "J2K CUDA JPEG owned decode does not support scaled region output",
181            });
182        }
183        let (bytes, outcome) = self.inner.decode_region_scaled(fmt, roi.into(), scale)?;
184        wrap_surface(
185            bytes,
186            (outcome.decoded.w, outcome.decoded.h),
187            fmt,
188            backend,
189            session,
190        )
191    }
192}
193
194impl ImageCodec for Decoder<'_> {
195    type Error = Error;
196    type Warning = CpuWarning;
197    type Pool = CpuScratchPool;
198}
199
200impl<'a> ImageDecode<'a> for Decoder<'a> {
201    type View = JpegView<'a>;
202
203    fn inspect(input: &'a [u8]) -> Result<j2k_core::Info, Self::Error> {
204        Ok(CpuDecoder::inspect(input)?.to_core_info())
205    }
206
207    fn parse(input: &'a [u8]) -> Result<Self::View, Self::Error> {
208        Ok(JpegView::parse(input)?)
209    }
210
211    fn from_view(view: Self::View) -> Result<Self, Self::Error> {
212        Ok(Self {
213            inner: CpuDecoder::from_view(view)?,
214        })
215    }
216
217    fn decode_into(
218        &mut self,
219        out: &mut [u8],
220        stride: usize,
221        fmt: PixelFormat,
222    ) -> Result<DecodeOutcome<Self::Warning>, Self::Error> {
223        Ok(self.inner.decode_into(out, stride, fmt)?.into())
224    }
225
226    fn decode_into_with_scratch(
227        &mut self,
228        pool: &mut Self::Pool,
229        out: &mut [u8],
230        stride: usize,
231        fmt: PixelFormat,
232    ) -> Result<DecodeOutcome<Self::Warning>, Self::Error> {
233        Ok(self
234            .inner
235            .decode_into_with_scratch(pool, out, stride, fmt)?
236            .into())
237    }
238
239    fn decode_region_into(
240        &mut self,
241        pool: &mut Self::Pool,
242        out: &mut [u8],
243        stride: usize,
244        fmt: PixelFormat,
245        roi: Rect,
246    ) -> Result<DecodeOutcome<Self::Warning>, Self::Error> {
247        Ok(self
248            .inner
249            .decode_region_into_with_scratch(pool, out, stride, fmt, roi.into())?
250            .into())
251    }
252
253    fn decode_scaled_into(
254        &mut self,
255        pool: &mut Self::Pool,
256        out: &mut [u8],
257        stride: usize,
258        fmt: PixelFormat,
259        scale: Downscale,
260    ) -> Result<DecodeOutcome<Self::Warning>, Self::Error> {
261        Ok(self
262            .inner
263            .decode_scaled_into_with_scratch(pool, out, stride, fmt, scale)?
264            .into())
265    }
266
267    fn decode_region_scaled_into(
268        &mut self,
269        pool: &mut Self::Pool,
270        out: &mut [u8],
271        stride: usize,
272        fmt: PixelFormat,
273        roi: Rect,
274        scale: Downscale,
275    ) -> Result<DecodeOutcome<Self::Warning>, Self::Error> {
276        Ok(self
277            .inner
278            .decode_region_scaled_into_with_scratch(pool, out, stride, fmt, roi.into(), scale)?
279            .into())
280    }
281}
282
283impl<'a> ImageDecodeDevice<'a> for Decoder<'a> {
284    type DeviceSurface = Surface;
285}
286
287impl<'a> ImageDecodeSubmit<'a> for Decoder<'a> {
288    type Session = CudaSession;
289    type DeviceSurface = Surface;
290    type SubmittedSurface = ReadySubmission<Surface, Error>;
291
292    fn submit_to_device(
293        &mut self,
294        session: &mut Self::Session,
295        fmt: PixelFormat,
296        backend: BackendRequest,
297    ) -> Result<Self::SubmittedSurface, Self::Error> {
298        validate_surface_request(backend)?;
299        Ok(submit_ready_device(session, |session| {
300            self.decode_to_surface_impl(session, fmt, backend)
301        }))
302    }
303
304    fn submit_region_to_device(
305        &mut self,
306        session: &mut Self::Session,
307        fmt: PixelFormat,
308        roi: Rect,
309        backend: BackendRequest,
310    ) -> Result<Self::SubmittedSurface, Self::Error> {
311        validate_surface_request(backend)?;
312        Ok(submit_ready_device(session, |session| {
313            self.decode_region_to_surface_impl(session, fmt, roi, backend)
314        }))
315    }
316
317    fn submit_scaled_to_device(
318        &mut self,
319        session: &mut Self::Session,
320        fmt: PixelFormat,
321        scale: Downscale,
322        backend: BackendRequest,
323    ) -> Result<Self::SubmittedSurface, Self::Error> {
324        validate_surface_request(backend)?;
325        Ok(submit_ready_device(session, |session| {
326            self.decode_scaled_to_surface_impl(session, fmt, scale, backend)
327        }))
328    }
329
330    fn submit_region_scaled_to_device(
331        &mut self,
332        session: &mut Self::Session,
333        fmt: PixelFormat,
334        roi: Rect,
335        scale: Downscale,
336        backend: BackendRequest,
337    ) -> Result<Self::SubmittedSurface, Self::Error> {
338        validate_surface_request(backend)?;
339        Ok(submit_ready_device(session, |session| {
340            self.decode_region_scaled_to_surface_impl(session, fmt, roi, scale, backend)
341        }))
342    }
343}