spirv_std/
arch.rs

1//! SPIR-V Intrinsics
2//!
3//! This module is intended as a low level abstraction over SPIR-V instructions.
4//! These functions will typically map to a single instruction, and will perform
5//! no additional safety checks beyond type-checking.
6#[cfg(target_arch = "spirv")]
7use crate::integer::Integer;
8use crate::{
9    integer::{SignedInteger, UnsignedInteger},
10    scalar::Scalar,
11    vector::Vector,
12};
13#[cfg(target_arch = "spirv")]
14use core::arch::asm;
15
16mod atomics;
17mod barrier;
18mod demote_to_helper_invocation_ext;
19mod derivative;
20mod mesh_shading;
21mod primitive;
22mod ray_tracing;
23mod subgroup;
24
25pub use atomics::*;
26pub use barrier::*;
27pub use demote_to_helper_invocation_ext::*;
28pub use derivative::*;
29pub use mesh_shading::*;
30pub use primitive::*;
31pub use ray_tracing::*;
32pub use subgroup::*;
33
34/// Result is true if any component of `vector` is true, otherwise result is
35/// false.
36#[spirv_std_macros::gpu_only]
37#[doc(alias = "OpAny")]
38#[inline]
39pub fn any<V: Vector<bool, N>, const N: usize>(vector: V) -> bool {
40    let mut result = false;
41
42    unsafe {
43        asm! {
44            "%bool = OpTypeBool",
45            "%vector = OpLoad _ {vector}",
46            "%result = OpAny %bool %vector",
47            "OpStore {result} %result",
48            vector = in(reg) &vector,
49            result = in(reg) &mut result
50        }
51    }
52
53    result
54}
55
56/// Result is true if all components of `vector` is true, otherwise result is
57/// false.
58#[spirv_std_macros::gpu_only]
59#[doc(alias = "OpAll")]
60#[inline]
61pub fn all<V: Vector<bool, N>, const N: usize>(vector: V) -> bool {
62    let mut result = false;
63
64    unsafe {
65        asm! {
66            "%bool = OpTypeBool",
67            "%vector = OpLoad _ {vector}",
68            "%result = OpAll %bool %vector",
69            "OpStore {result} %result",
70            vector = in(reg) &vector,
71            result = in(reg) &mut result
72        }
73    }
74
75    result
76}
77
78/// Extract a single, dynamically selected, component of a vector.
79///
80/// # Safety
81/// Behavior is undefined if `index`’s value is greater than or equal to the
82/// number of components in `vector`.
83#[spirv_std_macros::gpu_only]
84#[doc(alias = "OpVectorExtractDynamic")]
85#[inline]
86pub unsafe fn vector_extract_dynamic<T: Scalar, const N: usize>(
87    vector: impl Vector<T, N>,
88    index: usize,
89) -> T {
90    unsafe {
91        let mut result = T::default();
92
93        asm! {
94            "%vector = OpLoad _ {vector}",
95            "%element = OpVectorExtractDynamic _ %vector {index}",
96            "OpStore {element} %element",
97            vector = in(reg) &vector,
98            index = in(reg) index,
99            element = in(reg) &mut result
100        }
101
102        result
103    }
104}
105
106/// Make a copy of a vector, with a single, variably selected,
107/// component modified.
108///
109/// # Safety
110/// Behavior is undefined if `index`’s value is greater than or equal to the
111/// number of components in `vector`.
112#[spirv_std_macros::gpu_only]
113#[doc(alias = "OpVectorInsertDynamic")]
114#[inline]
115pub unsafe fn vector_insert_dynamic<T: Scalar, V: Vector<T, N>, const N: usize>(
116    vector: V,
117    index: usize,
118    element: T,
119) -> V {
120    unsafe {
121        let mut result = V::default();
122
123        asm! {
124            "%vector = OpLoad _ {vector}",
125            "%element = OpLoad _ {element}",
126            "%new_vector = OpVectorInsertDynamic _ %vector %element {index}",
127            "OpStore {result} %new_vector",
128            vector = in(reg) &vector,
129            index = in(reg) index,
130            element = in(reg) &element,
131            result = in(reg) &mut result,
132        }
133
134        result
135    }
136}
137
138/// Fragment-shader discard. Equivalvent to `discard()` from GLSL
139///
140/// Ceases all further processing in any invocation that executes it: Only
141/// instructions these invocations executed before [kill] have observable side
142/// effects.
143#[spirv_std_macros::gpu_only]
144#[doc(alias = "OpKill", alias = "discard")]
145#[allow(clippy::empty_loop)]
146pub fn kill() -> ! {
147    unsafe { asm!("OpKill", options(noreturn)) }
148}
149
150/// Read from the shader clock with either the `Subgroup` or `Device` scope.
151///
152/// See:
153/// <https://htmlpreview.github.io/?https://github.com/KhronosGroup/SPIRV-Registry/blob/master/extensions/KHR/SPV_KHR_shader_clock.html>
154#[spirv_std_macros::gpu_only]
155#[doc(alias = "OpReadClockKHR")]
156pub unsafe fn read_clock_khr<const SCOPE: u32>() -> u64 {
157    unsafe {
158        let mut result: u64;
159
160        asm! {
161            "%uint = OpTypeInt 32 0",
162            "%scope = OpConstant %uint {scope}",
163            "{result} = OpReadClockKHR typeof*{result} %scope",
164            result = out(reg) result,
165            scope = const SCOPE,
166        };
167
168        result
169    }
170}
171
172/// Like `read_clock_khr` but returns a vector to avoid requiring the `Int64`
173/// capability. It returns a 'vector of two-components of 32-bit unsigned
174/// integer type with the first component containing the 32 least significant
175/// bits and the second component containing the 32 most significant bits.'
176#[spirv_std_macros::gpu_only]
177#[doc(alias = "OpReadClockKHR")]
178pub unsafe fn read_clock_uvec2_khr<V: Vector<u32, 2>, const SCOPE: u32>() -> V {
179    unsafe {
180        let mut result = V::default();
181
182        asm! {
183            "%uint = OpTypeInt 32 0",
184            "%scope = OpConstant %uint {scope}",
185            "%result = OpReadClockKHR typeof*{result} %scope",
186            "OpStore {result} %result",
187            result = in(reg) &mut result,
188            scope = const SCOPE,
189        };
190
191        result
192    }
193}
194
195#[cfg(target_arch = "spirv")]
196unsafe fn call_glsl_op_with_ints<T: Integer, const OP: u32>(a: T, b: T) -> T {
197    unsafe {
198        let mut result = T::default();
199        asm!(
200            "%glsl = OpExtInstImport \"GLSL.std.450\"",
201            "%a = OpLoad _ {a}",
202            "%b = OpLoad _ {b}",
203            "%result = OpExtInst typeof*{result} %glsl {op} %a %b",
204            "OpStore {result} %result",
205            a = in(reg) &a,
206            b = in(reg) &b,
207            result = in(reg) &mut result,
208            op = const OP
209        );
210        result
211    }
212}
213
214/// Compute the minimum of two unsigned integers via a GLSL extended instruction.
215#[spirv_std_macros::gpu_only]
216pub fn unsigned_min<T: UnsignedInteger>(a: T, b: T) -> T {
217    unsafe { call_glsl_op_with_ints::<_, 38>(a, b) }
218}
219
220/// Compute the maximum of two unsigned integers via a GLSL extended instruction.
221#[spirv_std_macros::gpu_only]
222pub fn unsigned_max<T: UnsignedInteger>(a: T, b: T) -> T {
223    unsafe { call_glsl_op_with_ints::<_, 41>(a, b) }
224}
225
226/// Compute the minimum of two signed integers via a GLSL extended instruction.
227#[spirv_std_macros::gpu_only]
228pub fn signed_min<T: SignedInteger>(a: T, b: T) -> T {
229    unsafe { call_glsl_op_with_ints::<_, 39>(a, b) }
230}
231
232/// Compute the maximum of two signed integers via a GLSL extended instruction.
233#[spirv_std_macros::gpu_only]
234pub fn signed_max<T: SignedInteger>(a: T, b: T) -> T {
235    unsafe { call_glsl_op_with_ints::<_, 42>(a, b) }
236}
237
238/// Index into an array without bounds checking.
239///
240/// The main purpose of this trait is to work around the fact that the regular `get_unchecked*`
241/// methods do not work in in SPIR-V.
242pub trait IndexUnchecked<T> {
243    /// Returns a reference to the element at `index`. The equivalent of `get_unchecked`.
244    ///
245    /// # Safety
246    /// Behavior is undefined if the `index` value is greater than or equal to the length of the array.
247    unsafe fn index_unchecked(&self, index: usize) -> &T;
248    /// Returns a mutable reference to the element at `index`. The equivalent of `get_unchecked_mut`.
249    ///
250    /// # Safety
251    /// Behavior is undefined if the `index` value is greater than or equal to the length of the array.
252    unsafe fn index_unchecked_mut(&mut self, index: usize) -> &mut T;
253}
254
255impl<T> IndexUnchecked<T> for [T] {
256    #[cfg(target_arch = "spirv")]
257    unsafe fn index_unchecked(&self, index: usize) -> &T {
258        unsafe {
259            // FIXME(eddyb) `let mut result = T::default()` uses (for `asm!`), with this.
260            let mut result_slot = core::mem::MaybeUninit::uninit();
261            asm! {
262                "%slice_ptr = OpLoad _ {slice_ptr_ptr}",
263                "%data_ptr = OpCompositeExtract _ %slice_ptr 0",
264                "%result = OpAccessChain _ %data_ptr {index}",
265                "OpStore {result_slot} %result",
266                slice_ptr_ptr = in(reg) &self,
267                index = in(reg) index,
268                result_slot = in(reg) result_slot.as_mut_ptr(),
269            }
270            result_slot.assume_init()
271        }
272    }
273
274    #[cfg(not(target_arch = "spirv"))]
275    unsafe fn index_unchecked(&self, index: usize) -> &T {
276        unsafe { self.get_unchecked(index) }
277    }
278
279    #[cfg(target_arch = "spirv")]
280    unsafe fn index_unchecked_mut(&mut self, index: usize) -> &mut T {
281        unsafe {
282            // FIXME(eddyb) `let mut result = T::default()` uses (for `asm!`), with this.
283            let mut result_slot = core::mem::MaybeUninit::uninit();
284            asm! {
285                "%slice_ptr = OpLoad _ {slice_ptr_ptr}",
286                "%data_ptr = OpCompositeExtract _ %slice_ptr 0",
287                "%result = OpAccessChain _ %data_ptr {index}",
288                "OpStore {result_slot} %result",
289                slice_ptr_ptr = in(reg) &self,
290                index = in(reg) index,
291                result_slot = in(reg) result_slot.as_mut_ptr(),
292            }
293            result_slot.assume_init()
294        }
295    }
296
297    #[cfg(not(target_arch = "spirv"))]
298    unsafe fn index_unchecked_mut(&mut self, index: usize) -> &mut T {
299        unsafe { self.get_unchecked_mut(index) }
300    }
301}
302
303impl<T, const N: usize> IndexUnchecked<T> for [T; N] {
304    #[cfg(target_arch = "spirv")]
305    unsafe fn index_unchecked(&self, index: usize) -> &T {
306        unsafe {
307            // FIXME(eddyb) `let mut result = T::default()` uses (for `asm!`), with this.
308            let mut result_slot = core::mem::MaybeUninit::uninit();
309            asm! {
310                "%result = OpAccessChain _ {array_ptr} {index}",
311                "OpStore {result_slot} %result",
312                array_ptr = in(reg) self,
313                index = in(reg) index,
314                result_slot = in(reg) result_slot.as_mut_ptr(),
315            }
316            result_slot.assume_init()
317        }
318    }
319
320    #[cfg(not(target_arch = "spirv"))]
321    unsafe fn index_unchecked(&self, index: usize) -> &T {
322        unsafe { self.get_unchecked(index) }
323    }
324
325    #[cfg(target_arch = "spirv")]
326    unsafe fn index_unchecked_mut(&mut self, index: usize) -> &mut T {
327        unsafe {
328            // FIXME(eddyb) `let mut result = T::default()` uses (for `asm!`), with this.
329            let mut result_slot = core::mem::MaybeUninit::uninit();
330            asm! {
331                "%result = OpAccessChain _ {array_ptr} {index}",
332                "OpStore {result_slot} %result",
333                array_ptr = in(reg) self,
334                index = in(reg) index,
335                result_slot = in(reg) result_slot.as_mut_ptr(),
336            }
337            result_slot.assume_init()
338        }
339    }
340
341    #[cfg(not(target_arch = "spirv"))]
342    unsafe fn index_unchecked_mut(&mut self, index: usize) -> &mut T {
343        unsafe { self.get_unchecked_mut(index) }
344    }
345}