Skip to main content

j2k_cuda/
codec.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3use j2k::{
4    DeviceDecodePlan, DeviceDecodeRequest, J2kCodec as CpuCodec, J2kContext as CpuJ2kContext,
5    J2kDecodeWarning, J2kDecoder as CpuDecoder, J2kScratchPool as CpuJ2kScratchPool,
6};
7use j2k_core::{
8    checked_surface_len, submit_ready_device, BackendRequest, Downscale, ImageCodec, PixelFormat,
9    ReadySubmission, Rect, TileBatchDecode, TileBatchDecodeDevice, TileBatchDecodeManyDevice,
10    TileBatchDecodeSubmit, TileRegionScaledDecodeJob, TileRegionScaledDeviceDecodeRequest,
11    DEFAULT_MAX_HOST_ALLOCATION_BYTES,
12};
13
14use crate::{
15    allocation::{try_collect_results_exact, try_vec_filled},
16    routing::{auto_cuda_available, auto_repeated_decode_uses_cuda, inputs_repeat_one_slice},
17    runtime::{validate_surface_request, wrap_surface},
18};
19use crate::{CudaSession, Error, J2kDecoder, Surface};
20
21/// Marker type implementing tile-batch CUDA surface decode traits.
22#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
23pub struct Codec;
24
25struct TileSurfaceRequest<'a> {
26    ctx: &'a mut CpuJ2kContext,
27    session: &'a mut CudaSession,
28    pool: &'a mut CpuJ2kScratchPool,
29    input: &'a [u8],
30    fmt: PixelFormat,
31    operation: DeviceDecodeRequest,
32    backend: BackendRequest,
33}
34
35#[doc(hidden)]
36impl ImageCodec for Codec {
37    type Error = Error;
38    type Warning = J2kDecodeWarning;
39    type Pool = crate::J2kScratchPool;
40}
41
42impl Codec {
43    fn supports_cuda_batch_format(fmt: PixelFormat) -> bool {
44        matches!(
45            fmt,
46            PixelFormat::Gray8
47                | PixelFormat::Gray16
48                | PixelFormat::GrayI16
49                | PixelFormat::Rgb8
50                | PixelFormat::Rgba8
51                | PixelFormat::Rgb16
52                | PixelFormat::Rgba16
53        )
54    }
55
56    #[cfg(feature = "cuda-runtime")]
57    fn decode_tiles_to_cuda_batch(
58        inputs: &[&[u8]],
59        fmt: PixelFormat,
60        session: &mut CudaSession,
61    ) -> Result<Vec<Surface>, Error> {
62        J2kDecoder::decode_batch_to_device_with_session(inputs, fmt, session)
63    }
64
65    #[cfg(not(feature = "cuda-runtime"))]
66    fn decode_tiles_to_cuda_batch(
67        _inputs: &[&[u8]],
68        _fmt: PixelFormat,
69        _session: &mut CudaSession,
70    ) -> Result<Vec<Surface>, Error> {
71        Err(Error::CudaUnavailable)
72    }
73
74    fn decode_tile_op_to_surface_impl(request: TileSurfaceRequest<'_>) -> Result<Surface, Error> {
75        let TileSurfaceRequest {
76            ctx,
77            session,
78            pool,
79            input,
80            fmt,
81            operation,
82            backend,
83        } = request;
84        validate_surface_request(backend)?;
85        if backend == BackendRequest::Cuda {
86            let mut decoder = J2kDecoder::new(input)?;
87            return decoder.decode_request_to_device_with_session(fmt, operation, session);
88        }
89
90        let plan = DeviceDecodePlan::for_image(CpuDecoder::inspect(input)?.dimensions, operation)?;
91        let dims = plan.output_dims();
92        let (mut out, stride) = allocate_cpu_surface(dims, fmt)?;
93        match operation {
94            DeviceDecodeRequest::Full => {
95                CpuCodec::decode_tile(ctx, pool, input, &mut out, stride, fmt)?;
96            }
97            DeviceDecodeRequest::Region { .. } => {
98                CpuCodec::decode_tile_region(
99                    ctx,
100                    pool,
101                    input,
102                    &mut out,
103                    stride,
104                    fmt,
105                    plan.source_rect(),
106                )?;
107            }
108            DeviceDecodeRequest::Scaled { scale } => {
109                CpuCodec::decode_tile_scaled(ctx, pool, input, &mut out, stride, fmt, scale)?;
110            }
111            DeviceDecodeRequest::RegionScaled { scale, .. } => {
112                CpuCodec::decode_tile_region_scaled(
113                    ctx,
114                    pool,
115                    fmt,
116                    TileRegionScaledDecodeJob {
117                        input,
118                        out: &mut out,
119                        stride,
120                        roi: plan.source_rect(),
121                        scale,
122                    },
123                )?;
124            }
125        }
126        wrap_surface(out, dims, fmt, backend, session)
127    }
128}
129
130fn allocate_cpu_surface(dims: (u32, u32), fmt: PixelFormat) -> Result<(Vec<u8>, usize), Error> {
131    let (stride, len) = checked_surface_len(
132        dims,
133        fmt.bytes_per_pixel(),
134        DEFAULT_MAX_HOST_ALLOCATION_BYTES,
135        "j2k CUDA CPU fallback surface",
136    )?;
137    Ok((
138        try_vec_filled(len, 0u8, "j2k CUDA CPU fallback surface")?,
139        stride,
140    ))
141}
142
143#[doc(hidden)]
144impl TileBatchDecodeSubmit for Codec {
145    type Context = CpuJ2kContext;
146    type Session = CudaSession;
147    type DeviceSurface = Surface;
148    type SubmittedSurface = ReadySubmission<Surface, Error>;
149
150    fn submit_tile_to_device(
151        ctx: &mut Self::Context,
152        session: &mut Self::Session,
153        pool: &mut Self::Pool,
154        input: &[u8],
155        fmt: PixelFormat,
156        backend: BackendRequest,
157    ) -> Result<Self::SubmittedSurface, Self::Error> {
158        validate_surface_request(backend)?;
159        Ok(submit_ready_device(session, |session| {
160            Self::decode_tile_op_to_surface_impl(TileSurfaceRequest {
161                ctx,
162                session,
163                pool,
164                input,
165                fmt,
166                operation: DeviceDecodeRequest::Full,
167                backend,
168            })
169        }))
170    }
171
172    fn submit_tile_region_to_device(
173        ctx: &mut Self::Context,
174        session: &mut Self::Session,
175        pool: &mut Self::Pool,
176        input: &[u8],
177        fmt: PixelFormat,
178        roi: Rect,
179        backend: BackendRequest,
180    ) -> Result<Self::SubmittedSurface, Self::Error> {
181        validate_surface_request(backend)?;
182        Ok(submit_ready_device(session, |session| {
183            Self::decode_tile_op_to_surface_impl(TileSurfaceRequest {
184                ctx,
185                session,
186                pool,
187                input,
188                fmt,
189                operation: DeviceDecodeRequest::Region { roi },
190                backend,
191            })
192        }))
193    }
194
195    fn submit_tile_scaled_to_device(
196        ctx: &mut Self::Context,
197        session: &mut Self::Session,
198        pool: &mut Self::Pool,
199        input: &[u8],
200        fmt: PixelFormat,
201        scale: Downscale,
202        backend: BackendRequest,
203    ) -> Result<Self::SubmittedSurface, Self::Error> {
204        validate_surface_request(backend)?;
205        Ok(submit_ready_device(session, |session| {
206            Self::decode_tile_op_to_surface_impl(TileSurfaceRequest {
207                ctx,
208                session,
209                pool,
210                input,
211                fmt,
212                operation: DeviceDecodeRequest::Scaled { scale },
213                backend,
214            })
215        }))
216    }
217
218    fn submit_tile_region_scaled_to_device(
219        ctx: &mut Self::Context,
220        session: &mut Self::Session,
221        pool: &mut Self::Pool,
222        request: TileRegionScaledDeviceDecodeRequest<'_>,
223    ) -> Result<Self::SubmittedSurface, Self::Error> {
224        let TileRegionScaledDeviceDecodeRequest {
225            input,
226            fmt,
227            roi,
228            scale,
229            backend,
230        } = request;
231        validate_surface_request(backend)?;
232        Ok(submit_ready_device(session, |session| {
233            Self::decode_tile_op_to_surface_impl(TileSurfaceRequest {
234                ctx,
235                session,
236                pool,
237                input,
238                fmt,
239                operation: DeviceDecodeRequest::RegionScaled { roi, scale },
240                backend,
241            })
242        }))
243    }
244}
245
246#[doc(hidden)]
247impl TileBatchDecodeDevice for Codec {
248    type Context = CpuJ2kContext;
249    type DeviceSurface = Surface;
250}
251
252#[doc(hidden)]
253impl TileBatchDecodeManyDevice for Codec {
254    type Context = CpuJ2kContext;
255    type DeviceSurface = Surface;
256
257    fn decode_tiles_to_device(
258        ctx: &mut Self::Context,
259        pool: &mut Self::Pool,
260        inputs: &[&[u8]],
261        fmt: PixelFormat,
262        backend: BackendRequest,
263    ) -> Result<Vec<Self::DeviceSurface>, Self::Error> {
264        validate_surface_request(backend)?;
265        if inputs.is_empty() {
266            return Ok(Vec::new());
267        }
268
269        let mut session = CudaSession::default();
270        if matches!(backend, BackendRequest::Cuda) && Self::supports_cuda_batch_format(fmt) {
271            return Self::decode_tiles_to_cuda_batch(inputs, fmt, &mut session);
272        }
273        if backend == BackendRequest::Auto
274            && Self::supports_cuda_batch_format(fmt)
275            && inputs_repeat_one_slice(inputs)
276        {
277            let support = CpuDecoder::inspect_support(inputs[0])?;
278            if auto_repeated_decode_uses_cuda(
279                support.info.dimensions,
280                support.info.components,
281                fmt,
282                support.transfer_syntax,
283                support.payload_kind,
284                inputs.len(),
285            ) && auto_cuda_available(&mut session)?
286            {
287                return Self::decode_tiles_to_cuda_batch(inputs, fmt, &mut session);
288            }
289        }
290
291        try_collect_results_exact(
292            inputs.iter().map(|input| {
293                Self::decode_tile_op_to_surface_impl(TileSurfaceRequest {
294                    ctx,
295                    session: &mut session,
296                    pool,
297                    input,
298                    fmt,
299                    operation: DeviceDecodeRequest::Full,
300                    backend,
301                })
302            }),
303            "j2k CUDA decode batch surfaces",
304        )
305    }
306}
307
308#[cfg(all(test, feature = "cuda-runtime"))]
309mod tests {
310    use j2k_core::{BackendRequest, PixelFormat, TileBatchDecodeManyDevice};
311    use j2k_test_support::{cuda_runtime_required, htj2k_rgb8_pattern_fixture};
312
313    use super::{Codec, CpuJ2kContext, CpuJ2kScratchPool};
314    use crate::decoder::{
315        testing_cuda_htj2k_batch_decode_calls, testing_reset_cuda_htj2k_batch_decode_calls,
316    };
317    use crate::{Error, SurfaceResidency};
318
319    #[test]
320    fn explicit_cuda_rgb_many_decode_uses_batch_api_once() {
321        testing_reset_cuda_htj2k_batch_decode_calls();
322        let fixture = rgb8_htj2k_fixture(32, 32);
323        let inputs = [fixture.as_slice(), fixture.as_slice()];
324        let mut ctx = CpuJ2kContext::default();
325        let mut pool = CpuJ2kScratchPool::new();
326
327        let result = Codec::decode_tiles_to_device(
328            &mut ctx,
329            &mut pool,
330            &inputs,
331            PixelFormat::Rgb8,
332            BackendRequest::Cuda,
333        );
334
335        assert_eq!(testing_cuda_htj2k_batch_decode_calls(), 1);
336        match result {
337            Ok(surfaces) => {
338                assert_eq!(surfaces.len(), inputs.len());
339                for surface in surfaces {
340                    assert_eq!(surface.residency(), SurfaceResidency::CudaResidentDecode);
341                    assert_eq!(surface.as_host_bytes(), None);
342                }
343            }
344            Err(Error::CudaUnavailable) => {
345                assert!(!cuda_runtime_required());
346            }
347            Err(error) => panic!("unexpected strict CUDA RGB batch error: {error}"),
348        }
349    }
350
351    fn rgb8_htj2k_fixture(width: u32, height: u32) -> Vec<u8> {
352        htj2k_rgb8_pattern_fixture(width, height, 17)
353    }
354}