spirv_std/
byte_addressable_buffer.rs

1//! Container for an untyped blob of data.
2
3use core::mem;
4
5#[spirv(buffer_load_intrinsic)]
6// HACK(eddyb) try to prevent MIR inlining from breaking our intrinsics.
7#[inline(never)]
8#[spirv_std_macros::gpu_only]
9unsafe fn buffer_load_intrinsic<T>(
10    buffer: &[u32],
11    // FIXME(eddyb) should be `usize`.
12    offset: u32,
13) -> T {
14    // NOTE(eddyb) this doesn't work with `rustc_codegen_spirv` and is only here
15    // for explanatory purposes, and to cause some kind of verbose error if
16    // `#[spirv(buffer_load_intrinsic)]` fails to replace calls to this function.
17    buffer
18        .as_ptr()
19        .cast::<u8>()
20        .add(offset as usize)
21        .cast::<T>()
22        .read()
23}
24
25#[spirv(buffer_store_intrinsic)]
26// HACK(eddyb) try to prevent MIR inlining from breaking our intrinsics.
27#[inline(never)]
28#[spirv_std_macros::gpu_only]
29unsafe fn buffer_store_intrinsic<T>(
30    buffer: &mut [u32],
31    // FIXME(eddyb) should be `usize`.
32    offset: u32,
33    value: T,
34) {
35    // NOTE(eddyb) this doesn't work with `rustc_codegen_spirv` and is only here
36    // for explanatory purposes, and to cause some kind of verbose error if
37    // `#[spirv(buffer_store_intrinsic)]` fails to replace calls to this function.
38    buffer
39        .as_mut_ptr()
40        .cast::<u8>()
41        .add(offset as usize)
42        .cast::<T>()
43        .write(value);
44}
45
46/// `ByteAddressableBuffer` is an untyped blob of data, allowing loads and stores of arbitrary
47/// basic data types at arbitrary indicies. However, all data must be aligned to size 4, each
48/// element within the data (e.g. struct fields) must have a size and alignment of a multiple of 4,
49/// and the `byte_index` passed to load and store must be a multiple of 4 (`byte_index` will be
50/// rounded down to the nearest multiple of 4). So, it's not technically a *byte* addressable
51/// buffer, but rather a *word* buffer, but this naming and behavior was inhereted from HLSL (where
52/// it's UB to pass in an index not a multiple of 4).
53#[repr(transparent)]
54pub struct ByteAddressableBuffer<'a> {
55    /// The underlying array of bytes, able to be directly accessed.
56    pub data: &'a mut [u32],
57}
58
59impl<'a> ByteAddressableBuffer<'a> {
60    /// Creates a `ByteAddressableBuffer` from the untyped blob of data.
61    #[inline]
62    pub fn new(data: &'a mut [u32]) -> Self {
63        Self { data }
64    }
65
66    /// Loads an arbitrary type from the buffer. `byte_index` must be a multiple of 4, otherwise,
67    /// it will get silently rounded down to the nearest multiple of 4.
68    ///
69    /// # Safety
70    /// This function allows writing a type to an untyped buffer, then reading a different type
71    /// from the same buffer, allowing all sorts of safety guarantees to be bypassed (effectively a
72    /// transmute)
73    pub unsafe fn load<T>(&self, byte_index: u32) -> T {
74        if byte_index + mem::size_of::<T>() as u32 > self.data.len() as u32 {
75            panic!("Index out of range");
76        }
77        buffer_load_intrinsic(self.data, byte_index)
78    }
79
80    /// Loads an arbitrary type from the buffer. `byte_index` must be a multiple of 4, otherwise,
81    /// it will get silently rounded down to the nearest multiple of 4. Bounds checking is not
82    /// performed.
83    ///
84    /// # Safety
85    /// This function allows writing a type to an untyped buffer, then reading a different type
86    /// from the same buffer, allowing all sorts of safety guarantees to be bypassed (effectively a
87    /// transmute). Additionally, bounds checking is not performed.
88    pub unsafe fn load_unchecked<T>(&self, byte_index: u32) -> T {
89        buffer_load_intrinsic(self.data, byte_index)
90    }
91
92    /// Stores an arbitrary type int the buffer. `byte_index` must be a multiple of 4, otherwise,
93    /// it will get silently rounded down to the nearest multiple of 4.
94    ///
95    /// # Safety
96    /// This function allows writing a type to an untyped buffer, then reading a different type
97    /// from the same buffer, allowing all sorts of safety guarantees to be bypassed (effectively a
98    /// transmute)
99    pub unsafe fn store<T>(&mut self, byte_index: u32, value: T) {
100        if byte_index + mem::size_of::<T>() as u32 > self.data.len() as u32 {
101            panic!("Index out of range");
102        }
103        buffer_store_intrinsic(self.data, byte_index, value);
104    }
105
106    /// Stores an arbitrary type int the buffer. `byte_index` must be a multiple of 4, otherwise,
107    /// it will get silently rounded down to the nearest multiple of 4. Bounds checking is not
108    /// performed.
109    ///
110    /// # Safety
111    /// This function allows writing a type to an untyped buffer, then reading a different type
112    /// from the same buffer, allowing all sorts of safety guarantees to be bypassed (effectively a
113    /// transmute). Additionally, bounds checking is not performed.
114    pub unsafe fn store_unchecked<T>(&mut self, byte_index: u32, value: T) {
115        buffer_store_intrinsic(self.data, byte_index, value);
116    }
117}