diff --git a/README.md b/README.md index 9d7c187..acaef86 100644 --- a/README.md +++ b/README.md @@ -13,12 +13,13 @@ This project aims to transform common but unsupported SPIRV shaders into a form At the moment, the following transformations are supported: -| Feature | `spirv-val` | `naga` | `tint` | -| ------------------------- | ----------- | ------ | ------ | -| Combined Image Samplers | ✅ | ✅ | ✅ | -| Mixed Depth / Comparison | ✅ | ⚠️\* | ❌ | -| isnan / isinf Patching | ✅ | ✅ | ✅ | -| Storage Cube Patching | ✅ | ✅ | ✅ | +| Feature | `spirv-val` | `naga` | `tint` | +| --------------------------------- | ----------- | ------ | ------ | +| Combined Image Samplers | ✅ | ✅ | ✅ | +| Mixed Depth / Comparison | ✅ | ⚠️\* | ❌ | +| isnan / isinf Patching | ✅ | ✅ | ✅ | +| Storage Cube Patching | ✅ | ✅ | ✅ | +| Unused Image Sampler Pruning | ✅ | ✅ | ✅ | > \* Simple cases are OK. > With some [special patches](https://github.com/davnotdev/wgpu/tree/trunk-naga-patches), `naga` can process these. @@ -159,6 +160,27 @@ void main() { - You *can* nest `imageCube` usages in functions, but this will not translate to WGSL - `imageCubeArray` is not supported +## Unused Image Sampler Pruning + +In ubershader configurations, it is common to have uniforms that are referenced in some variants but not others. +If you were to declare a `uniform texture2D depth_buffer;` that is unused, the WGSL translation is always `texture_2d`. +However, in shaders that DO reference `depth_buffer`, you likely get a `texture_depth_2d`. +This may cause conflicts in your bind group layouts or errors when creating bind groups. +This patch prunes samplers and textures that are unused. + +### Tests + +| Test | `spirv-val` | Naga | Tint | +| ------------------------------ | ----------- | ------ | ---- | +| `pruneunuseddref.frag` | ✅ | ✅ | ✅ | +| `pruneunuseddref_nested.frag` | ✅ | ✅ | ✅ | +| `pruneunuseddref_storage.frag` | ✅ | ✅ | ✅ | + +### Additional Notes + +- Does not prune combined image samplers +- Does not prune storage textures since they are separate category of texture + ## Library Usage Add this to your `Cargo.toml`: diff --git a/ffi/bin/spv_webgpu_transform.c b/ffi/bin/spv_webgpu_transform.c index 85f0d23..148563c 100644 --- a/ffi/bin/spv_webgpu_transform.c +++ b/ffi/bin/spv_webgpu_transform.c @@ -1,89 +1,94 @@ +#include "../spirv_webgpu_transform.h" +#include #include #include -#include -#include "../spirv_webgpu_transform.h" #define BAD_FILE_PATH "./bad.spv" void print_set_binding(TransformCorrectionMap map, uint32_t set, uint32_t binding); int main() { - // 1. Read the SPIRV file - FILE* file = fopen(BAD_FILE_PATH, "rb"); - fseek(file, 0, SEEK_END); - int spirv_bytes = ftell(file); - fseek(file, 0, SEEK_SET); - uint8_t* spirv = (uint8_t*)malloc(spirv_bytes); - fread(spirv, 1, spirv_bytes, file); - fclose(file); - - // 2. Run the transformations - TransformCorrectionMap correction_map = SPIRV_WEBGPU_TRANSFORM_CORRECTION_MAP_NULL; - - uint32_t* comb_out_spv; - uint32_t comb_out_count; - spirv_webgpu_transform_combimgsampsplitter_alloc((uint32_t*)spirv, spirv_bytes / 4, &comb_out_spv, &comb_out_count, &correction_map); - - uint32_t* dref_out_spv; - uint32_t dref_out_count; - spirv_webgpu_transform_drefsplitter_alloc(comb_out_spv, comb_out_count, &dref_out_spv, &dref_out_count, &correction_map); - - uint32_t* isnanisinf_out_spv; - uint32_t isnanisinf_out_count; - spirv_webgpu_transform_isnanisinfpatch_alloc(dref_out_spv, dref_out_count, &isnanisinf_out_spv, &isnanisinf_out_count); - - uint32_t* storagecube_out_spv; - uint32_t storagecube_out_count; - spirv_webgpu_transform_storagecubepatch_alloc(isnanisinf_out_spv, isnanisinf_out_count, &storagecube_out_spv, &storagecube_out_count, &correction_map); - - // 3. Observe the patched variables - print_set_binding(correction_map, 0, 0); - print_set_binding(correction_map, 0, 1); - print_set_binding(correction_map, 1, 0); - print_set_binding(correction_map, 2, 0); - - // Fluke values should return None - print_set_binding(correction_map, 1, 1); - print_set_binding(correction_map, 3, 0); - - // 4. Free memory - spirv_webgpu_transform_storagecubepatch_free(storagecube_out_spv); - spirv_webgpu_transform_isnanisinfpatch_free(isnanisinf_out_spv); - spirv_webgpu_transform_drefsplitter_free(dref_out_spv); - spirv_webgpu_transform_combimgsampsplitter_free(comb_out_spv); - spirv_webgpu_transform_correction_map_free(correction_map); - free(spirv); + // 1. Read the SPIRV file + FILE *file = fopen(BAD_FILE_PATH, "rb"); + fseek(file, 0, SEEK_END); + int spirv_bytes = ftell(file); + fseek(file, 0, SEEK_SET); + uint8_t *spirv = (uint8_t *)malloc(spirv_bytes); + fread(spirv, 1, spirv_bytes, file); + fclose(file); + + // 2. Run the transformations + TransformCorrectionMap correction_map = SPIRV_WEBGPU_TRANSFORM_CORRECTION_MAP_NULL; + + uint32_t *comb_out_spv; + uint32_t comb_out_count; + spirv_webgpu_transform_combimgsampsplitter_alloc((uint32_t *)spirv, spirv_bytes / 4, &comb_out_spv, &comb_out_count, &correction_map); + + uint32_t *dref_out_spv; + uint32_t dref_out_count; + spirv_webgpu_transform_drefsplitter_alloc(comb_out_spv, comb_out_count, &dref_out_spv, &dref_out_count, &correction_map); + + uint32_t *isnanisinf_out_spv; + uint32_t isnanisinf_out_count; + spirv_webgpu_transform_isnanisinfpatch_alloc(dref_out_spv, dref_out_count, &isnanisinf_out_spv, &isnanisinf_out_count); + + uint32_t *storagecube_out_spv; + uint32_t storagecube_out_count; + spirv_webgpu_transform_storagecubepatch_alloc(isnanisinf_out_spv, isnanisinf_out_count, &storagecube_out_spv, &storagecube_out_count, &correction_map); + + uint32_t *pruneunuseddref_out_spv; + uint32_t pruneunuseddref_out_count; + spirv_webgpu_transform_pruneunuseddref_alloc(storagecube_out_spv, storagecube_out_count, &pruneunuseddref_out_spv, &pruneunuseddref_out_count); + + // 3. Observe the patched variables + print_set_binding(correction_map, 0, 0); + print_set_binding(correction_map, 0, 1); + print_set_binding(correction_map, 1, 0); + print_set_binding(correction_map, 2, 0); + + // Fluke values should return None + print_set_binding(correction_map, 1, 1); + print_set_binding(correction_map, 3, 0); + + // 4. Free memory + spirv_webgpu_transform_pruneunuseddref_free(pruneunuseddref_out_spv); + spirv_webgpu_transform_storagecubepatch_free(storagecube_out_spv); + spirv_webgpu_transform_isnanisinfpatch_free(isnanisinf_out_spv); + spirv_webgpu_transform_drefsplitter_free(dref_out_spv); + spirv_webgpu_transform_combimgsampsplitter_free(comb_out_spv); + spirv_webgpu_transform_correction_map_free(correction_map); + free(spirv); } void print_set_binding(TransformCorrectionMap map, uint32_t set, uint32_t binding) { - uint16_t* corrections; - uint32_t correction_count; - TransformCorrectionStatus status = spirv_webgpu_transform_correction_map_index(map, set, binding, &corrections, &correction_count); - - printf("For set %d, binding %d:\n", set, binding); - - if (status == SPIRV_WEBGPU_TRANSFORM_CORRECTION_STATUS_NONE) { - printf("\tNone\n"); - } else { - printf("\tSome\n"); - } - - printf("\t"); - for (int i = 0; i < correction_count; i++) { - switch (corrections[i]) { - case SPIRV_WEBGPU_TRANSFORM_CORRECTION_TYPE_SPLIT_COMBINED: - printf("SPLIT_COMBINED "); - break; - case SPIRV_WEBGPU_TRANSFORM_CORRECTION_TYPE_SPLIT_DREF_REGULAR: - printf("SPLIT_DREF_REGULAR "); - break; - case SPIRV_WEBGPU_TRANSFORM_CORRECTION_TYPE_SPLIT_DREF_COMPARISON: - printf("SPLIT_DREF_COMPARISON "); - break; - case SPIRV_WEBGPU_TRANSFORM_CORRECTION_TYPE_CONVERT_STORAGE_CUBE: - printf("CONVERT_STORAGE_CUBE "); - break; - } - } - printf("\n"); + uint16_t *corrections; + uint32_t correction_count; + TransformCorrectionStatus status = spirv_webgpu_transform_correction_map_index(map, set, binding, &corrections, &correction_count); + + printf("For set %d, binding %d:\n", set, binding); + + if (status == SPIRV_WEBGPU_TRANSFORM_CORRECTION_STATUS_NONE) { + printf("\tNone\n"); + } else { + printf("\tSome\n"); + } + + printf("\t"); + for (int i = 0; i < correction_count; i++) { + switch (corrections[i]) { + case SPIRV_WEBGPU_TRANSFORM_CORRECTION_TYPE_SPLIT_COMBINED: + printf("SPLIT_COMBINED "); + break; + case SPIRV_WEBGPU_TRANSFORM_CORRECTION_TYPE_SPLIT_DREF_REGULAR: + printf("SPLIT_DREF_REGULAR "); + break; + case SPIRV_WEBGPU_TRANSFORM_CORRECTION_TYPE_SPLIT_DREF_COMPARISON: + printf("SPLIT_DREF_COMPARISON "); + break; + case SPIRV_WEBGPU_TRANSFORM_CORRECTION_TYPE_CONVERT_STORAGE_CUBE: + printf("CONVERT_STORAGE_CUBE "); + break; + } + } + printf("\n"); } diff --git a/ffi/spirv_webgpu_transform.h b/ffi/spirv_webgpu_transform.h index 142200d..533e0b3 100644 --- a/ffi/spirv_webgpu_transform.h +++ b/ffi/spirv_webgpu_transform.h @@ -19,6 +19,8 @@ void spirv_webgpu_transform_isnanisinfpatch_alloc(uint32_t *in_spv, uint32_t in_ void spirv_webgpu_transform_isnanisinfpatch_free(uint32_t *out_spv); void spirv_webgpu_transform_storagecubepatch_alloc(uint32_t *in_spv, uint32_t in_count, uint32_t **out_spv, uint32_t *out_count, TransformCorrectionMap *correction_map); void spirv_webgpu_transform_storagecubepatch_free(uint32_t *out_spv); +void spirv_webgpu_transform_pruneunuseddref_alloc(uint32_t *int_spv, uint32_t in_count, uint32_t **out_spv, uint32_t *out_count); +void spirv_webgpu_transform_pruneunuseddref_free(uint32_t *out_spv); void spirv_webgpu_transform_mirrorpatch_alloc( uint32_t *in_left_spv, uint32_t in_left_count, TransformCorrectionMap *left_corrections, diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 8f8fdc4..ab8fbc2 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -3,7 +3,7 @@ use core::{ffi, ptr, slice}; use spirv_webgpu_transform::{ CorrectionMap, combimgsampsplitter, drefsplitter, isnanisinfpatch, mirrorpatch, - storagecubepatch, + pruneunuseddref, storagecubepatch, }; type TransformCorrectionMap = *mut ffi::c_void; @@ -147,6 +147,32 @@ pub unsafe extern "C" fn spirv_webgpu_transform_storagecubepatch_free(out_spv: * unsafe { drop(Box::from_raw(out_spv)) } } +#[unsafe(no_mangle)] +pub unsafe extern "C" fn spirv_webgpu_transform_pruneunuseddref_alloc( + in_spv: *const u32, + in_count: u32, + out_spv: *mut *const u32, + out_count: *mut u32, +) { + let in_spv = unsafe { slice::from_raw_parts(in_spv, in_count as usize) }; + match pruneunuseddref(in_spv) { + Ok(spv) => unsafe { + *out_count = spv.len() as u32; + let leaked = Box::leak(spv.into_boxed_slice()); + *out_spv = leaked.as_ptr(); + }, + Err(_) => unsafe { + *out_spv = ptr::null(); + *out_count = 0; + }, + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn spirv_webgpu_transform_pruneunuseddref_free(out_spv: *mut u32) { + unsafe { drop(Box::from_raw(out_spv)) } +} + #[unsafe(no_mangle)] pub unsafe extern "C" fn spirv_webgpu_transform_mirrorpatch_alloc( in_left_spv: *const u32, diff --git a/src/bin/spv_webgpu_transform.rs b/src/bin/spv_webgpu_transform.rs index c00efcb..0f1203a 100644 --- a/src/bin/spv_webgpu_transform.rs +++ b/src/bin/spv_webgpu_transform.rs @@ -5,7 +5,7 @@ fn main() { if args.len() != 4 { eprintln!( - "Usage: spv_webgpu_transform " + "Usage: spv_webgpu_transform " ); process::exit(1); } @@ -30,6 +30,7 @@ fn main() { "storagecube" => { spirv_webgpu_transform::storagecubepatch(&spv, &mut out_correction_map).unwrap() } + "pruneunuseddref" => spirv_webgpu_transform::pruneunuseddref(&spv).unwrap(), mode => { eprintln!("unknown mode {:?}", mode); process::exit(1) diff --git a/src/lib.rs b/src/lib.rs index 91f6c69..92ade24 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,6 +31,7 @@ use std::collections::{HashMap, HashSet}; mod correction; mod isnanisinfpatch; mod mirrorpatch; +mod pruneunuseddref; mod splitcombined; mod splitdref; mod spv; @@ -46,6 +47,7 @@ use util::*; pub use correction::*; pub use isnanisinfpatch::*; pub use mirrorpatch::*; +pub use pruneunuseddref::*; pub use splitcombined::*; pub use splitdref::*; pub use storagecubepatch::*; diff --git a/src/pruneunuseddref.rs b/src/pruneunuseddref.rs new file mode 100644 index 0000000..0febd59 --- /dev/null +++ b/src/pruneunuseddref.rs @@ -0,0 +1,194 @@ +use super::*; + +pub fn pruneunuseddref(in_spv: &[u32]) -> Result, ()> { + let spv = in_spv.to_owned(); + + let instruction_bound = spv[SPV_HEADER_INSTRUCTION_BOUND_OFFSET]; + let magic_number = spv[SPV_HEADER_MAGIC_NUM_OFFSET]; + + let spv_header = spv[0..SPV_HEADER_LENGTH].to_owned(); + + assert_eq!(magic_number, SPV_HEADER_MAGIC); + + let spv = spv.into_iter().skip(SPV_HEADER_LENGTH).collect::>(); + let mut new_spv = spv.clone(); + + // 1. Find locations instructions we need + let mut op_type_pointer_idxs = vec![]; + let mut op_type_image_idxs = vec![]; + let mut op_variable_idxs = vec![]; + let mut op_load_idxs = vec![]; + let mut op_function_parameter_idxs = vec![]; + let mut op_function_call_idxs = vec![]; + let mut op_decorate_idxs = vec![]; + let mut op_name_idxs = vec![]; + + let mut op_type_sampler_id_map = HashSet::new(); + let mut op_sampled_image_id_map = HashSet::new(); + + let mut spv_idx = 0; + while spv_idx < spv.len() { + let op = spv[spv_idx]; + let word_count = hiword(op); + let instruction = loword(op); + + match instruction { + SPV_INSTRUCTION_OP_TYPE_POINTER => op_type_pointer_idxs.push(spv_idx), + SPV_INSTRUCTION_OP_TYPE_IMAGE => op_type_image_idxs.push(spv_idx), + SPV_INSTRUCTION_OP_VARIABLE => op_variable_idxs.push(spv_idx), + SPV_INSTRUCTION_OP_LOAD => op_load_idxs.push(spv_idx), + SPV_INSTRUCTION_OP_FUNCTION_PARAMETER => op_function_parameter_idxs.push(spv_idx), + SPV_INSTRUCTION_OP_FUNCTION_CALL => op_function_call_idxs.push(spv_idx), + SPV_INSTRUCTION_OP_DECORATE => op_decorate_idxs.push(spv_idx), + SPV_INSTRUCTION_OP_NAME => op_name_idxs.push(spv_idx), + + SPV_INSTRUCTION_OP_TYPE_SAMPLER => { + let result_id = spv[spv_idx + 1]; + op_type_sampler_id_map.insert(result_id); + } + SPV_INSTRUCTION_OP_SAMPLED_IMAGE => { + let image_id = spv[spv_idx + 3]; + let sampler_id = spv[spv_idx + 4]; + op_sampled_image_id_map.insert(image_id); + op_sampled_image_id_map.insert(sampler_id); + } + _ => {} + } + + spv_idx += word_count as usize; + } + + // 2. Find all OpTypePointer to OpTypeImage and OpTypeSampler + let image_type_pointers_map = op_type_pointer_idxs + .iter() + .filter_map(|&tp_idx| { + let result_id = spv[tp_idx + 1]; + let underlying_type_id = spv[tp_idx + 3]; + op_type_image_idxs + .iter() + .any(|ti_idx| { + let type_id = spv[ti_idx + 1]; + let image_sampled = spv[ti_idx + 7]; + + // `!= 2` filters for storage textures which shouldn't be pruned. + image_sampled != 2 && type_id == underlying_type_id + }) + .then_some(result_id) + }) + .collect::>(); + let sampler_type_pointers_map = op_type_pointer_idxs + .iter() + .filter_map(|&tp_idx| { + let result_id = spv[tp_idx + 1]; + let underlying_type_id = spv[tp_idx + 3]; + op_type_sampler_id_map + .contains(&underlying_type_id) + .then_some(result_id) + }) + .collect::>(); + + // 3. Final all OpVariable to OpTypePointers + let variable_result_map = op_variable_idxs + .iter() + .filter_map(|&idx| { + let tp_id = spv[idx + 1]; + let result_id = spv[idx + 2]; + + (image_type_pointers_map.contains(&tp_id) || sampler_type_pointers_map.contains(&tp_id)) + .then_some((result_id, idx)) + }) + .collect::>(); + + // 4. Find all OpLoad to OpSampledImage + let loaded_idxs = op_load_idxs + .iter() + .filter(|&idx| { + let result_id = spv[idx + 2]; + op_sampled_image_id_map.contains(&result_id) + }) + .collect::>(); + + let mut used_variable_idxs = loaded_idxs + .iter() + .filter_map(|&&load_idx| { + let pointer = spv[load_idx + 3]; + variable_result_map + .contains_key(&pointer) + .then_some(pointer) + }) + .collect::>(); + + // 5. Final all OpFunctionParameter to OpLoad + let function_parameter_idxs = op_function_parameter_idxs.iter().filter(|&fp_idx| { + let result_id = spv[fp_idx + 2]; + op_load_idxs.iter().any(|&l_idx| { + let pointer = spv[l_idx + 3]; + pointer == result_id + }) + }); + + // 6. Trace Variables from OpFunctionParameter + for &fp_idx in function_parameter_idxs { + let entry = get_function_from_parameter(&spv, fp_idx); + let variables = trace_function_argument_to_variables(TraceFunctionArgumentToVariablesIn { + spv: &spv, + op_variable_idxs: &op_variable_idxs, + op_function_parameter_idxs: &op_function_parameter_idxs, + op_function_call_idxs: &op_function_call_idxs, + entry, + traced_function_call_idxs: &mut vec![], + }); + for variable_idx in variables { + let variable_result_id = spv[variable_idx + 2]; + used_variable_idxs.insert(variable_result_id); + } + } + + // 8. Remove unused variables + let unused_variable_idxs = variable_result_map + .iter() + .filter_map(|(id, &idx)| (!used_variable_idxs.contains(id)).then_some(idx)) + .collect::>(); + + // 9. Find OpDecorate / OpName to OpVariable + let unused_decorate_idxs = op_decorate_idxs + .iter() + .filter(|&idx| { + let target = spv[idx + 1]; + unused_variable_idxs.iter().any(|&v_idx| { + let result_id = spv[v_idx + 2]; + target == result_id + }) + }) + .copied() + .collect::>(); + + let unused_name_idxs = op_name_idxs + .iter() + .filter(|&idx| { + let target = spv[idx + 1]; + unused_variable_idxs.iter().any(|&v_idx| { + let result_id = spv[v_idx + 2]; + target == result_id + }) + }) + .copied() + .collect::>(); + + // 9. Remove instructions + for spv_idx in unused_variable_idxs + .into_iter() + .chain(unused_decorate_idxs) + .chain(unused_name_idxs) + { + let op = spv[spv_idx]; + let word_count = hiword(op) as usize; + + new_spv[spv_idx..spv_idx + word_count].fill(encode_word(1, SPV_INSTRUCTION_OP_NOP)); + } + + prune_noops(&mut new_spv); + + // 10. Write New Header and New Code + Ok(fuse_final(spv_header, new_spv, instruction_bound)) +} diff --git a/src/spv.rs b/src/spv.rs index 33b3b0a..216e6a3 100644 --- a/src/spv.rs +++ b/src/spv.rs @@ -4,6 +4,7 @@ pub const SPV_HEADER_MAGIC_NUM_OFFSET: usize = 0; pub const SPV_HEADER_INSTRUCTION_BOUND_OFFSET: usize = 3; pub const SPV_INSTRUCTION_OP_NOP: u16 = 1; +pub const SPV_INSTRUCTION_OP_NAME: u16 = 5; pub const SPV_INSTRUCTION_OP_TYPE_VOID: u16 = 19; pub const SPV_INSTRUCTION_OP_TYPE_BOOL: u16 = 20; pub const SPV_INSTRUCTION_OP_TYPE_INT: u16 = 21; diff --git a/src/test.rs b/src/test.rs index e7893cf..19e1f76 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,6 +1,6 @@ use super::{ - combimgsampsplitter, drefsplitter, isnanisinfpatch, mirrorpatch, storagecubepatch, - u8_slice_to_u32_vec, u32_slice_to_u8_vec, + combimgsampsplitter, drefsplitter, isnanisinfpatch, mirrorpatch, pruneunuseddref, + storagecubepatch, u8_slice_to_u32_vec, u32_slice_to_u8_vec, }; use naga::{back, front, valid}; @@ -194,3 +194,25 @@ test_with_spv_and_fn!( "./test/storagecubepatch/storagecube_immediate.spv", storagecubepatch ); + +// --- + +// TODO: This only tests shader validity, not functionality +test_with_spv_and_fn_no_correction![ + pruneunuseddref_pruneunuseddref, + DO_ALL, + "./test/pruneunuseddref/pruneunuseddref.spv", + pruneunuseddref +]; +test_with_spv_and_fn_no_correction![ + pruneunuseddref_pruneunuseddref_nested, + DO_ALL, + "./test/pruneunuseddref/pruneunuseddref_nested.spv", + pruneunuseddref +]; +test_with_spv_and_fn_no_correction![ + pruneunuseddref_pruneunuseddref_storage, + DO_ALL, + "./test/pruneunuseddref/pruneunuseddref_storage.spv", + pruneunuseddref +]; diff --git a/src/test/compile.sh b/src/test/compile.sh index cb4c812..b622858 100755 --- a/src/test/compile.sh +++ b/src/test/compile.sh @@ -5,4 +5,4 @@ set -e (cd mirrorpatch; ./compile.sh) (cd isnanisinfpatch; ./compile.sh) (cd storagecubepatch; ./compile.sh) - +(cd pruneunuseddref; ./compile.sh) diff --git a/src/test/pruneunuseddref/compile.sh b/src/test/pruneunuseddref/compile.sh new file mode 100755 index 0000000..78df48c --- /dev/null +++ b/src/test/pruneunuseddref/compile.sh @@ -0,0 +1,6 @@ +set -e + +glslc pruneunuseddref.frag -o pruneunuseddref.spv +glslc pruneunuseddref_nested.frag -o pruneunuseddref_nested.spv +glslc pruneunuseddref_storage.frag -o pruneunuseddref_storage.spv + diff --git a/src/test/pruneunuseddref/pruneunuseddref.frag b/src/test/pruneunuseddref/pruneunuseddref.frag new file mode 100644 index 0000000..b3bf8e7 --- /dev/null +++ b/src/test/pruneunuseddref/pruneunuseddref.frag @@ -0,0 +1,12 @@ +#version 450 + +layout(set = 0, binding = 0) uniform sampler u_unused_sampler; +layout(set = 0, binding = 1) uniform sampler u_sampler; + +layout(set = 0, binding = 2) uniform texture2D u_unused_texture; +layout(set = 0, binding = 3) uniform texture2D u_used; + +void main() { + float g0 = textureProj(sampler2DShadow(u_used, u_sampler), vec4(0.0, 0.0, 0.0, 0.0)); +} + diff --git a/src/test/pruneunuseddref/pruneunuseddref.spv b/src/test/pruneunuseddref/pruneunuseddref.spv new file mode 100644 index 0000000..e3e4301 Binary files /dev/null and b/src/test/pruneunuseddref/pruneunuseddref.spv differ diff --git a/src/test/pruneunuseddref/pruneunuseddref_nested.frag b/src/test/pruneunuseddref/pruneunuseddref_nested.frag new file mode 100644 index 0000000..02ca20a --- /dev/null +++ b/src/test/pruneunuseddref/pruneunuseddref_nested.frag @@ -0,0 +1,23 @@ +#version 450 + +layout(set = 0, binding = 0) uniform sampler u_sampler; + +layout(set = 0, binding = 1) uniform texture2D u_unused; +layout(set = 0, binding = 2) uniform texture2D u_used; + +float inner(texture2D used) { + // NOTE: Shader translation currently cannot handle these nested cases. + // return textureProj(sampler2DShadow(used, u_sampler), vec4(0.0, 0.0, 0.0, 0.0)); + // This patch is designed to solve ^^^, but this is equivalent: + return texture(sampler2D(used, u_sampler), vec2(0.0, 0.0)).x; + +} + +float outer(texture2D used) { + return inner(used); +} + +void main() { + float g0 = inner(u_used); +} + diff --git a/src/test/pruneunuseddref/pruneunuseddref_nested.spv b/src/test/pruneunuseddref/pruneunuseddref_nested.spv new file mode 100644 index 0000000..b9abd2d Binary files /dev/null and b/src/test/pruneunuseddref/pruneunuseddref_nested.spv differ diff --git a/src/test/pruneunuseddref/pruneunuseddref_storage.frag b/src/test/pruneunuseddref/pruneunuseddref_storage.frag new file mode 100644 index 0000000..82b3b2d --- /dev/null +++ b/src/test/pruneunuseddref/pruneunuseddref_storage.frag @@ -0,0 +1,16 @@ +#version 450 + +// Only difference here is that storage textures should not be effected. +layout(rgba32f, set = 0, binding = 4) uniform writeonly image2D u_no_effect; + +layout(set = 0, binding = 0) uniform sampler u_unused_sampler; +layout(set = 0, binding = 1) uniform sampler u_sampler; + +layout(set = 0, binding = 2) uniform texture2D u_unused_texture; +layout(set = 0, binding = 3) uniform texture2D u_used; + +void main() { + float g0 = textureProj(sampler2DShadow(u_used, u_sampler), vec4(0.0, 0.0, 0.0, 0.0)); + imageStore(u_no_effect, ivec2(0, 0), vec4(g0, 0.0, 0.0, 0.0)); +} + diff --git a/src/test/pruneunuseddref/pruneunuseddref_storage.spv b/src/test/pruneunuseddref/pruneunuseddref_storage.spv new file mode 100644 index 0000000..59520c6 Binary files /dev/null and b/src/test/pruneunuseddref/pruneunuseddref_storage.spv differ diff --git a/src/util/function.rs b/src/util/function.rs index 4a6b303..2ff6abf 100644 --- a/src/util/function.rs +++ b/src/util/function.rs @@ -16,7 +16,7 @@ pub fn get_function_from_parameter(spv: &[u32], function_parameter_idx: usize) - let word_count = hiword(op); let instruction = loword(op); match instruction { - SPV_INSTRUCTION_OP_FUNCTION_PARAMETER => { + SPV_INSTRUCTION_OP_FUNCTION_PARAMETER if word_count == 3 => { spv_idx -= word_count as usize; param_idx += 1; }