Skip to main content

j2k_cuda_runtime/execution/
queued.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3use crate::{
4    error::CudaError,
5    memory::{
6        CudaBufferPoolReuseGuard, CudaDeviceBuffer, CudaDeviceBufferRange, CudaPooledDeviceBuffer,
7    },
8};
9
10#[doc(hidden)]
11/// Device buffer plus execution metadata.
12#[derive(Debug)]
13pub struct CudaKernelOutput {
14    pub(crate) buffer: CudaDeviceBuffer,
15    pub(crate) execution: CudaExecutionStats,
16}
17
18#[doc(hidden)]
19/// Multiple device buffers plus shared execution metadata from one batched kernel.
20#[derive(Debug)]
21pub struct CudaKernelBatchOutput {
22    pub(crate) outputs: Vec<CudaDeviceBuffer>,
23    pub(crate) execution: CudaExecutionStats,
24}
25
26#[doc(hidden)]
27/// One contiguous device buffer plus per-item ranges from one batched kernel.
28#[derive(Debug)]
29pub struct CudaKernelContiguousBatchOutput {
30    pub(crate) output: CudaDeviceBuffer,
31    pub(crate) ranges: Vec<CudaDeviceBufferRange>,
32    pub(crate) execution: CudaExecutionStats,
33}
34
35#[doc(hidden)]
36/// Pooled device buffer plus execution metadata.
37#[derive(Debug)]
38pub struct CudaPooledKernelOutput {
39    pub(crate) buffer: CudaPooledDeviceBuffer,
40    pub(crate) execution: CudaExecutionStats,
41}
42
43/// Enqueued CUDA work plus pooled resources that must stay unavailable for
44/// reuse until the default stream is synchronized. Dropping an unreleased
45/// value synchronizes before pool reuse.
46#[doc(hidden)]
47#[derive(Debug)]
48#[must_use = "queued CUDA work must be finished or retained until Drop synchronizes it"]
49pub struct CudaQueuedExecution {
50    pub(crate) resources: Vec<CudaPooledDeviceBuffer>,
51    pub(crate) execution: CudaExecutionStats,
52    pub(crate) pool_reuse_guard: Option<CudaBufferPoolReuseGuard>,
53}
54
55impl CudaQueuedExecution {
56    /// CUDA execution counters for the enqueued work.
57    pub fn execution(&self) -> CudaExecutionStats {
58        self.execution
59    }
60
61    /// Number of pooled resource buffers held live for the queued work.
62    pub fn resource_count(&self) -> usize {
63        self.resources.len()
64    }
65
66    /// Synchronize the queued work, release its pool hold, and surface any
67    /// completion failure.
68    pub fn finish(mut self) -> Result<CudaExecutionStats, CudaError> {
69        let completion_result = self
70            .pool_reuse_guard
71            .take()
72            .map_or(Ok(()), CudaBufferPoolReuseGuard::synchronize_and_release);
73        self.resources.clear();
74        completion_result?;
75        Ok(self.execution)
76    }
77
78    /// Release deferred pool buffers after the owning context has completed
79    /// this queued work.
80    ///
81    /// # Safety
82    ///
83    /// The owning CUDA context must have completed this queued work. Merely
84    /// ordering a dependent kernel is insufficient because Rust owners could
85    /// otherwise deallocate the pool before either kernel completes.
86    #[doc(hidden)]
87    pub unsafe fn release_pool_reuse_after_completion(&mut self) -> Result<(), CudaError> {
88        self.resources.clear();
89        if let Some(guard) = self.pool_reuse_guard.take() {
90            guard.release()?;
91        }
92        Ok(())
93    }
94}
95
96impl Drop for CudaQueuedExecution {
97    fn drop(&mut self) {
98        let Some(guard) = self.pool_reuse_guard.take() else {
99            return;
100        };
101
102        // Keep resources owned while driver synchronization is attempted. Any
103        // synchronization error leaves completion uncertain, so recycling puts
104        // them behind the permanently retained pool hold.
105        let outcome = guard.synchronize_pool_context();
106        self.resources.clear();
107        if outcome.completion_established() {
108            let _ = guard.release();
109        } else {
110            guard.abandon();
111        }
112    }
113}
114
115impl CudaKernelOutput {
116    /// Device buffer produced by the kernel.
117    pub fn buffer(&self) -> &CudaDeviceBuffer {
118        &self.buffer
119    }
120
121    /// CUDA execution counters for the kernel.
122    pub fn execution(&self) -> CudaExecutionStats {
123        self.execution
124    }
125
126    /// Split output into device buffer and execution metadata.
127    pub fn into_parts(self) -> (CudaDeviceBuffer, CudaExecutionStats) {
128        (self.buffer, self.execution)
129    }
130}
131
132impl CudaKernelBatchOutput {
133    /// Device buffers produced by the batched kernel.
134    pub fn outputs(&self) -> &[CudaDeviceBuffer] {
135        &self.outputs
136    }
137
138    /// CUDA execution counters for the batched kernel.
139    pub fn execution(&self) -> CudaExecutionStats {
140        self.execution
141    }
142
143    /// Split output into device buffers and execution metadata.
144    pub fn into_parts(self) -> (Vec<CudaDeviceBuffer>, CudaExecutionStats) {
145        (self.outputs, self.execution)
146    }
147}
148
149impl CudaKernelContiguousBatchOutput {
150    /// Contiguous device buffer produced by the batched kernel.
151    pub fn output(&self) -> &CudaDeviceBuffer {
152        &self.output
153    }
154
155    /// Per-item byte ranges inside the contiguous output buffer.
156    pub fn ranges(&self) -> &[CudaDeviceBufferRange] {
157        &self.ranges
158    }
159
160    /// CUDA execution counters for the batched kernel.
161    pub fn execution(&self) -> CudaExecutionStats {
162        self.execution
163    }
164
165    /// Split output into the contiguous buffer, per-item ranges, and execution metadata.
166    pub fn into_parts(
167        self,
168    ) -> (
169        CudaDeviceBuffer,
170        Vec<CudaDeviceBufferRange>,
171        CudaExecutionStats,
172    ) {
173        (self.output, self.ranges, self.execution)
174    }
175}
176
177impl CudaPooledKernelOutput {
178    /// Device buffer produced by the kernel.
179    pub fn buffer(&self) -> Option<&CudaDeviceBuffer> {
180        self.buffer.as_device_buffer()
181    }
182
183    /// CUDA execution counters for the kernel.
184    pub fn execution(&self) -> CudaExecutionStats {
185        self.execution
186    }
187
188    /// Split output into pooled device buffer and execution metadata.
189    pub fn into_parts(self) -> (CudaPooledDeviceBuffer, CudaExecutionStats) {
190        (self.buffer, self.execution)
191    }
192}
193
194/// CUDA execution counters exposed for dispatch observability.
195#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
196pub struct CudaExecutionStats {
197    pub(crate) kernel_dispatches: usize,
198    pub(crate) copy_kernel_dispatches: usize,
199    pub(crate) decode_kernel_dispatches: usize,
200    pub(crate) hardware_decode: bool,
201}
202
203impl CudaExecutionStats {
204    /// Total kernel dispatch count.
205    pub fn kernel_dispatches(self) -> usize {
206        self.kernel_dispatches
207    }
208
209    /// Copy-kernel dispatch count.
210    pub fn copy_kernel_dispatches(self) -> usize {
211        self.copy_kernel_dispatches
212    }
213
214    /// Hardware decode dispatch count.
215    pub fn decode_kernel_dispatches(self) -> usize {
216        self.decode_kernel_dispatches
217    }
218
219    /// True when a hardware decode path was used.
220    pub fn used_hardware_decode(self) -> bool {
221        self.hardware_decode
222    }
223}