Skip to main content

spirv_webgpu_transform/
mirrorpatch.rs

1use super::*;
2
3type LeftRightOutput = (Option<Vec<u32>>, Option<Vec<u32>>);
4
5/// Reflect one set of patched uniforms onto another shader with the same underlying set of
6/// uniforms.
7/// This is important for ensuring patched vertex and fragment shaders have the same layout.
8/// This is because some transformations occur based off what instructions appear, so as a result,
9/// the vertex and fragment shader may have a different layout after a set of transformations
10pub fn mirrorpatch(
11    left_spv: &[u32],
12    left_corrections: &mut CorrectionMap,
13    right_spv: &[u32],
14    right_corrections: &mut CorrectionMap,
15) -> Result<LeftRightOutput, ()> {
16    if left_corrections.sets.is_none() && right_corrections.sets.is_none() {
17        return Ok((None, None));
18    }
19
20    let mut left_affected_decorations = vec![];
21    let mut right_affected_decorations = vec![];
22
23    let mut left_instruction_bound = left_spv[SPV_HEADER_INSTRUCTION_BOUND_OFFSET];
24    let mut right_instruction_bound = right_spv[SPV_HEADER_INSTRUCTION_BOUND_OFFSET];
25
26    let left_corrections_map = left_corrections.sets.as_ref().cloned().unwrap_or_default();
27    let right_corrections_map = right_corrections.sets.as_ref().cloned().unwrap_or_default();
28
29    let mut scan_set_idxs = left_corrections_map
30        .keys()
31        .chain(right_corrections_map.keys())
32        .copied()
33        .collect::<Vec<_>>();
34
35    scan_set_idxs.dedup();
36
37    for set_idx in scan_set_idxs {
38        let left_bindings = left_corrections_map
39            .get(&set_idx)
40            .cloned()
41            .map(|v| v.bindings)
42            .unwrap_or_default();
43        let right_bindings = right_corrections_map
44            .get(&set_idx)
45            .cloned()
46            .map(|v| v.bindings)
47            .unwrap_or_default();
48
49        for (left_binding_idx, l) in left_bindings.iter() {
50            let r = right_bindings
51                .get(left_binding_idx)
52                .cloned()
53                .unwrap_or_default();
54
55            push_affected_decorations(
56                &mut right_affected_decorations,
57                &mut right_instruction_bound,
58                set_idx,
59                *left_binding_idx,
60                l,
61                &r,
62            );
63        }
64
65        for (right_binding_idx, r) in right_bindings.iter() {
66            let l = left_bindings
67                .get(right_binding_idx)
68                .cloned()
69                .unwrap_or_default();
70
71            push_affected_decorations(
72                &mut left_affected_decorations,
73                &mut left_instruction_bound,
74                set_idx,
75                *right_binding_idx,
76                r,
77                &l,
78            );
79        }
80    }
81
82    let l = (!left_affected_decorations.is_empty())
83        .then(|| {
84            patch_spv_decorations(
85                left_spv,
86                left_corrections,
87                left_instruction_bound,
88                &left_affected_decorations,
89            )
90        })
91        .transpose()?;
92    let r = (!right_affected_decorations.is_empty())
93        .then(|| {
94            patch_spv_decorations(
95                right_spv,
96                right_corrections,
97                right_instruction_bound,
98                &right_affected_decorations,
99            )
100        })
101        .transpose()?;
102    Ok((l, r))
103}
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq)]
106struct NewVariable {
107    set: u32,
108    binding: u32,
109    new_res_id: u32,
110    correction_type: CorrectionType,
111}
112
113fn patch_spv_decorations(
114    in_spv: &[u32],
115    corrections: &mut CorrectionMap,
116    new_instruction_bound: u32,
117    affected_decorations: &[NewVariable],
118) -> Result<Vec<u32>, ()> {
119    let spv = in_spv.to_owned();
120
121    let instruction_bound = new_instruction_bound;
122    let magic_number = spv[SPV_HEADER_MAGIC_NUM_OFFSET];
123    let spv_header = spv[0..SPV_HEADER_LENGTH].to_owned();
124
125    assert_eq!(magic_number, SPV_HEADER_MAGIC);
126
127    let mut instruction_inserts: Vec<InstructionInsert> = vec![];
128
129    let spv = spv.into_iter().skip(SPV_HEADER_LENGTH).collect::<Vec<_>>();
130    let mut new_spv = spv.clone();
131
132    // 1. Find locations instructions we need
133    let mut op_decorate_idxs = vec![];
134    let mut op_variable_idxs = vec![];
135    let mut spv_idx = 0;
136    while spv_idx < spv.len() {
137        let op = spv[spv_idx];
138        let word_count = hiword(op);
139        let instruction = loword(op);
140
141        if instruction == SPV_INSTRUCTION_OP_DECORATE {
142            op_decorate_idxs.push(spv_idx)
143        }
144        if instruction == SPV_INSTRUCTION_OP_VARIABLE {
145            op_variable_idxs.push(spv_idx)
146        }
147
148        spv_idx += word_count as usize;
149    }
150    let first_op_deocrate_idx = op_decorate_idxs.first().copied();
151
152    // 2. Convert and insert new variables
153    let mut cached_original_variable_idxs = HashMap::new();
154    let affected_decorations = affected_decorations
155        .iter()
156        .map(|affected| {
157            // Given a set binding, find the original variable
158            let NewVariable {
159                set,
160                binding,
161                new_res_id,
162                correction_type,
163            } = *affected;
164            let original_variable_idx =
165                *if let Some(idx) = cached_original_variable_idxs.get(&(set, binding)) {
166                    idx
167                } else {
168                    let Some(original_variable_id) = op_decorate_idxs.iter().find_map(|&d_idx| {
169                        let target_id = spv[d_idx + 1];
170                        let decoration_id = spv[d_idx + 2];
171                        let decoration_value = spv[d_idx + 3];
172                        (decoration_id == SPV_DECORATION_DESCRIPTOR_SET
173                            && decoration_value == set
174                            && op_decorate_idxs.iter().any(|&idx| {
175                                let binding_target_id = spv[idx + 1];
176                                let decoration_id = spv[idx + 2];
177                                let decoration_value = spv[idx + 3];
178                                decoration_id == SPV_DECORATION_BINDING
179                                    && decoration_value == binding
180                                    && target_id == binding_target_id
181                            }))
182                        .then_some(target_id)
183                    }) else {
184                        // If there are no OpDecorates, no patching needs to be done.
185                        return Err(in_spv.to_vec());
186                    };
187                    let idx = op_variable_idxs
188                        .iter()
189                        .find(|&idx| spv[idx + 2] == original_variable_id)
190                        .unwrap();
191                    cached_original_variable_idxs.insert((set, binding), idx);
192                    idx
193                };
194
195            // Copy the original variable instruction and substitute new variable id
196            let original_variable_id = spv[original_variable_idx + 2];
197            let mut new_variable = Vec::new();
198            let word_count = hiword(spv[original_variable_idx]);
199            new_variable.extend_from_slice(
200                &spv[original_variable_idx..original_variable_idx + word_count as usize],
201            );
202            new_variable[2] = new_res_id;
203            instruction_inserts.push(InstructionInsert {
204                previous_spv_idx: original_variable_idx,
205                instruction: new_variable,
206            });
207
208            // Convert into affected decoration
209            Ok(AffectedDecoration {
210                original_res_id: original_variable_id,
211                new_res_ids: vec![new_res_id],
212                correction_type,
213            })
214        })
215        .collect::<Result<Vec<_>, _>>();
216
217    let affected_decorations = match affected_decorations {
218        Ok(d) => d,
219        Err(spv) => return Ok(spv),
220    };
221
222    // 3. Insert new OpDecorate
223    let DecorateOut {
224        descriptor_sets_to_correct,
225    } = util::decorate(DecorateIn {
226        spv: &spv,
227        instruction_inserts: &mut instruction_inserts,
228        first_op_deocrate_idx,
229        op_decorate_idxs: &op_decorate_idxs,
230        affected_decorations: &affected_decorations,
231        corrections,
232    });
233
234    // 4. Insert New Instructions
235    insert_new_instructions(&spv, &mut new_spv, &[], &instruction_inserts);
236
237    // 5. Correct OpDecorate Bindings
238    util::correct_decorate(CorrectDecorateIn {
239        new_spv: &mut new_spv,
240        descriptor_sets_to_correct,
241    });
242
243    // 6. Remove Instructions that have been Whited Out.
244    prune_noops(&mut new_spv);
245
246    // 7. Write New Header and New Code
247    Ok(fuse_final(spv_header, new_spv, instruction_bound))
248}
249
250fn push_affected_decorations(
251    new_variables: &mut Vec<NewVariable>,
252    instruction_bound: &mut u32,
253    set: u32,
254    binding: u32,
255    l: &CorrectionBinding,
256    r: &CorrectionBinding,
257) {
258    let mut ll = l
259        .corrections
260        .iter()
261        .map(Some)
262        .enumerate()
263        .collect::<Vec<_>>();
264
265    for r_correction in r.corrections.iter() {
266        let idx_ty = ll
267            .iter()
268            .find(|(_, correction)| Some(r_correction) == correction.as_ref().copied())
269            .copied();
270        if let Some((idx, _)) = idx_ty {
271            ll[idx].1 = None;
272        }
273    }
274
275    let mut offset = 0;
276    for (_, op) in ll {
277        if let Some(correction) = op {
278            *instruction_bound += 1;
279            let new_res_id = *instruction_bound - 1;
280            new_variables.push(NewVariable {
281                set,
282                binding: binding + offset,
283                new_res_id,
284                correction_type: *correction,
285            });
286        } else {
287            offset += 1;
288        }
289    }
290}
291
292#[test]
293fn test_push_affected_decorations() {
294    let l = CorrectionBinding {
295        corrections: vec![
296            CorrectionType::SplitCombined,
297            CorrectionType::SplitDrefRegular,
298            CorrectionType::SplitDrefRegular,
299            CorrectionType::SplitCombined,
300            CorrectionType::SplitDrefComparison,
301        ],
302    };
303
304    let r = CorrectionBinding {
305        corrections: vec![
306            CorrectionType::SplitDrefRegular,
307            CorrectionType::SplitDrefComparison,
308        ],
309    };
310
311    let mut affected = vec![];
312    push_affected_decorations(&mut affected, &mut 0, 0, 0, &l, &r);
313    assert_eq!(
314        affected,
315        vec![
316            NewVariable {
317                set: 0,
318                binding: 0,
319                new_res_id: 0,
320                correction_type: CorrectionType::SplitCombined,
321            },
322            NewVariable {
323                set: 0,
324                binding: 1,
325                new_res_id: 1,
326                correction_type: CorrectionType::SplitDrefRegular,
327            },
328            NewVariable {
329                set: 0,
330                binding: 1,
331                new_res_id: 2,
332                correction_type: CorrectionType::SplitCombined,
333            },
334        ]
335    );
336
337    let mut affected = vec![];
338    push_affected_decorations(&mut affected, &mut 0, 0, 0, &r, &l);
339    assert_eq!(affected, vec![]);
340}