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