Skip to main content

j2k_cuda/batch/
types.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! CUDA batch result and resident-output contracts.
4
5#[cfg(feature = "cuda-runtime")]
6use super::Arc;
7use super::{
8    BatchGroupInfo, BatchInfrastructureError, Error, IndexedBatchError, J2kDecodeWarning,
9    PreparedBatchGroup, Rect, Surface,
10};
11
12/// Failure while preparing or executing an owned CUDA batch.
13#[derive(Debug, thiserror::Error)]
14#[non_exhaustive]
15pub enum CudaBatchError {
16    /// Shared codec preparation or host-side batch infrastructure failed.
17    #[error(transparent)]
18    Infrastructure(#[from] BatchInfrastructureError),
19    /// CUDA execution failed for one homogeneous group.
20    ///
21    /// No output surfaces from the affected group are exposed.
22    #[error("CUDA batch group containing source indices {source_indices:?} failed: {source}")]
23    GroupExecution {
24        /// Every original input index whose dense group output was discarded.
25        source_indices: Vec<usize>,
26        /// Strict CUDA adapter or runtime failure.
27        #[source]
28        source: Box<Error>,
29    },
30}
31
32impl CudaBatchError {
33    #[allow(
34        clippy::disallowed_methods,
35        reason = "error construction preserves its infallible public signature while retaining affected source indices"
36    )]
37    pub(super) fn group(group: &PreparedBatchGroup, source: Error) -> Self {
38        Self::GroupExecution {
39            source_indices: group.source_indices().to_vec(),
40            source: Box::new(source),
41        }
42    }
43
44    /// Whether submitted CUDA work may still reference an external
45    /// destination because completion could not be established.
46    #[cfg(feature = "cuda-runtime")]
47    #[doc(hidden)]
48    pub fn completion_is_uncertain(&self) -> bool {
49        match self {
50            Self::GroupExecution { source, .. } => source.completion_is_uncertain(),
51            Self::Infrastructure(_) => false,
52        }
53    }
54
55    /// Whether this failure prevents the current persistent batch operation
56    /// from safely continuing with later groups.
57    #[doc(hidden)]
58    #[must_use]
59    pub fn session_is_unusable(&self) -> bool {
60        match self {
61            Self::Infrastructure(_) => true,
62            Self::GroupExecution { source, .. } => source.session_is_unusable(),
63        }
64    }
65}
66
67#[cfg(test)]
68mod classification_tests {
69    use j2k_core::BatchInfrastructureError;
70
71    use super::CudaBatchError;
72    use crate::Error;
73
74    fn group_error(source: Error) -> CudaBatchError {
75        CudaBatchError::GroupExecution {
76            source_indices: vec![3],
77            source: Box::new(source),
78        }
79    }
80
81    #[test]
82    fn cuda_batch_session_classification_is_owned_by_codec_errors() {
83        assert!(
84            CudaBatchError::Infrastructure(BatchInfrastructureError::EmptyBatchPlan)
85                .session_is_unusable()
86        );
87        assert!(group_error(Error::CudaUnavailable).session_is_unusable());
88        assert!(!group_error(Error::UnsupportedCudaRequest {
89            reason: "test contract rejection",
90        })
91        .session_is_unusable());
92    }
93}
94
95/// Failure while executing one homogeneous CUDA group.
96///
97/// No partially written dense output from the affected group is exposed.
98/// Other prepared groups may still succeed when the retained CUDA session
99/// remains usable.
100#[derive(Debug, thiserror::Error)]
101#[error("CUDA batch group containing source indices {source_indices:?} failed: {source}")]
102pub struct CudaBatchGroupError {
103    source_indices: Vec<usize>,
104    #[source]
105    source: Box<Error>,
106}
107
108impl CudaBatchGroupError {
109    #[cfg(feature = "cuda-runtime")]
110    #[allow(
111        clippy::disallowed_methods,
112        reason = "error construction preserves its infallible public signature while retaining affected source indices"
113    )]
114    pub(super) fn new(group: &PreparedBatchGroup, source: Error) -> Self {
115        Self {
116            source_indices: group.source_indices().to_vec(),
117            source: Box::new(source),
118        }
119    }
120
121    #[cfg(feature = "cuda-runtime")]
122    pub(super) fn from_parts(source_indices: Vec<usize>, source: Error) -> Self {
123        Self {
124            source_indices,
125            source: Box::new(source),
126        }
127    }
128
129    /// Original input indices whose dense group output was discarded.
130    #[must_use]
131    pub fn source_indices(&self) -> &[usize] {
132        &self.source_indices
133    }
134
135    /// Strict CUDA adapter or runtime failure for this group.
136    #[must_use]
137    pub fn source(&self) -> &Error {
138        &self.source
139    }
140
141    /// Consume the group failure into affected indices and its source.
142    #[must_use]
143    pub fn into_parts(self) -> (Vec<usize>, Error) {
144        (self.source_indices, *self.source)
145    }
146}
147
148/// One successful homogeneous CUDA-resident output group.
149#[derive(Debug)]
150pub struct CudaBatchGroup {
151    pub(super) info: BatchGroupInfo,
152    pub(super) source_indices: Vec<usize>,
153    pub(super) decoded_rects: Vec<Rect>,
154    pub(super) warnings: Vec<Vec<J2kDecodeWarning>>,
155    pub(super) surfaces: Vec<Surface>,
156    #[cfg(feature = "cuda-runtime")]
157    pub(super) dense_output: CudaResidentBatchBuffer,
158}
159
160/// One codec-owned dense CUDA allocation containing a homogeneous batch.
161///
162/// This owner is the canonical resident representation for every exact-native
163/// grayscale or color group. Grayscale and NHWC RGB/RGBA groups also expose
164/// ordinary [`Surface`] views over the same allocation for compatibility.
165#[cfg(feature = "cuda-runtime")]
166#[derive(Debug)]
167pub struct CudaResidentBatchBuffer {
168    pub(super) buffer: Arc<j2k_cuda_runtime::CudaDeviceBuffer>,
169    pub(super) ranges: Vec<j2k_cuda_runtime::CudaDeviceBufferRange>,
170}
171
172#[cfg(feature = "cuda-runtime")]
173impl CudaResidentBatchBuffer {
174    /// Codec-owned CUDA allocation containing every image range.
175    #[must_use]
176    pub fn buffer(&self) -> &j2k_cuda_runtime::CudaDeviceBuffer {
177        &self.buffer
178    }
179
180    /// Tightly concatenated per-image byte ranges in dense batch order.
181    #[must_use]
182    pub fn ranges(&self) -> &[j2k_cuda_runtime::CudaDeviceBufferRange] {
183        &self.ranges
184    }
185}
186
187impl CudaBatchGroup {
188    /// Shared decoded dimensions, type, color, transform, route, and layout.
189    #[must_use]
190    pub const fn info(&self) -> &BatchGroupInfo {
191        &self.info
192    }
193
194    /// Original input indices in dense batch order.
195    #[must_use]
196    pub fn source_indices(&self) -> &[usize] {
197        &self.source_indices
198    }
199
200    /// Actual decoded rectangle for each image.
201    #[must_use]
202    pub fn decoded_rects(&self) -> &[Rect] {
203        &self.decoded_rects
204    }
205
206    /// Non-fatal codec warnings for each image.
207    #[must_use]
208    pub fn warnings(&self) -> &[Vec<J2kDecodeWarning>] {
209        &self.warnings
210    }
211
212    /// CUDA-resident image views in dense batch order.
213    ///
214    /// Grayscale groups expose one view per image. NHWC RGB/RGBA groups also
215    /// expose compatible interleaved views over their dense group allocation.
216    /// No decoded host staging is used.
217    #[must_use]
218    pub fn surfaces(&self) -> &[Surface] {
219        &self.surfaces
220    }
221
222    /// Dense codec-owned allocation for exact-native grayscale or color output.
223    ///
224    /// NCHW color groups must be consumed through this owner because a
225    /// [`Surface`] describes interleaved pixels. Grayscale and NHWC RGB/RGBA
226    /// groups return both this owner and compatible surface views.
227    #[cfg(feature = "cuda-runtime")]
228    #[must_use]
229    pub const fn dense_output(&self) -> &CudaResidentBatchBuffer {
230        &self.dense_output
231    }
232
233    /// Consume the group into metadata and CUDA-resident views.
234    #[must_use]
235    #[expect(
236        clippy::type_complexity,
237        reason = "the tuple mirrors the group's five explicitly documented owners"
238    )]
239    #[cfg(not(feature = "cuda-runtime"))]
240    pub fn into_parts(
241        self,
242    ) -> (
243        BatchGroupInfo,
244        Vec<usize>,
245        Vec<Rect>,
246        Vec<Vec<J2kDecodeWarning>>,
247        Vec<Surface>,
248    ) {
249        (
250            self.info,
251            self.source_indices,
252            self.decoded_rects,
253            self.warnings,
254            self.surfaces,
255        )
256    }
257
258    /// Consume the group into metadata, compatible surfaces, and its required
259    /// dense exact-native allocation.
260    #[cfg(feature = "cuda-runtime")]
261    #[must_use]
262    #[expect(
263        clippy::type_complexity,
264        reason = "the tuple mirrors the group's explicitly documented owners"
265    )]
266    pub fn into_parts(
267        self,
268    ) -> (
269        BatchGroupInfo,
270        Vec<usize>,
271        Vec<Rect>,
272        Vec<Vec<J2kDecodeWarning>>,
273        Vec<Surface>,
274        CudaResidentBatchBuffer,
275    ) {
276        (
277            self.info,
278            self.source_indices,
279            self.decoded_rects,
280            self.warnings,
281            self.surfaces,
282            self.dense_output,
283        )
284    }
285}
286
287/// CUDA batch successes plus indexed codec preflight failures.
288#[derive(Debug)]
289pub struct CudaBatchDecodeResult {
290    pub(super) groups: Vec<CudaBatchGroup>,
291    pub(super) errors: Vec<IndexedBatchError>,
292    pub(super) group_errors: Vec<CudaBatchGroupError>,
293}
294
295impl CudaBatchDecodeResult {
296    /// Successfully decoded homogeneous device groups.
297    #[must_use]
298    pub fn groups(&self) -> &[CudaBatchGroup] {
299        &self.groups
300    }
301
302    /// Per-input parsing and representability failures from shared preflight.
303    #[must_use]
304    pub fn errors(&self) -> &[IndexedBatchError] {
305        &self.errors
306    }
307
308    /// Homogeneous groups that failed during recoverable CUDA execution.
309    #[must_use]
310    pub fn group_errors(&self) -> &[CudaBatchGroupError] {
311        &self.group_errors
312    }
313
314    /// Consume this result into successful groups, indexed preflight errors,
315    /// and homogeneous execution failures.
316    #[must_use]
317    pub fn into_parts(
318        self,
319    ) -> (
320        Vec<CudaBatchGroup>,
321        Vec<IndexedBatchError>,
322        Vec<CudaBatchGroupError>,
323    ) {
324        (self.groups, self.errors, self.group_errors)
325    }
326}