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}