naga/back/spv/
helpers.rs

1use alloc::{vec, vec::Vec};
2
3use arrayvec::ArrayVec;
4use spirv::Word;
5
6use crate::{Handle, UniqueArena};
7
8pub(super) fn bytes_to_words(bytes: &[u8]) -> Vec<Word> {
9    bytes
10        .chunks(4)
11        .map(|chars| chars.iter().rev().fold(0u32, |u, c| (u << 8) | *c as u32))
12        .collect()
13}
14
15pub(super) fn string_to_words(input: &str) -> Vec<Word> {
16    let bytes = input.as_bytes();
17
18    str_bytes_to_words(bytes)
19}
20
21pub(super) fn str_bytes_to_words(bytes: &[u8]) -> Vec<Word> {
22    let mut words = bytes_to_words(bytes);
23    if bytes.len().is_multiple_of(4) {
24        // nul-termination
25        words.push(0x0u32);
26    }
27
28    words
29}
30
31/// split a string into chunks and keep utf8 valid
32#[allow(unstable_name_collisions)]
33pub(super) fn string_to_byte_chunks(input: &str, limit: usize) -> Vec<&[u8]> {
34    let mut offset: usize = 0;
35    let mut start: usize = 0;
36    let mut words = vec![];
37    while offset < input.len() {
38        offset = input.floor_char_boundary_polyfill(offset + limit);
39        // Clippy wants us to call as_bytes() first to avoid the UTF-8 check,
40        // but we want to assert the output is valid UTF-8.
41        #[allow(clippy::sliced_string_as_bytes)]
42        words.push(input[start..offset].as_bytes());
43        start = offset;
44    }
45
46    words
47}
48
49pub(super) const fn map_storage_class(space: crate::AddressSpace) -> spirv::StorageClass {
50    match space {
51        crate::AddressSpace::Handle => spirv::StorageClass::UniformConstant,
52        crate::AddressSpace::Function => spirv::StorageClass::Function,
53        crate::AddressSpace::Private => spirv::StorageClass::Private,
54        crate::AddressSpace::Storage { .. } => spirv::StorageClass::StorageBuffer,
55        crate::AddressSpace::Uniform => spirv::StorageClass::Uniform,
56        crate::AddressSpace::WorkGroup => spirv::StorageClass::Workgroup,
57        crate::AddressSpace::Immediate => spirv::StorageClass::PushConstant,
58        crate::AddressSpace::TaskPayload => spirv::StorageClass::TaskPayloadWorkgroupEXT,
59        // We can't require capabilities here but we request capabilities on the ray pipeline stages
60        // and when writing global variables - global variables because we may be writing an
61        // uncompacted module and pipeline stages for all other cases because these can only be
62        //accessed in a ray tracing pipeline stage.
63        crate::AddressSpace::RayPayload => spirv::StorageClass::RayPayloadKHR,
64        crate::AddressSpace::IncomingRayPayload => spirv::StorageClass::IncomingRayPayloadKHR,
65    }
66}
67
68pub(super) fn contains_builtin(
69    binding: Option<&crate::Binding>,
70    ty: Handle<crate::Type>,
71    arena: &UniqueArena<crate::Type>,
72    built_in: crate::BuiltIn,
73) -> bool {
74    if let Some(&crate::Binding::BuiltIn(bi)) = binding {
75        bi == built_in
76    } else if let crate::TypeInner::Struct { ref members, .. } = arena[ty].inner {
77        members
78            .iter()
79            .any(|member| contains_builtin(member.binding.as_ref(), member.ty, arena, built_in))
80    } else {
81        false // unreachable
82    }
83}
84
85impl crate::AddressSpace {
86    pub(super) const fn to_spirv_semantics_and_scope(
87        self,
88    ) -> (spirv::MemorySemantics, spirv::Scope) {
89        match self {
90            Self::Storage { .. } => (spirv::MemorySemantics::empty(), spirv::Scope::Device),
91            Self::WorkGroup => (spirv::MemorySemantics::empty(), spirv::Scope::Workgroup),
92            Self::Uniform => (spirv::MemorySemantics::empty(), spirv::Scope::Device),
93            Self::Handle => (spirv::MemorySemantics::empty(), spirv::Scope::Device),
94            _ => (spirv::MemorySemantics::empty(), spirv::Scope::Invocation),
95        }
96    }
97}
98
99/// Return true if the global requires a type decorated with `Block`.
100///
101/// See [`back::spv::GlobalVariable`] for details.
102///
103/// [`back::spv::GlobalVariable`]: super::GlobalVariable
104pub fn global_needs_wrapper(ir_module: &crate::Module, var: &crate::GlobalVariable) -> bool {
105    match var.space {
106        crate::AddressSpace::Uniform
107        | crate::AddressSpace::Storage { .. }
108        | crate::AddressSpace::Immediate => {}
109        _ => return false,
110    };
111    match ir_module.types[var.ty].inner {
112        crate::TypeInner::Struct {
113            ref members,
114            span: _,
115        } => match members.last() {
116            Some(member) => match ir_module.types[member.ty].inner {
117                // Structs with dynamically sized arrays can't be copied and can't be wrapped.
118                crate::TypeInner::Array {
119                    size: crate::ArraySize::Dynamic,
120                    ..
121                } => false,
122                _ => true,
123            },
124            None => false,
125        },
126        crate::TypeInner::BindingArray { .. } => false,
127        // if it's not a structure or a binding array, let's wrap it to be able to put "Block"
128        _ => true,
129    }
130}
131
132/// Returns true if `pointer` refers to two-row matrix which is a member of a
133/// struct in the [`crate::AddressSpace::Uniform`] address space.
134pub fn is_uniform_matcx2_struct_member_access(
135    ir_function: &crate::Function,
136    fun_info: &crate::valid::FunctionInfo,
137    ir_module: &crate::Module,
138    pointer: Handle<crate::Expression>,
139) -> bool {
140    if let crate::TypeInner::Pointer {
141        base: pointer_base_type,
142        space: crate::AddressSpace::Uniform,
143    } = *fun_info[pointer].ty.inner_with(&ir_module.types)
144    {
145        if let crate::TypeInner::Matrix {
146            rows: crate::VectorSize::Bi,
147            ..
148        } = ir_module.types[pointer_base_type].inner
149        {
150            if let crate::Expression::AccessIndex {
151                base: parent_pointer,
152                ..
153            } = ir_function.expressions[pointer]
154            {
155                if let crate::TypeInner::Pointer {
156                    base: parent_type, ..
157                } = *fun_info[parent_pointer].ty.inner_with(&ir_module.types)
158                {
159                    if let crate::TypeInner::Struct { .. } = ir_module.types[parent_type].inner {
160                        return true;
161                    }
162                }
163            }
164        }
165    }
166
167    false
168}
169
170///HACK: this is taken from std unstable, remove it when std's floor_char_boundary is stable
171/// and available in our msrv.
172trait U8Internal {
173    fn is_utf8_char_boundary_polyfill(&self) -> bool;
174}
175
176impl U8Internal for u8 {
177    fn is_utf8_char_boundary_polyfill(&self) -> bool {
178        // This is bit magic equivalent to: b < 128 || b >= 192
179        (*self as i8) >= -0x40
180    }
181}
182
183trait StrUnstable {
184    fn floor_char_boundary_polyfill(&self, index: usize) -> usize;
185}
186
187impl StrUnstable for str {
188    fn floor_char_boundary_polyfill(&self, index: usize) -> usize {
189        if index >= self.len() {
190            self.len()
191        } else {
192            let lower_bound = index.saturating_sub(3);
193            let new_index = self.as_bytes()[lower_bound..=index]
194                .iter()
195                .rposition(|b| b.is_utf8_char_boundary_polyfill());
196
197            // We know that the character boundary will be within four bytes.
198            lower_bound + new_index.unwrap()
199        }
200    }
201}
202
203pub enum BindingDecorations {
204    BuiltIn(spirv::BuiltIn, ArrayVec<spirv::Decoration, 2>),
205    Location {
206        location: u32,
207        others: ArrayVec<spirv::Decoration, 5>,
208        /// If this is `Some`, use Decoration::Index with blend_src as an operand
209        blend_src: Option<Word>,
210    },
211    None,
212}