Skip to main content

spirv_webgpu_transform/
storagecubepatch.rs

1use super::*;
2
3fn inc(ib: &mut u32) -> u32 {
4    *ib += 1;
5    *ib - 1
6}
7
8mod image_cube_direction_to_arrayed;
9use image_cube_direction_to_arrayed::*;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12enum ImageOperation<T> {
13    Fetch(T),
14    Read(T),
15    Write(T),
16}
17
18impl<T> ImageOperation<T>
19where
20    T: Clone + Copy,
21{
22    fn get(&self) -> T {
23        match self {
24            ImageOperation::Fetch(v) | ImageOperation::Read(v) | ImageOperation::Write(v) => *v,
25        }
26    }
27
28    fn image_offset(&self) -> usize {
29        match self {
30            ImageOperation::Fetch(_) => 3,
31            ImageOperation::Read(_) => 3,
32            ImageOperation::Write(_) => 1,
33        }
34    }
35
36    fn coordinate_offset(&self) -> usize {
37        match self {
38            ImageOperation::Fetch(_) => 4,
39            ImageOperation::Read(_) => 4,
40            ImageOperation::Write(_) => 2,
41        }
42    }
43}
44
45/// Perform the operation on a `Vec<u32>`.
46/// Use [u8_slice_to_u32_vec] to convert a `&[u8]` into a `Vec<u32>`.
47pub fn storagecubepatch(
48    in_spv: &[u32],
49    corrections: &mut CorrectionMap,
50) -> Result<Vec<u32>, ()> {
51    let spv = in_spv.to_owned();
52
53    let mut instruction_bound = spv[SPV_HEADER_INSTRUCTION_BOUND_OFFSET];
54    let magic_number = spv[SPV_HEADER_MAGIC_NUM_OFFSET];
55
56    let spv_header = spv[0..SPV_HEADER_LENGTH].to_owned();
57
58    assert_eq!(magic_number, SPV_HEADER_MAGIC);
59
60    let mut instruction_inserts = vec![];
61    let word_inserts = vec![];
62
63    let spv = spv.into_iter().skip(SPV_HEADER_LENGTH).collect::<Vec<_>>();
64    let mut new_spv = spv.clone();
65
66    // 1. Find locations instructions we need
67    let mut op_type_image_idxs = vec![];
68    let mut op_type_pointer_idxs = vec![];
69    let mut op_variable_idxs = vec![];
70    let mut op_load_idxs = vec![];
71    let mut op_type_int_idxs = vec![];
72    let mut op_type_bool_idxs = vec![];
73    let mut op_type_vector_idxs = vec![];
74    let mut op_ext_inst_import_idxs = vec![];
75    let mut op_function_parameter_idxs = vec![];
76    let mut op_function_call_idxs = vec![];
77    let mut op_decorate_idxs = vec![];
78
79    let mut image_operation_idxs = vec![];
80
81    let mut spv_idx = 0;
82    while spv_idx < spv.len() {
83        let op = spv[spv_idx];
84        let word_count = hiword(op);
85        let instruction = loword(op);
86
87        match instruction {
88            SPV_INSTRUCTION_OP_TYPE_IMAGE => op_type_image_idxs.push(spv_idx),
89            SPV_INSTRUCTION_OP_TYPE_POINTER => op_type_pointer_idxs.push(spv_idx),
90            SPV_INSTRUCTION_OP_VARIABLE => op_variable_idxs.push(spv_idx),
91            SPV_INSTRUCTION_OP_LOAD => op_load_idxs.push(spv_idx),
92            SPV_INSTRUCTION_OP_TYPE_INT => op_type_int_idxs.push(spv_idx),
93            SPV_INSTRUCTION_OP_TYPE_BOOL => op_type_bool_idxs.push(spv_idx),
94            SPV_INSTRUCTION_OP_TYPE_VECTOR => op_type_vector_idxs.push(spv_idx),
95            SPV_INSTRUCTION_OP_EXT_INST_IMPORT => op_ext_inst_import_idxs.push(spv_idx),
96            SPV_INSTRUCTION_OP_FUNCTION_PARAMETER => op_function_parameter_idxs.push(spv_idx),
97            SPV_INSTRUCTION_OP_FUNCTION_CALL => op_function_call_idxs.push(spv_idx),
98            SPV_INSTRUCTION_OP_DECORATE => op_decorate_idxs.push(spv_idx),
99            SPV_INSTRUCTION_OP_IMAGE_FETCH => {
100                image_operation_idxs.push(ImageOperation::Fetch(spv_idx))
101            }
102            SPV_INSTRUCTION_OP_IMAGE_READ => {
103                image_operation_idxs.push(ImageOperation::Read(spv_idx))
104            }
105            SPV_INSTRUCTION_OP_IMAGE_WRITE => {
106                image_operation_idxs.push(ImageOperation::Write(spv_idx))
107            }
108            _ => {}
109        }
110
111        spv_idx += word_count as usize;
112    }
113
114    if op_type_vector_idxs.is_empty()
115        || op_type_image_idxs.is_empty()
116        || image_operation_idxs.is_empty()
117    {
118        return Ok(in_spv.to_vec());
119    }
120
121    let header_position = last_of_indices!(
122        op_type_int_idxs,
123        op_type_bool_idxs,
124        op_type_vector_idxs,
125        op_type_pointer_idxs
126    );
127
128    // 2. Insert Required Types
129    let mut header_insert = InstructionInsert {
130        previous_spv_idx: header_position.unwrap(),
131        instruction: vec![],
132    };
133
134    let bool_id = ensure_type_bool(
135        &spv,
136        &op_type_bool_idxs,
137        &mut instruction_bound,
138        &mut header_insert.instruction,
139    );
140    let bool_ptr_id = ensure_type_pointer(
141        &spv,
142        &op_type_pointer_idxs,
143        &mut instruction_bound,
144        &mut header_insert.instruction,
145        SPV_STORAGE_CLASS_FUNCTION,
146        bool_id,
147    );
148    let int32_id = ensure_type_int(
149        &spv,
150        &op_type_int_idxs,
151        &mut instruction_bound,
152        &mut header_insert.instruction,
153        32,
154        SPV_SIGNEDNESS_SIGNED,
155    );
156    let int32_ptr_id = ensure_type_pointer(
157        &spv,
158        &op_type_pointer_idxs,
159        &mut instruction_bound,
160        &mut header_insert.instruction,
161        SPV_STORAGE_CLASS_FUNCTION,
162        int32_id,
163    );
164    let v3int32_id = ensure_type_vector(
165        &spv,
166        &op_type_vector_idxs,
167        &mut instruction_bound,
168        &mut header_insert.instruction,
169        int32_id,
170        3,
171    );
172    let v3int32_ptr_id = ensure_type_pointer(
173        &spv,
174        &op_type_pointer_idxs,
175        &mut instruction_bound,
176        &mut header_insert.instruction,
177        SPV_STORAGE_CLASS_FUNCTION,
178        v3int32_id,
179    );
180    let v2int32_id = ensure_type_vector(
181        &spv,
182        &op_type_vector_idxs,
183        &mut instruction_bound,
184        &mut header_insert.instruction,
185        int32_id,
186        2,
187    );
188    let v2int32_ptr_id = ensure_type_pointer(
189        &spv,
190        &op_type_pointer_idxs,
191        &mut instruction_bound,
192        &mut header_insert.instruction,
193        SPV_STORAGE_CLASS_FUNCTION,
194        v2int32_id,
195    );
196    let glsl_std_id = ensure_ext_inst_import(
197        &spv,
198        &op_ext_inst_import_idxs,
199        &mut instruction_bound,
200        &mut header_insert.instruction,
201        |s| s.starts_with("GLSL.std."),
202        "GLSL.std.450",
203    );
204
205    // We only need to validate the bool_id, ptr_int_id
206    let type_inputs = CubeDirectionTypeInputs {
207        int_id: int32_id,
208        v3int_id: v3int32_id,
209        v2int_id: v2int32_id,
210        bool_id,
211        ptr_v3int_id: v3int32_ptr_id,
212        ptr_int_id: int32_ptr_id,
213        ptr_bool_id: bool_ptr_id,
214        ptr_v2int_id: v2int32_ptr_id,
215    };
216    let (function_type_id, mut function_type_spv) =
217        image_cube_direction_to_arrayed_fn_type(&mut instruction_bound, type_inputs);
218    header_insert.instruction.append(&mut function_type_spv);
219
220    // 3. Find / Insert Required Constants
221    let (shared_constants, mut constants_spv) =
222        image_cube_direction_to_arrayed_constants_spv(&mut instruction_bound, int32_id);
223    header_insert.instruction.append(&mut constants_spv);
224
225    // 4. Insert Function Type and Definition
226    let (function_id, function_spv) = image_cube_direction_to_arrayed_spv(
227        &mut instruction_bound,
228        type_inputs,
229        function_type_id,
230        shared_constants,
231        glsl_std_id,
232    );
233    let mut function_definition_words = function_spv;
234
235    // 5. Find OpTypeImage, change Cube -> 2D
236    let type_image_ids = op_type_image_idxs
237        .iter()
238        .filter_map(|idx| {
239            let result_id = spv[idx + 1];
240            let dim = spv[idx + 3];
241            let arrayed = spv[idx + 5];
242            // 0: unknown, 1: sampling, 2: read/write
243            let sampled = spv[idx + 7];
244
245            if dim == SPV_DIMENSION_CUBE && arrayed == 1 && sampled == 2 {
246                panic!("imageCubeArray is not supported");
247            }
248
249            // imageCube => image2DArray
250            if dim == SPV_DIMENSION_CUBE && sampled == 2 {
251                new_spv[idx + 3] = SPV_DIMENSION_2D;
252                new_spv[idx + 5] = 1;
253                Some(result_id)
254            } else {
255                None
256            }
257        })
258        .collect::<Vec<_>>();
259
260    // 6. Find OpTypePointer -> OpVariable / OpFunctionParameter -> OpLoad
261    let type_pointer_ids = op_type_pointer_idxs
262        .iter()
263        .filter_map(|idx| {
264            let result_id = spv[idx + 1];
265            let underlying_type_id = spv[idx + 3];
266
267            type_image_ids
268                .contains(&underlying_type_id)
269                .then_some(result_id)
270        })
271        .collect::<Vec<_>>();
272    let loadable_ids = op_variable_idxs
273        .iter()
274        // Yes, offsets 1 and 2 are identical
275        .chain(op_function_parameter_idxs.iter())
276        .filter_map(|idx| {
277            let result_id = spv[idx + 2];
278            let result_type_id = spv[idx + 1];
279            type_pointer_ids
280                .contains(&result_type_id)
281                .then_some(result_id)
282        })
283        .collect::<Vec<_>>();
284    let loaded_ids = op_load_idxs
285        .iter()
286        .filter_map(|idx| {
287            let result_id = spv[idx + 2];
288            let pointer_id = spv[idx + 3];
289
290            loadable_ids.contains(&pointer_id).then_some(result_id)
291        })
292        .collect::<Vec<_>>();
293
294    // 7. Find and Patch OpImage{Fetch, Read, Write}
295    for operation_with_idx in image_operation_idxs.iter() {
296        let op_idx = operation_with_idx.get();
297        let op_word_count = hiword(spv[op_idx]) as usize;
298        let image_id = spv[op_idx + operation_with_idx.image_offset()];
299        let coord_id = spv[op_idx + operation_with_idx.coordinate_offset()];
300
301        if loaded_ids.contains(&image_id) {
302            // Inject new temp variable to store coordinate
303            // TODO: OPT further reduce the number of temp variables by sharing then within the same functions
304            let temp_id = inc(&mut instruction_bound);
305            instruction_inserts.push(InstructionInsert {
306                previous_spv_idx: get_function_label_index_of_instruction_index(&spv, op_idx),
307                instruction: vec![
308                    encode_word(4, SPV_INSTRUCTION_OP_VARIABLE),
309                    type_inputs.ptr_v3int_id,
310                    temp_id,
311                    SPV_STORAGE_CLASS_FUNCTION,
312                ],
313            });
314            // Store existing coordinate, pass to our function, create new instruction
315            let output_id = inc(&mut instruction_bound);
316            let mut new_instructions = vec![
317                encode_word(3, SPV_INSTRUCTION_OP_STORE),
318                temp_id,
319                coord_id,
320                encode_word(5, SPV_INSTRUCTION_OP_FUNCTION_CALL),
321                type_inputs.v3int_id,
322                output_id,
323                function_id,
324                temp_id,
325            ];
326            let cl = new_instructions.len();
327            new_instructions.extend_from_slice(&spv[op_idx..op_idx + op_word_count]);
328            new_instructions[cl + operation_with_idx.coordinate_offset()] = output_id;
329            instruction_inserts.push(InstructionInsert {
330                previous_spv_idx: op_idx,
331                instruction: new_instructions,
332            });
333
334            new_spv[op_idx..op_idx + op_word_count].fill(encode_word(1, SPV_INSTRUCTION_OP_NOP));
335        }
336    }
337
338    // 8. Fill Correction Map
339    decorate(DecorateIn {
340        spv: &spv,
341        instruction_inserts: &mut vec![],
342        first_op_deocrate_idx: op_decorate_idxs.first().copied(),
343        op_decorate_idxs: &op_decorate_idxs,
344        affected_decorations: &loadable_ids
345            .iter()
346            .map(|id| AffectedDecoration {
347                original_res_id: *id,
348                new_res_ids: vec![*id],
349                correction_type: CorrectionType::ConvertStorageCube,
350            })
351            .collect::<Vec<_>>(),
352        corrections,
353    });
354
355    // 9. Insert New Instructions
356    instruction_inserts.insert(0, header_insert);
357    insert_new_instructions(&spv, &mut new_spv, &word_inserts, &instruction_inserts);
358    new_spv.append(&mut function_definition_words);
359
360    // 10. Remove Instructions that have been Whited Out.
361    prune_noops(&mut new_spv);
362
363    // 11. Write New Header and New Code
364    Ok(fuse_final(spv_header, new_spv, instruction_bound))
365}