Skip to main content

j2k_core/
accelerator.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2// j2k-coverage: shared-accelerator-host
3
4use core::mem::{size_of, size_of_val};
5use core::slice;
6
7use crate::{backend::BackendKind, pixel::PixelFormat};
8
9/// Residency of an accelerator-visible surface or buffer.
10#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
11#[non_exhaustive]
12pub enum SurfaceResidency {
13    /// Host memory owned by CPU code.
14    #[default]
15    Host,
16    /// Pixels were produced directly by a CUDA decode path.
17    CudaResidentDecode,
18    /// Pixels were decoded on CPU and uploaded into CUDA memory.
19    CpuStagedCudaUpload,
20    /// Pixels were produced directly by a Metal decode path.
21    MetalResidentDecode,
22    /// Pixels were decoded on CPU and uploaded into Metal memory.
23    CpuStagedMetalUpload,
24    /// Device-local memory owned by a backend.
25    Device(BackendKind),
26    /// Host/device shared memory for the backend.
27    Shared(BackendKind),
28}
29
30impl SurfaceResidency {
31    /// Generic residency for a backend-produced surface.
32    #[must_use]
33    pub const fn for_backend(backend: BackendKind) -> Self {
34        match backend {
35            BackendKind::Cpu => Self::Host,
36            BackendKind::Metal => Self::Device(BackendKind::Metal),
37            BackendKind::Cuda => Self::Device(BackendKind::Cuda),
38        }
39    }
40}
41
42/// Execution counters reported by accelerator sessions and surfaces.
43#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
44pub struct ExecutionStats {
45    /// Number of submitted backend command buffers, streams, or equivalent jobs.
46    pub submissions: u64,
47    /// Number of kernel or shader dispatches.
48    pub kernel_dispatches: u64,
49    /// Bytes uploaded from host to device.
50    pub upload_bytes: u64,
51    /// Bytes read back from device to host.
52    pub readback_bytes: u64,
53    /// Backend-reported execution time in microseconds, when available.
54    pub device_us: u128,
55}
56
57impl ExecutionStats {
58    /// Construct empty execution statistics.
59    #[must_use]
60    pub const fn new() -> Self {
61        Self {
62            submissions: 0,
63            kernel_dispatches: 0,
64            upload_bytes: 0,
65            readback_bytes: 0,
66            device_us: 0,
67        }
68    }
69
70    /// Saturating sum of two execution-stat blocks.
71    #[must_use]
72    pub const fn saturating_add(self, other: Self) -> Self {
73        Self {
74            submissions: self.submissions.saturating_add(other.submissions),
75            kernel_dispatches: self
76                .kernel_dispatches
77                .saturating_add(other.kernel_dispatches),
78            upload_bytes: self.upload_bytes.saturating_add(other.upload_bytes),
79            readback_bytes: self.readback_bytes.saturating_add(other.readback_bytes),
80            device_us: self.device_us.saturating_add(other.device_us),
81        }
82    }
83}
84
85/// Opaque byte range in accelerator-visible memory.
86#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
87pub struct DeviceMemoryRange {
88    /// Backend that owns the range.
89    pub backend: BackendKind,
90    /// Backend-local allocation identifier. Backends define its meaning.
91    pub allocation: u64,
92    /// Byte offset inside the allocation.
93    pub offset: usize,
94    /// Byte length of the range.
95    pub len: usize,
96}
97
98impl DeviceMemoryRange {
99    /// Construct a backend-local memory range.
100    #[must_use]
101    pub const fn new(backend: BackendKind, allocation: u64, offset: usize, len: usize) -> Self {
102        Self {
103            backend,
104            allocation,
105            offset,
106            len,
107        }
108    }
109}
110
111/// Backend-neutral metadata for a decoded accelerator surface.
112#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
113pub struct SurfaceMetadata {
114    /// Backend that owns or produced the surface.
115    pub backend: BackendKind,
116    /// Memory residency of the surface bytes.
117    pub residency: SurfaceResidency,
118    /// Surface dimensions in pixels.
119    pub dimensions: (u32, u32),
120    /// Pixel format stored by the surface.
121    pub pixel_format: PixelFormat,
122    /// Number of bytes between consecutive rows.
123    pub pitch_bytes: usize,
124    /// Byte offset into the backend allocation.
125    pub byte_offset: usize,
126}
127
128impl SurfaceMetadata {
129    /// Construct tight or explicitly pitched surface metadata with no byte offset.
130    #[must_use]
131    pub const fn new(
132        backend: BackendKind,
133        residency: SurfaceResidency,
134        dimensions: (u32, u32),
135        pixel_format: PixelFormat,
136        pitch_bytes: usize,
137    ) -> Self {
138        Self {
139            backend,
140            residency,
141            dimensions,
142            pixel_format,
143            pitch_bytes,
144            byte_offset: 0,
145        }
146    }
147
148    /// Return metadata adjusted to start at `byte_offset` inside an allocation.
149    #[must_use]
150    pub const fn with_byte_offset(mut self, byte_offset: usize) -> Self {
151        self.byte_offset = byte_offset;
152        self
153    }
154
155    /// Number of bytes represented by the surface.
156    #[must_use]
157    pub const fn byte_len(self) -> usize {
158        self.pitch_bytes.saturating_mul(self.dimensions.1 as usize)
159    }
160}
161
162/// Shared session contract for caller-owned accelerator runtime state.
163pub trait AcceleratorSession {
164    /// Backend owned by this session.
165    fn backend_kind(&self) -> BackendKind;
166
167    /// Execution statistics accumulated by this session.
168    fn execution_stats(&self) -> ExecutionStats {
169        ExecutionStats::default()
170    }
171}
172
173/// Marker trait for host-side values whose memory layout is part of a GPU ABI.
174///
175/// # Safety
176/// Implementers must guarantee all of the following:
177///
178/// - `Self` has a stable host/shader layout for every backend that consumes it,
179///   normally through `#[repr(C)]` or an equivalent explicit representation.
180/// - The object representation contains no internal or tail padding. Safe byte
181///   views read every byte, while Rust may leave padding uninitialized. A
182///   compile-time field-offset/end proof or a sound plain-data derive must
183///   enforce this property; size-only tests and comments are insufficient.
184/// - Every field, including explicit reserved fields that occupy ABI gaps, is
185///   initialized before a value is passed to any byte-view method.
186/// - Every possible bit pattern is a valid value. Types with validity
187///   invariants such as `bool`, references, and restricted enums must not
188///   implement this trait.
189pub unsafe trait GpuAbi: Copy + 'static {
190    /// Human-readable ABI name used in layout-test failures.
191    const NAME: &'static str;
192
193    /// View one value as bytes.
194    fn as_bytes(value: &Self) -> &[u8] {
195        // SAFETY: The trait contract requires a padding-free, fully initialized
196        // object representation whose bytes are all valid to read.
197        unsafe { slice::from_raw_parts(core::ptr::from_ref(value).cast::<u8>(), size_of::<Self>()) }
198    }
199
200    /// View a slice of values as bytes.
201    fn slice_as_bytes(values: &[Self]) -> &[u8] {
202        // SAFETY: The trait contract requires each contiguous array element to
203        // have a padding-free, fully initialized object representation.
204        unsafe { slice::from_raw_parts(values.as_ptr().cast::<u8>(), size_of_val(values)) }
205    }
206
207    /// Mutably view a slice of values as bytes.
208    fn slice_as_bytes_mut(values: &mut [Self]) -> &mut [u8] {
209        // SAFETY: In addition to being padding-free, the trait contract requires
210        // every possible bit pattern written through this view to be valid.
211        unsafe { slice::from_raw_parts_mut(values.as_mut_ptr().cast::<u8>(), size_of_val(values)) }
212    }
213}
214
215macro_rules! impl_gpu_abi_primitive {
216    ($($ty:ty),* $(,)?) => {
217        $(
218            // SAFETY: Primitive numeric types are plain data with stable Rust layouts
219            // accepted by the shader ABI helpers as scalar values.
220            #[doc(hidden)]
221            unsafe impl GpuAbi for $ty {
222                const NAME: &'static str = stringify!($ty);
223            }
224        )*
225    };
226}
227
228impl_gpu_abi_primitive!(u8, i8, u16, i16, u32, i32, u64, i64, f32, f64);
229
230// SAFETY: Arrays preserve element order and contain no extra non-element state;
231// the element type supplies the GPU ABI layout contract.
232#[doc(hidden)]
233unsafe impl<T, const N: usize> GpuAbi for [T; N]
234where
235    T: GpuAbi,
236{
237    const NAME: &'static str = "array";
238}
239
240#[cfg(test)]
241mod tests {
242    use super::*;
243
244    #[test]
245    fn surface_metadata_reports_byte_len_and_offset() {
246        let metadata = SurfaceMetadata::new(
247            BackendKind::Metal,
248            SurfaceResidency::MetalResidentDecode,
249            (17, 11),
250            PixelFormat::Rgb8,
251            64,
252        )
253        .with_byte_offset(128);
254
255        assert_eq!(metadata.backend, BackendKind::Metal);
256        assert_eq!(metadata.residency, SurfaceResidency::MetalResidentDecode);
257        assert_eq!(metadata.byte_offset, 128);
258        assert_eq!(metadata.byte_len(), 704);
259    }
260}