Skip to main content

spirv_webgpu_transform/
immediatespatch.rs

1use super::*;
2
3mod layout;
4mod type_registry;
5
6use layout::*;
7use type_registry::*;
8
9/// Use [u8_slice_to_u32_vec] to convert a `&[u8]` into a `Vec<u32>`.
10pub fn immediatespatch(in_spv: &[u32], corrections: &mut CorrectionMap) -> Result<Vec<u32>, ()> {
11    let spv = in_spv.to_owned();
12
13    let instruction_bound = spv[SPV_HEADER_INSTRUCTION_BOUND_OFFSET];
14    let magic_number = spv[SPV_HEADER_MAGIC_NUM_OFFSET];
15
16    let spv_header = spv[0..SPV_HEADER_LENGTH].to_owned();
17
18    assert_eq!(magic_number, SPV_HEADER_MAGIC);
19
20    let mut instruction_inserts = vec![];
21    let word_inserts = vec![];
22
23    let spv = spv.into_iter().skip(SPV_HEADER_LENGTH).collect::<Vec<_>>();
24    let mut new_spv = spv.clone();
25
26    // 1. Find locations of instructions we need
27    let mut op_variable_idxs = vec![];
28    let mut op_type_pointer_idxs = vec![];
29    let mut op_type_struct_idxs = vec![];
30    let mut op_type_array_idxs = vec![];
31    let mut op_type_matrix_idxs = vec![];
32    let mut op_type_vector_idxs = vec![];
33    let mut op_type_float_idxs = vec![];
34    let mut op_type_int_idxs = vec![];
35    let mut op_constant_idxs = vec![];
36    let mut op_decorate_idxs = vec![];
37    let mut op_member_decorate_idxs = vec![];
38
39    let mut spv_idx = 0;
40    while spv_idx < spv.len() {
41        let op = spv[spv_idx];
42        let word_count = hiword(op);
43        let instruction = loword(op);
44
45        match instruction {
46            SPV_INSTRUCTION_OP_VARIABLE => op_variable_idxs.push(spv_idx),
47            SPV_INSTRUCTION_OP_TYPE_POINTER => op_type_pointer_idxs.push(spv_idx),
48            SPV_INSTRUCTION_OP_TYPE_STRUCT => op_type_struct_idxs.push(spv_idx),
49            SPV_INSTRUCTION_OP_TYPE_ARRAY => op_type_array_idxs.push(spv_idx),
50            SPV_INSTRUCTION_OP_TYPE_MATRIX => op_type_matrix_idxs.push(spv_idx),
51            SPV_INSTRUCTION_OP_TYPE_VECTOR => op_type_vector_idxs.push(spv_idx),
52            SPV_INSTRUCTION_OP_TYPE_FLOAT => op_type_float_idxs.push(spv_idx),
53            SPV_INSTRUCTION_OP_TYPE_INT => op_type_int_idxs.push(spv_idx),
54            SPV_INSTRUCTION_OP_CONSTANT => op_constant_idxs.push(spv_idx),
55            SPV_INSTRUCTION_OP_DECORATE => op_decorate_idxs.push(spv_idx),
56            SPV_INSTRUCTION_OP_MEMBER_DECORATE => op_member_decorate_idxs.push(spv_idx),
57            _ => {}
58        }
59
60        spv_idx += word_count as usize;
61    }
62
63    // 2. Find all `OpVariable` that is a `PushConstant`
64    let pc_variables = op_variable_idxs
65        .iter()
66        .filter_map(|&v_idx| {
67            let result_type_id = spv[v_idx + 1];
68            let result_id = spv[v_idx + 2];
69            let storage_class = spv[v_idx + 3];
70            (storage_class == SPV_STORAGE_CLASS_PUSH_CONSTANT).then_some((
71                v_idx,
72                result_type_id,
73                result_id,
74            ))
75        })
76        .collect::<Vec<_>>();
77
78    if pc_variables.is_empty() {
79        return Ok(in_spv.to_vec());
80    }
81
82    // 3. Find underlying type of variables
83    let block_struct_ids = pc_variables
84        .iter()
85        .map(|&(_, ptr_id, _)| {
86            op_type_pointer_idxs
87                .iter()
88                .find_map(|&tp_idx| {
89                    let result_id = spv[tp_idx + 1];
90                    let underlying_type_id = spv[tp_idx + 3];
91                    (result_id == ptr_id).then_some(underlying_type_id)
92                })
93                .expect("OpVariable PushConstant referenced an undefined OpTypePointer")
94        })
95        .collect::<Vec<_>>();
96
97    // 4. Build a registry of every relevant OpType*
98    let type_registry = build_type_registry(BuildTypeRegistryIn {
99        spv: &spv,
100        op_type_float_idxs: &op_type_float_idxs,
101        op_type_int_idxs: &op_type_int_idxs,
102        op_type_vector_idxs: &op_type_vector_idxs,
103        op_type_matrix_idxs: &op_type_matrix_idxs,
104        op_type_array_idxs: &op_type_array_idxs,
105        op_type_struct_idxs: &op_type_struct_idxs,
106        op_constant_idxs: &op_constant_idxs,
107    });
108
109    // 5. Rewrite Offset / ArrayStride / MatrixStride decoration
110    for &block_struct_id in &block_struct_ids {
111        relayout_type_recursive(
112            &spv,
113            &mut new_spv,
114            block_struct_id,
115            &type_registry,
116            &op_decorate_idxs,
117            &op_member_decorate_idxs,
118        );
119    }
120
121    // 6. Correct OpTypePointer and OpVariable PushConstant -> Uniform
122    // TODO: I believe having two of the same OpTypePointer is a validation error
123    for &tp_idx in &op_type_pointer_idxs {
124        let storage_class = spv[tp_idx + 2];
125        if storage_class == SPV_STORAGE_CLASS_PUSH_CONSTANT {
126            new_spv[tp_idx + 2] = SPV_STORAGE_CLASS_UNIFORM;
127        }
128    }
129    for &(v_idx, _, _) in &pc_variables {
130        new_spv[v_idx + 3] = SPV_STORAGE_CLASS_UNIFORM;
131    }
132
133    // 7. Place new uniforms in the set after the last set.
134    let first_op_decorate_idx = op_decorate_idxs.first().copied();
135    // TODO: What if our `immediates_set` value is faulty?
136    let get_max_set = || {
137        op_decorate_idxs
138            .iter()
139            .filter_map(|&d_idx| {
140                let decoration_id = spv[d_idx + 2];
141                let decoration_value = spv[d_idx + 3];
142                (decoration_id == SPV_DECORATION_DESCRIPTOR_SET).then_some(decoration_value)
143            })
144            .max()
145    };
146    let target_set = match (
147        corrections.immediates_set,
148        corrections.immediates_set_mode.unwrap_or_default(),
149    ) {
150        (Some(set), ImmediatesSetMode::MaxUpTo) => {
151            let max_set = get_max_set().unwrap_or(0);
152            if max_set < set { max_set } else { set }
153        }
154        (Some(set), ImmediatesSetMode::MaxPlusOneUpTo) => {
155            let max_set = get_max_set();
156            if max_set < Some(set) {
157                max_set.map(|x| x + 1).unwrap_or(0)
158            } else {
159                set
160            }
161        }
162        (Some(set), ImmediatesSetMode::Absolute) => set,
163        (None, _) => get_max_set().map(|x| x + 1).unwrap_or(0),
164    };
165
166    let set_bindings =
167        decorate_map_set_bindings(&spv, &op_decorate_idxs, &HashSet::from([target_set]));
168    let target_binding = match (
169        corrections.immediates_binding,
170        corrections.immediates_binding_mode,
171    ) {
172        (Some(binding_idx), Some(ImmediatesBindingMode::Absolute)) => binding_idx,
173        _ => set_bindings
174            .get(&target_set)
175            .unwrap()
176            .last()
177            .map(|&(_, b)| b + 1)
178            .unwrap_or(0u32),
179    };
180
181    for (binding_idx, &(_, _, var_id)) in pc_variables.iter().enumerate() {
182        instruction_inserts.push(InstructionInsert {
183            previous_spv_idx: first_op_decorate_idx
184                .expect("Push constant block has no OpDecorate (missing Block decoration?)"),
185            instruction: vec![
186                encode_word(4, SPV_INSTRUCTION_OP_DECORATE),
187                var_id,
188                SPV_DECORATION_DESCRIPTOR_SET,
189                target_set,
190                encode_word(4, SPV_INSTRUCTION_OP_DECORATE),
191                var_id,
192                SPV_DECORATION_BINDING,
193                target_binding + binding_idx as u32,
194            ],
195        });
196    }
197
198    corrections.immediates_set = Some(target_set);
199    corrections.immediates_binding = Some(target_binding);
200
201    // 8. Insert New Instructions
202    insert_new_instructions(&spv, &mut new_spv, &word_inserts, &instruction_inserts);
203
204    // 9. Remove Instructions that have been Whited Out.
205    prune_noops(&mut new_spv);
206
207    // 10. Write New Header and New Code
208    Ok(fuse_final(spv_header, new_spv, instruction_bound))
209}
210
211// Recursively patch Offset / ArrayStride / MatrixStride decorations using our type registry.
212fn relayout_type_recursive(
213    spv: &[u32],
214    new_spv: &mut [u32],
215    type_id: u32,
216    registry: &TypeRegistry,
217    op_decorate_idxs: &[usize],
218    op_member_decorate_idxs: &[usize],
219) {
220    let ty = match registry.get(&type_id) {
221        Some(t) => t,
222        None => return,
223    };
224
225    match &ty.kind {
226        TypeKind::Struct { members } => {
227            let layout = layout_struct(members, LayoutRule::Std140);
228
229            for (i, new_offset) in layout.member_offsets.iter().enumerate() {
230                patch_member_decoration_literal(
231                    spv,
232                    new_spv,
233                    op_member_decorate_idxs,
234                    type_id,
235                    i as u32,
236                    SPV_DECORATION_OFFSET,
237                    *new_offset,
238                );
239            }
240
241            for (i, member) in members.iter().enumerate() {
242                let matrix_kind = match &member.kind {
243                    TypeKind::Matrix { .. } => Some(&member.kind),
244                    TypeKind::Array { element, .. } => match &element.kind {
245                        TypeKind::Matrix { .. } => Some(&element.kind),
246                        _ => None,
247                    },
248                    _ => None,
249                };
250                if let Some(TypeKind::Matrix { column, .. }) = matrix_kind {
251                    let col_count = column_vec_count(column);
252                    let scalar_w = column_scalar_width(column);
253                    let new_stride = matrix_stride(col_count, scalar_w, LayoutRule::Std140);
254                    patch_member_decoration_literal(
255                        spv,
256                        new_spv,
257                        op_member_decorate_idxs,
258                        type_id,
259                        i as u32,
260                        SPV_DECORATION_MATRIX_STRIDE,
261                        new_stride,
262                    );
263                }
264                relayout_type_recursive(
265                    spv,
266                    new_spv,
267                    member.id,
268                    registry,
269                    op_decorate_idxs,
270                    op_member_decorate_idxs,
271                );
272            }
273        }
274
275        TypeKind::Array { element, .. } => {
276            let new_stride = array_stride(&element.kind, LayoutRule::Std140);
277            for &d_idx in op_decorate_idxs {
278                let target_id = spv[d_idx + 1];
279                let decoration_id = spv[d_idx + 2];
280                if target_id == type_id && decoration_id == SPV_DECORATION_ARRAY_STRIDE {
281                    new_spv[d_idx + 3] = new_stride;
282                }
283            }
284            // Ensure arrays of arrays and array of structs are updated.
285            relayout_type_recursive(
286                spv,
287                new_spv,
288                element.id,
289                registry,
290                op_decorate_idxs,
291                op_member_decorate_idxs,
292            );
293        }
294        // No effect from scalars, vectors, and matrices.
295        _ => {}
296    }
297}
298
299fn patch_member_decoration_literal(
300    spv: &[u32],
301    new_spv: &mut [u32],
302    op_member_decorate_idxs: &[usize],
303    target_id: u32,
304    member: u32,
305    decoration: u32,
306    new_value: u32,
307) {
308    for &md_idx in op_member_decorate_idxs {
309        let md_target_id = spv[md_idx + 1];
310        let md_member = spv[md_idx + 2];
311        let md_decoration = spv[md_idx + 3];
312        if md_target_id == target_id && md_member == member && md_decoration == decoration {
313            new_spv[md_idx + 4] = new_value;
314        }
315    }
316}