Skip to main content

j2k_cuda/decoder/
api.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3#[cfg(feature = "cuda-runtime")]
4use super::grayscale_batch::decode_grayscale_cuda_resident_batch_into_with_profile;
5#[cfg(feature = "cuda-runtime")]
6use super::resident::{
7    decode_batch_to_cuda_resident_surface_with_profile_control,
8    decode_region_scaled_to_cuda_resident_surface_impl,
9    decode_region_to_cuda_resident_surface_impl, decode_scaled_to_cuda_resident_surface_impl,
10    decode_to_cuda_resident_surface_impl, decode_to_cuda_resident_surface_with_profile_impl,
11};
12use super::{
13    checked_surface_len, submit_ready_device, validate_surface_request,
14    wrap_cpu_staged_cuda_surface, wrap_surface, BackendRequest, CpuBackedImageDecode, CpuDecoder,
15    CpuJ2kScratchPool, CudaHtj2kProfileReport, CudaSession, DecodeOutcome, DeviceDecodePlan,
16    DeviceDecodeRequest, Downscale, Error, ImageCodec, ImageDecodeDevice, ImageDecodeSubmit,
17    J2kDecodeWarning, J2kDecoder, J2kView, PixelFormat, ReadySubmission, Rect, Surface,
18    DEFAULT_MAX_HOST_ALLOCATION_BYTES,
19};
20use crate::{
21    allocation::try_vec_filled,
22    routing::{auto_cuda_available, auto_decode_uses_cuda, AutoDecodeOperation},
23};
24
25impl<'a> J2kDecoder<'a> {
26    /// Create a CUDA-facing decoder from compressed bytes.
27    pub fn new(input: &'a [u8]) -> Result<Self, Error> {
28        let view = J2kView::parse(input)?;
29        let (transfer_syntax, payload_kind) = view.support_info().map_or((None, None), |support| {
30            (Some(support.transfer_syntax), Some(support.payload_kind))
31        });
32        Ok(Self {
33            bytes: input,
34            inner: CpuDecoder::from_view(view)?,
35            transfer_syntax,
36            payload_kind,
37            pool: CpuJ2kScratchPool::new(),
38        })
39    }
40
41    fn auto_decode_uses_cuda(
42        &self,
43        work_dimensions: (u32, u32),
44        fmt: PixelFormat,
45        operation: AutoDecodeOperation,
46    ) -> bool {
47        match (self.transfer_syntax, self.payload_kind) {
48            (Some(transfer_syntax), Some(payload_kind)) => auto_decode_uses_cuda(
49                work_dimensions,
50                self.inner.info().components,
51                fmt,
52                transfer_syntax,
53                payload_kind,
54                operation,
55            ),
56            _ => false,
57        }
58    }
59
60    fn decode_to_surface_impl(
61        &mut self,
62        session: &mut CudaSession,
63        fmt: PixelFormat,
64        backend: BackendRequest,
65    ) -> Result<Surface, Error> {
66        validate_surface_request(backend)?;
67        if matches!(backend, BackendRequest::Cuda)
68            || (backend == BackendRequest::Auto
69                && self.auto_decode_uses_cuda(
70                    self.inner.info().dimensions,
71                    fmt,
72                    AutoDecodeOperation::Full,
73                )
74                && auto_cuda_available(session)?)
75        {
76            return decode_to_cuda_resident_surface_impl(self, session, fmt);
77        }
78        let dims = self.inner.info().dimensions;
79        let (mut out, stride) = allocate_cpu_surface(dims, fmt)?;
80        j2k_profile::emit_gpu_route_surface_profile(
81            ("j2k", "cuda"),
82            (
83                "full",
84                format_args!("{backend:?}"),
85                format_args!("{fmt:?}"),
86                "cpu_decode_then_wrap",
87            ),
88            dims,
89            [],
90        );
91        self.inner
92            .decode_into_with_scratch(&mut self.pool, &mut out, stride, fmt)?;
93        wrap_surface(out, dims, fmt, backend, session)
94    }
95
96    fn decode_region_to_surface_impl(
97        &mut self,
98        session: &mut CudaSession,
99        fmt: PixelFormat,
100        roi: Rect,
101        backend: BackendRequest,
102    ) -> Result<Surface, Error> {
103        validate_surface_request(backend)?;
104        if matches!(backend, BackendRequest::Cuda) {
105            return decode_region_to_cuda_resident_surface_impl(self, session, fmt, roi);
106        }
107        let plan = DeviceDecodePlan::for_image(
108            self.inner.info().dimensions,
109            DeviceDecodeRequest::Region { roi },
110        )?;
111        let dims = plan.output_dims();
112        if backend == BackendRequest::Auto
113            && self.auto_decode_uses_cuda(dims, fmt, AutoDecodeOperation::Region)
114            && auto_cuda_available(session)?
115        {
116            return decode_region_to_cuda_resident_surface_impl(self, session, fmt, roi);
117        }
118        let (mut out, stride) = allocate_cpu_surface(dims, fmt)?;
119        self.inner
120            .decode_region_into(&mut self.pool, &mut out, stride, fmt, plan.source_rect())?;
121        wrap_surface(out, dims, fmt, backend, session)
122    }
123
124    fn decode_scaled_to_surface_impl(
125        &mut self,
126        session: &mut CudaSession,
127        fmt: PixelFormat,
128        scale: Downscale,
129        backend: BackendRequest,
130    ) -> Result<Surface, Error> {
131        validate_surface_request(backend)?;
132        if matches!(backend, BackendRequest::Cuda) {
133            return decode_scaled_to_cuda_resident_surface_impl(self, session, fmt, scale);
134        }
135        let plan = DeviceDecodePlan::for_image(
136            self.inner.info().dimensions,
137            DeviceDecodeRequest::Scaled { scale },
138        )?;
139        let dims = plan.output_dims();
140        if backend == BackendRequest::Auto
141            && scale == Downscale::Half
142            && self.auto_decode_uses_cuda(dims, fmt, AutoDecodeOperation::ScaledHalf)
143            && auto_cuda_available(session)?
144        {
145            return decode_scaled_to_cuda_resident_surface_impl(self, session, fmt, scale);
146        }
147        let (mut out, stride) = allocate_cpu_surface(dims, fmt)?;
148        self.inner
149            .decode_scaled_into(&mut self.pool, &mut out, stride, fmt, scale)?;
150        wrap_surface(out, dims, fmt, backend, session)
151    }
152
153    fn decode_region_scaled_to_surface_impl(
154        &mut self,
155        session: &mut CudaSession,
156        fmt: PixelFormat,
157        roi: Rect,
158        scale: Downscale,
159        backend: BackendRequest,
160    ) -> Result<Surface, Error> {
161        validate_surface_request(backend)?;
162        if matches!(backend, BackendRequest::Cuda) {
163            return decode_region_scaled_to_cuda_resident_surface_impl(
164                self, session, fmt, roi, scale,
165            );
166        }
167        let plan = DeviceDecodePlan::for_image(
168            self.inner.info().dimensions,
169            DeviceDecodeRequest::RegionScaled { roi, scale },
170        )?;
171        let dims = plan.output_dims();
172        let (mut out, stride) = allocate_cpu_surface(dims, fmt)?;
173        self.inner.decode_region_scaled_into(
174            &mut self.pool,
175            &mut out,
176            stride,
177            fmt,
178            plan.source_rect(),
179            scale,
180        )?;
181        wrap_surface(out, dims, fmt, backend, session)
182    }
183
184    /// Strictly decode a full HTJ2K image into a CUDA-backed surface using an
185    /// existing backend session.
186    pub fn decode_to_device_with_session(
187        &mut self,
188        fmt: PixelFormat,
189        session: &mut CudaSession,
190    ) -> Result<Surface, Error> {
191        self.decode_to_surface_impl(session, fmt, BackendRequest::Cuda)
192    }
193
194    /// Strictly decode a geometry request into a CUDA-backed surface using an
195    /// existing backend session.
196    #[doc(hidden)]
197    pub fn decode_request_to_device_with_session(
198        &mut self,
199        fmt: PixelFormat,
200        request: DeviceDecodeRequest,
201        session: &mut CudaSession,
202    ) -> Result<Surface, Error> {
203        match request {
204            DeviceDecodeRequest::Full => self.decode_to_device_with_session(fmt, session),
205            DeviceDecodeRequest::Region { roi } => {
206                self.decode_region_to_device_with_session(fmt, roi, session)
207            }
208            DeviceDecodeRequest::Scaled { scale } => {
209                self.decode_scaled_to_device_with_session(fmt, scale, session)
210            }
211            DeviceDecodeRequest::RegionScaled { roi, scale } => {
212                self.decode_region_scaled_to_device_with_session(fmt, roi, scale, session)
213            }
214        }
215    }
216
217    /// Strictly decode a full HTJ2K image into a CUDA-backed surface and return
218    /// a structured profile report for CPU planning and CUDA stages.
219    #[doc(hidden)]
220    pub fn decode_to_device_with_session_and_profile(
221        &mut self,
222        fmt: PixelFormat,
223        session: &mut CudaSession,
224    ) -> Result<(Surface, CudaHtj2kProfileReport), Error> {
225        decode_to_cuda_resident_surface_with_profile_impl(self, session, fmt)
226    }
227
228    /// Strictly decode a batch of full HTJ2K images into CUDA-backed surfaces
229    /// using an existing backend session.
230    pub fn decode_batch_to_device_with_session(
231        inputs: &[&[u8]],
232        fmt: PixelFormat,
233        session: &mut CudaSession,
234    ) -> Result<Vec<Surface>, Error> {
235        decode_batch_to_cuda_resident_surface_with_profile_control(inputs, session, fmt, false)
236            .map(|(surfaces, _report)| surfaces)
237    }
238
239    /// Strictly decode a batch of full HTJ2K images into CUDA-backed surfaces
240    /// and return one aggregate profile report for the shared batch.
241    #[doc(hidden)]
242    pub fn decode_batch_to_device_with_session_and_profile(
243        inputs: &[&[u8]],
244        fmt: PixelFormat,
245        session: &mut CudaSession,
246    ) -> Result<(Vec<Surface>, CudaHtj2kProfileReport), Error> {
247        decode_batch_to_cuda_resident_surface_with_profile_control(inputs, session, fmt, true)
248    }
249
250    /// Strictly decode a full grayscale batch directly into a validated
251    /// caller-owned CUDA destination.
252    #[cfg(feature = "cuda-runtime")]
253    #[doc(hidden)]
254    pub fn decode_batch_into_external_device_with_session(
255        inputs: &[&[u8]],
256        fmt: PixelFormat,
257        destination: &mut j2k_cuda_runtime::CudaExternalDeviceBufferViewMut<'_>,
258        session: &mut CudaSession,
259    ) -> Result<
260        (
261            Vec<j2k_cuda_runtime::CudaDeviceBufferRange>,
262            CudaHtj2kProfileReport,
263        ),
264        Error,
265    > {
266        decode_grayscale_cuda_resident_batch_into_with_profile(
267            inputs,
268            session,
269            fmt,
270            destination,
271            false,
272        )
273    }
274
275    /// Strictly decode a full-resolution HTJ2K region into a CUDA-backed
276    /// surface using an existing backend session.
277    pub(crate) fn decode_region_to_device_with_session(
278        &mut self,
279        fmt: PixelFormat,
280        roi: Rect,
281        session: &mut CudaSession,
282    ) -> Result<Surface, Error> {
283        self.decode_region_to_surface_impl(session, fmt, roi, BackendRequest::Cuda)
284    }
285
286    /// Strictly decode a reduced-resolution HTJ2K image into a CUDA-backed
287    /// surface using an existing backend session.
288    pub(crate) fn decode_scaled_to_device_with_session(
289        &mut self,
290        fmt: PixelFormat,
291        scale: Downscale,
292        session: &mut CudaSession,
293    ) -> Result<Surface, Error> {
294        self.decode_scaled_to_surface_impl(session, fmt, scale, BackendRequest::Cuda)
295    }
296
297    /// Strictly decode a reduced-resolution HTJ2K region into a CUDA-backed
298    /// surface using an existing backend session.
299    pub(crate) fn decode_region_scaled_to_device_with_session(
300        &mut self,
301        fmt: PixelFormat,
302        roi: Rect,
303        scale: Downscale,
304        session: &mut CudaSession,
305    ) -> Result<Surface, Error> {
306        self.decode_region_scaled_to_surface_impl(session, fmt, roi, scale, BackendRequest::Cuda)
307    }
308
309    /// Decode a full image through the CPU path and wrap it as a host surface.
310    pub fn decode_to_host_surface(&mut self, fmt: PixelFormat) -> Result<Surface, Error> {
311        let mut session = CudaSession::default();
312        self.decode_to_surface_impl(&mut session, fmt, BackendRequest::Cpu)
313    }
314
315    /// Decode a full image on CPU and upload it into a CUDA buffer using an
316    /// existing backend session.
317    pub fn decode_to_cpu_staged_cuda_surface_with_session(
318        &mut self,
319        fmt: PixelFormat,
320        session: &mut CudaSession,
321    ) -> Result<Surface, Error> {
322        let dims = self.inner.info().dimensions;
323        let (mut out, stride) = allocate_cpu_surface(dims, fmt)?;
324        self.inner
325            .decode_into_with_scratch(&mut self.pool, &mut out, stride, fmt)?;
326        wrap_cpu_staged_cuda_surface(&out, dims, fmt, session)
327    }
328
329    /// Decode a region on CPU and upload it into a CUDA buffer using an
330    /// existing backend session.
331    pub fn decode_region_to_cpu_staged_cuda_surface_with_session(
332        &mut self,
333        fmt: PixelFormat,
334        roi: Rect,
335        session: &mut CudaSession,
336    ) -> Result<Surface, Error> {
337        let plan = DeviceDecodePlan::for_image(
338            self.inner.info().dimensions,
339            DeviceDecodeRequest::Region { roi },
340        )?;
341        let dims = plan.output_dims();
342        let (mut out, stride) = allocate_cpu_surface(dims, fmt)?;
343        self.inner
344            .decode_region_into(&mut self.pool, &mut out, stride, fmt, plan.source_rect())?;
345        wrap_cpu_staged_cuda_surface(&out, dims, fmt, session)
346    }
347
348    /// Decode a scaled image on CPU and upload it into a CUDA buffer using an
349    /// existing backend session.
350    pub fn decode_scaled_to_cpu_staged_cuda_surface_with_session(
351        &mut self,
352        fmt: PixelFormat,
353        scale: Downscale,
354        session: &mut CudaSession,
355    ) -> Result<Surface, Error> {
356        let dims = DeviceDecodePlan::for_image(
357            self.inner.info().dimensions,
358            DeviceDecodeRequest::Scaled { scale },
359        )?
360        .output_dims();
361        let (mut out, stride) = allocate_cpu_surface(dims, fmt)?;
362        self.inner
363            .decode_scaled_into(&mut self.pool, &mut out, stride, fmt, scale)?;
364        wrap_cpu_staged_cuda_surface(&out, dims, fmt, session)
365    }
366
367    /// Decode a scaled region on CPU and upload it into a CUDA buffer using an
368    /// existing backend session.
369    pub fn decode_region_scaled_to_cpu_staged_cuda_surface_with_session(
370        &mut self,
371        fmt: PixelFormat,
372        roi: Rect,
373        scale: Downscale,
374        session: &mut CudaSession,
375    ) -> Result<Surface, Error> {
376        let plan = DeviceDecodePlan::for_image(
377            self.inner.info().dimensions,
378            DeviceDecodeRequest::RegionScaled { roi, scale },
379        )?;
380        let dims = plan.output_dims();
381        let (mut out, stride) = allocate_cpu_surface(dims, fmt)?;
382        self.inner.decode_region_scaled_into(
383            &mut self.pool,
384            &mut out,
385            stride,
386            fmt,
387            plan.source_rect(),
388            scale,
389        )?;
390        wrap_cpu_staged_cuda_surface(&out, dims, fmt, session)
391    }
392}
393
394fn allocate_cpu_surface(dims: (u32, u32), fmt: PixelFormat) -> Result<(Vec<u8>, usize), Error> {
395    let (stride, len) = checked_surface_len(
396        dims,
397        fmt.bytes_per_pixel(),
398        DEFAULT_MAX_HOST_ALLOCATION_BYTES,
399        "j2k CUDA CPU-staged surface",
400    )?;
401    Ok((
402        try_vec_filled(len, 0u8, "j2k CUDA CPU-staged surface")?,
403        stride,
404    ))
405}
406
407#[cfg(not(feature = "cuda-runtime"))]
408fn decode_to_cuda_resident_surface_impl(
409    _decoder: &mut J2kDecoder<'_>,
410    _session: &mut CudaSession,
411    _fmt: PixelFormat,
412) -> Result<Surface, Error> {
413    Err(Error::CudaUnavailable)
414}
415
416#[cfg(not(feature = "cuda-runtime"))]
417fn decode_to_cuda_resident_surface_with_profile_impl(
418    _decoder: &mut J2kDecoder<'_>,
419    _session: &mut CudaSession,
420    _fmt: PixelFormat,
421) -> Result<(Surface, CudaHtj2kProfileReport), Error> {
422    Err(Error::CudaUnavailable)
423}
424
425#[cfg(not(feature = "cuda-runtime"))]
426fn decode_region_to_cuda_resident_surface_impl(
427    _decoder: &mut J2kDecoder<'_>,
428    _session: &mut CudaSession,
429    _fmt: PixelFormat,
430    _roi: Rect,
431) -> Result<Surface, Error> {
432    Err(Error::CudaUnavailable)
433}
434
435#[cfg(not(feature = "cuda-runtime"))]
436fn decode_scaled_to_cuda_resident_surface_impl(
437    _decoder: &mut J2kDecoder<'_>,
438    _session: &mut CudaSession,
439    _fmt: PixelFormat,
440    _scale: Downscale,
441) -> Result<Surface, Error> {
442    Err(Error::CudaUnavailable)
443}
444
445#[cfg(not(feature = "cuda-runtime"))]
446fn decode_region_scaled_to_cuda_resident_surface_impl(
447    _decoder: &mut J2kDecoder<'_>,
448    _session: &mut CudaSession,
449    _fmt: PixelFormat,
450    _roi: Rect,
451    _scale: Downscale,
452) -> Result<Surface, Error> {
453    Err(Error::CudaUnavailable)
454}
455
456#[cfg(not(feature = "cuda-runtime"))]
457fn decode_batch_to_cuda_resident_surface_with_profile_control(
458    _inputs: &[&[u8]],
459    _session: &mut CudaSession,
460    _fmt: PixelFormat,
461    _collect_stage_timings: bool,
462) -> Result<(Vec<Surface>, CudaHtj2kProfileReport), Error> {
463    Err(Error::CudaUnavailable)
464}
465
466#[doc(hidden)]
467impl ImageCodec for J2kDecoder<'_> {
468    type Error = Error;
469    type Warning = J2kDecodeWarning;
470    type Pool = crate::J2kScratchPool;
471}
472
473impl<'a> CpuBackedImageDecode<'a> for J2kDecoder<'a> {
474    type Cpu = CpuDecoder<'a>;
475    type View = J2kView<'a>;
476
477    fn inspect_cpu(input: &'a [u8]) -> Result<j2k_core::Info, Self::Error> {
478        Ok(CpuDecoder::inspect(input)?)
479    }
480
481    fn parse_cpu(input: &'a [u8]) -> Result<Self::View, Self::Error> {
482        Ok(J2kView::parse(input)?)
483    }
484
485    fn from_cpu_view(view: Self::View) -> Result<Self, Self::Error> {
486        let bytes = view.bytes();
487        let (transfer_syntax, payload_kind) = view.support_info().map_or((None, None), |support| {
488            (Some(support.transfer_syntax), Some(support.payload_kind))
489        });
490        Ok(Self {
491            bytes,
492            inner: CpuDecoder::from_view(view)?,
493            transfer_syntax,
494            payload_kind,
495            pool: CpuJ2kScratchPool::new(),
496        })
497    }
498
499    fn cpu_decoder_mut(&mut self) -> &mut Self::Cpu {
500        &mut self.inner
501    }
502
503    fn map_cpu_outcome(
504        outcome: DecodeOutcome<<Self::Cpu as ImageCodec>::Warning>,
505    ) -> DecodeOutcome<Self::Warning> {
506        outcome
507    }
508}
509
510#[doc(hidden)]
511impl<'a> ImageDecodeDevice<'a> for J2kDecoder<'a> {
512    type DeviceSurface = Surface;
513}
514
515#[doc(hidden)]
516impl<'a> ImageDecodeSubmit<'a> for J2kDecoder<'a> {
517    type Session = CudaSession;
518    type DeviceSurface = Surface;
519    type SubmittedSurface = ReadySubmission<Surface, Error>;
520
521    fn submit_to_device(
522        &mut self,
523        session: &mut Self::Session,
524        fmt: PixelFormat,
525        backend: BackendRequest,
526    ) -> Result<Self::SubmittedSurface, Self::Error> {
527        validate_surface_request(backend)?;
528        Ok(submit_ready_device(session, |session| {
529            self.decode_to_surface_impl(session, fmt, backend)
530        }))
531    }
532
533    fn submit_region_to_device(
534        &mut self,
535        session: &mut Self::Session,
536        fmt: PixelFormat,
537        roi: Rect,
538        backend: BackendRequest,
539    ) -> Result<Self::SubmittedSurface, Self::Error> {
540        validate_surface_request(backend)?;
541        Ok(submit_ready_device(session, |session| {
542            self.decode_region_to_surface_impl(session, fmt, roi, backend)
543        }))
544    }
545
546    fn submit_scaled_to_device(
547        &mut self,
548        session: &mut Self::Session,
549        fmt: PixelFormat,
550        scale: Downscale,
551        backend: BackendRequest,
552    ) -> Result<Self::SubmittedSurface, Self::Error> {
553        validate_surface_request(backend)?;
554        Ok(submit_ready_device(session, |session| {
555            self.decode_scaled_to_surface_impl(session, fmt, scale, backend)
556        }))
557    }
558
559    fn submit_region_scaled_to_device(
560        &mut self,
561        session: &mut Self::Session,
562        fmt: PixelFormat,
563        roi: Rect,
564        scale: Downscale,
565        backend: BackendRequest,
566    ) -> Result<Self::SubmittedSurface, Self::Error> {
567        validate_surface_request(backend)?;
568        Ok(submit_ready_device(session, |session| {
569            self.decode_region_scaled_to_surface_impl(session, fmt, roi, scale, backend)
570        }))
571    }
572}