naga/back/spv/
helpers.rs

1use alloc::{vec, vec::Vec};
2
3use spirv::Word;
4
5use crate::{Handle, UniqueArena};
6
7pub(super) fn bytes_to_words(bytes: &[u8]) -> Vec<Word> {
8    bytes
9        .chunks(4)
10        .map(|chars| chars.iter().rev().fold(0u32, |u, c| (u << 8) | *c as u32))
11        .collect()
12}
13
14pub(super) fn string_to_words(input: &str) -> Vec<Word> {
15    let bytes = input.as_bytes();
16
17    str_bytes_to_words(bytes)
18}
19
20pub(super) fn str_bytes_to_words(bytes: &[u8]) -> Vec<Word> {
21    let mut words = bytes_to_words(bytes);
22    if bytes.len() % 4 == 0 {
23        // nul-termination
24        words.push(0x0u32);
25    }
26
27    words
28}
29
30/// split a string into chunks and keep utf8 valid
31#[allow(unstable_name_collisions)]
32pub(super) fn string_to_byte_chunks(input: &str, limit: usize) -> Vec<&[u8]> {
33    let mut offset: usize = 0;
34    let mut start: usize = 0;
35    let mut words = vec![];
36    while offset < input.len() {
37        offset = input.floor_char_boundary(offset + limit);
38        // Clippy wants us to call as_bytes() first to avoid the UTF-8 check,
39        // but we want to assert the output is valid UTF-8.
40        #[allow(clippy::sliced_string_as_bytes)]
41        words.push(input[start..offset].as_bytes());
42        start = offset;
43    }
44
45    words
46}
47
48pub(super) const fn map_storage_class(space: crate::AddressSpace) -> spirv::StorageClass {
49    match space {
50        crate::AddressSpace::Handle => spirv::StorageClass::UniformConstant,
51        crate::AddressSpace::Function => spirv::StorageClass::Function,
52        crate::AddressSpace::Private => spirv::StorageClass::Private,
53        crate::AddressSpace::Storage { .. } => spirv::StorageClass::StorageBuffer,
54        crate::AddressSpace::Uniform => spirv::StorageClass::Uniform,
55        crate::AddressSpace::WorkGroup => spirv::StorageClass::Workgroup,
56        crate::AddressSpace::PushConstant => spirv::StorageClass::PushConstant,
57    }
58}
59
60pub(super) fn contains_builtin(
61    binding: Option<&crate::Binding>,
62    ty: Handle<crate::Type>,
63    arena: &UniqueArena<crate::Type>,
64    built_in: crate::BuiltIn,
65) -> bool {
66    if let Some(&crate::Binding::BuiltIn(bi)) = binding {
67        bi == built_in
68    } else if let crate::TypeInner::Struct { ref members, .. } = arena[ty].inner {
69        members
70            .iter()
71            .any(|member| contains_builtin(member.binding.as_ref(), member.ty, arena, built_in))
72    } else {
73        false // unreachable
74    }
75}
76
77impl crate::AddressSpace {
78    pub(super) const fn to_spirv_semantics_and_scope(
79        self,
80    ) -> (spirv::MemorySemantics, spirv::Scope) {
81        match self {
82            Self::Storage { .. } => (spirv::MemorySemantics::UNIFORM_MEMORY, spirv::Scope::Device),
83            Self::WorkGroup => (
84                spirv::MemorySemantics::WORKGROUP_MEMORY,
85                spirv::Scope::Workgroup,
86            ),
87            _ => (spirv::MemorySemantics::empty(), spirv::Scope::Invocation),
88        }
89    }
90}
91
92/// Return true if the global requires a type decorated with `Block`.
93///
94/// See [`back::spv::GlobalVariable`] for details.
95///
96/// [`back::spv::GlobalVariable`]: super::GlobalVariable
97pub fn global_needs_wrapper(ir_module: &crate::Module, var: &crate::GlobalVariable) -> bool {
98    match var.space {
99        crate::AddressSpace::Uniform
100        | crate::AddressSpace::Storage { .. }
101        | crate::AddressSpace::PushConstant => {}
102        _ => return false,
103    };
104    match ir_module.types[var.ty].inner {
105        crate::TypeInner::Struct {
106            ref members,
107            span: _,
108        } => match members.last() {
109            Some(member) => match ir_module.types[member.ty].inner {
110                // Structs with dynamically sized arrays can't be copied and can't be wrapped.
111                crate::TypeInner::Array {
112                    size: crate::ArraySize::Dynamic,
113                    ..
114                } => false,
115                _ => true,
116            },
117            None => false,
118        },
119        crate::TypeInner::BindingArray { .. } => false,
120        // if it's not a structure or a binding array, let's wrap it to be able to put "Block"
121        _ => true,
122    }
123}
124
125///HACK: this is taken from std unstable, remove it when std's floor_char_boundary is stable
126trait U8Internal {
127    fn is_utf8_char_boundary(&self) -> bool;
128}
129
130impl U8Internal for u8 {
131    fn is_utf8_char_boundary(&self) -> bool {
132        // This is bit magic equivalent to: b < 128 || b >= 192
133        (*self as i8) >= -0x40
134    }
135}
136
137trait StrUnstable {
138    fn floor_char_boundary(&self, index: usize) -> usize;
139}
140
141impl StrUnstable for str {
142    fn floor_char_boundary(&self, index: usize) -> usize {
143        if index >= self.len() {
144            self.len()
145        } else {
146            let lower_bound = index.saturating_sub(3);
147            let new_index = self.as_bytes()[lower_bound..=index]
148                .iter()
149                .rposition(|b| b.is_utf8_char_boundary());
150
151            // SAFETY: we know that the character boundary will be within four bytes
152            unsafe { lower_bound + new_index.unwrap_unchecked() }
153        }
154    }
155}