From 4053635b88b35daf551a349eab297e54d503bff4 Mon Sep 17 00:00:00 2001 From: davnotdev Date: Mon, 6 Apr 2026 00:57:39 -0700 Subject: [PATCH 1/9] WIP Add initial prune unused tests --- src/lib.rs | 1 + src/pruneunuseddref.rs | 5 +++++ src/test/compile.sh | 2 +- src/test/pruneunuseddref/compile.sh | 6 ++++++ .../pruneunuseddref_nested.frag | 19 ++++++++++++++++++ .../pruneunuseddref_nested.spv | Bin 0 -> 968 bytes .../pruneunuseddref_sampler.frag | 12 +++++++++++ .../pruneunuseddref_sampler.spv | Bin 0 -> 844 bytes .../pruneunuseddref_texture.frag | 11 ++++++++++ .../pruneunuseddref_texture.spv | Bin 0 -> 844 bytes 10 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 src/pruneunuseddref.rs create mode 100755 src/test/pruneunuseddref/compile.sh create mode 100644 src/test/pruneunuseddref/pruneunuseddref_nested.frag create mode 100644 src/test/pruneunuseddref/pruneunuseddref_nested.spv create mode 100644 src/test/pruneunuseddref/pruneunuseddref_sampler.frag create mode 100644 src/test/pruneunuseddref/pruneunuseddref_sampler.spv create mode 100644 src/test/pruneunuseddref/pruneunuseddref_texture.frag create mode 100644 src/test/pruneunuseddref/pruneunuseddref_texture.spv diff --git a/src/lib.rs b/src/lib.rs index 91f6c69..7b8a7f6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,6 +36,7 @@ mod splitdref; mod spv; mod storagecubepatch; mod util; +mod pruneunuseddref; #[cfg(test)] mod test; diff --git a/src/pruneunuseddref.rs b/src/pruneunuseddref.rs new file mode 100644 index 0000000..63a9db0 --- /dev/null +++ b/src/pruneunuseddref.rs @@ -0,0 +1,5 @@ +use super::*; + +pub fn purneunuseddref(in_spv: &[u32]) -> Result, ()> { + todo!() +} 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..ae4ab4f --- /dev/null +++ b/src/test/pruneunuseddref/compile.sh @@ -0,0 +1,6 @@ +set -e + +glslc pruneunuseddref_texture.frag -o pruneunuseddref_texture.spv +glslc pruneunuseddref_sampler.frag -o pruneunuseddref_sampler.spv +glslc pruneunuseddref_nested.frag -o pruneunuseddref_nested.spv + diff --git a/src/test/pruneunuseddref/pruneunuseddref_nested.frag b/src/test/pruneunuseddref/pruneunuseddref_nested.frag new file mode 100644 index 0000000..9d5ceae --- /dev/null +++ b/src/test/pruneunuseddref/pruneunuseddref_nested.frag @@ -0,0 +1,19 @@ +#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) { + return textureProj(sampler2DShadow(used, u_sampler), vec4(0.0, 0.0, 0.0, 0.0)); +} + +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 0000000000000000000000000000000000000000..75e2b46729b5ff8c2fef6b05fc21f23e4b9d608d GIT binary patch literal 968 zcmYk4OH0F05QV2NwQAM+wgpY?Mo@~1Pm1m&n}Vp|PC{ve1ZpUK2>v~Pl^ezLrMZxS z>D)PU=G?iH7V^z`vz#qi#hNy=s^x{4u(>4qotI8)Jn6SCE>B^sSRo<8S+$ZC74eU5 z6jNAJom|2?6{KeNEppKo&F=*Mukvea7dA81(dM>S#yZVN@@=2Yo%`~=kn$y{n?^!w7 zDLMUFbGfG(7SaOyZzwM-(gR-84J(T9RuwVwcAE1(jJwtq-Ca(fnmgAu+lF#@JL=z3 zgwMA*)-UI6Id-DSsqe_avd%2~!kCvHW?@!Oj=9|J=}O;*J#i1lu6~4+6yc)9cSY}3 zD4Gp9{}E4quMX;9zk1D$98Gv|Fm`q=9&_+NLVsEDPWtFpQkL%!Y&bqO) zjC1Cl_srKZn>pDw+p@A%?8stiSVo8mD>&pRW4eX%pJj_v4+&*V!>;`h}8fHy7GBjM=6rbg^p8-hAxm9yC;4^!Kljw@o_u(1-QSmm&pVvzm-B%XInyM> zTT-yJCbiiOa;4W{FXTb^XZ;At%fe+Jye;-tK6CC$nGv7)>QZKT)-8H$O5uP%2&1>Q olzym{V22xwnvY`9b4Qxsa9@OTzbhp#adO?MQ~Cn`)t0WMe}HE=-v9sr literal 0 HcmV?d00001 diff --git a/src/test/pruneunuseddref/pruneunuseddref_texture.frag b/src/test/pruneunuseddref/pruneunuseddref_texture.frag new file mode 100644 index 0000000..0e53004 --- /dev/null +++ b/src/test/pruneunuseddref/pruneunuseddref_texture.frag @@ -0,0 +1,11 @@ +#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; + +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_texture.spv b/src/test/pruneunuseddref/pruneunuseddref_texture.spv new file mode 100644 index 0000000000000000000000000000000000000000..fd0eebd5c4d382aac988dc197848d551e30cb1a3 GIT binary patch literal 844 zcmYk4O-llC5XL8U%`{E3G_`})v5*cGLDVhwB1i?@Vi9+V1r>2M==azad4F}CjLbu^6D{_y(d3P#QHF%iyzIaZKG=aX)1jU*c0QD{0)we|i;7CztMLjUoPrO2f=A>WdM zWi_3V+aS047Tkq82*0c!A&x9u?!mXkJ}73-eJMK=ko%9bZ&N#jR literal 0 HcmV?d00001 From b82a50f2f3baba2e43ab9af30960c5739673b564 Mon Sep 17 00:00:00 2001 From: davnotdev Date: Mon, 6 Apr 2026 10:50:46 -0700 Subject: [PATCH 2/9] WIP Implement finding unused images and sampler --- src/bin/spv_webgpu_transform.rs | 3 +- src/lib.rs | 3 +- src/pruneunuseddref.rs | 141 ++++++++++++++++++ src/test/pruneunuseddref/compile.sh | 3 +- src/test/pruneunuseddref/pruneunuseddref.frag | 12 ++ ...eddref_texture.spv => pruneunuseddref.spv} | Bin 844 -> 928 bytes .../pruneunuseddref_sampler.frag | 12 -- .../pruneunuseddref_sampler.spv | Bin 844 -> 0 bytes .../pruneunuseddref_texture.frag | 11 -- 9 files changed, 158 insertions(+), 27 deletions(-) create mode 100644 src/test/pruneunuseddref/pruneunuseddref.frag rename src/test/pruneunuseddref/{pruneunuseddref_texture.spv => pruneunuseddref.spv} (52%) delete mode 100644 src/test/pruneunuseddref/pruneunuseddref_sampler.frag delete mode 100644 src/test/pruneunuseddref/pruneunuseddref_sampler.spv delete mode 100644 src/test/pruneunuseddref/pruneunuseddref_texture.frag diff --git a/src/bin/spv_webgpu_transform.rs b/src/bin/spv_webgpu_transform.rs index c00efcb..1f7975f 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::purneunuseddref(&spv).unwrap(), mode => { eprintln!("unknown mode {:?}", mode); process::exit(1) diff --git a/src/lib.rs b/src/lib.rs index 7b8a7f6..92ade24 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,12 +31,12 @@ use std::collections::{HashMap, HashSet}; mod correction; mod isnanisinfpatch; mod mirrorpatch; +mod pruneunuseddref; mod splitcombined; mod splitdref; mod spv; mod storagecubepatch; mod util; -mod pruneunuseddref; #[cfg(test)] mod test; @@ -47,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 index 63a9db0..549a100 100644 --- a/src/pruneunuseddref.rs +++ b/src/pruneunuseddref.rs @@ -1,5 +1,146 @@ use super::*; pub fn purneunuseddref(in_spv: &[u32]) -> Result, ()> { + let spv = in_spv.to_owned(); + + let mut 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 mut instruction_inserts = vec![]; + // let word_inserts = vec![]; + + 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_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_type_image_id_map = HashSet::new(); + let mut op_type_sampler_id_map = HashSet::new(); + let mut op_sampled_image_ids_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_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_TYPE_IMAGE => { + let result_id = spv[spv_idx + 1]; + op_type_image_id_map.insert(result_id); + } + 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_ids_map.insert(image_id); + op_sampled_image_ids_map.insert(sampler_id); + } + _ => {} + } + + spv_idx += word_count as usize; + } + + // 2. Find all OpTypePointer to OpTypeImage and OpTypeSampler + let tp_func = |map: &HashSet, tp_idx| { + let result_id = spv[tp_idx + 1]; + let underlying_type_id = spv[tp_idx + 3]; + map.contains(&underlying_type_id).then_some(result_id) + }; + let image_type_pointers_map = op_type_pointer_idxs + .iter() + .filter_map(|&tp_idx| tp_func(&op_type_image_id_map, tp_idx)) + .collect::>(); + let sampler_type_pointers_map = op_type_pointer_idxs + .iter() + .filter_map(|&tp_idx| tp_func(&op_type_sampler_id_map, tp_idx)) + .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_ids_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); + } + } + + // 7. Remove unused variables + println!("{:?} {:?}", variable_result_map, used_variable_idxs); + + let unused_variable_idxs = variable_result_map + .iter() + .filter_map(|(id, idx)| (!used_variable_idxs.contains(id)).then_some(idx)) + .collect::>(); + + println!("{:?}", unused_variable_idxs); + todo!() } diff --git a/src/test/pruneunuseddref/compile.sh b/src/test/pruneunuseddref/compile.sh index ae4ab4f..5bb6f0a 100755 --- a/src/test/pruneunuseddref/compile.sh +++ b/src/test/pruneunuseddref/compile.sh @@ -1,6 +1,5 @@ set -e -glslc pruneunuseddref_texture.frag -o pruneunuseddref_texture.spv -glslc pruneunuseddref_sampler.frag -o pruneunuseddref_sampler.spv +glslc pruneunuseddref.frag -o pruneunuseddref.spv glslc pruneunuseddref_nested.frag -o pruneunuseddref_nested.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_texture.spv b/src/test/pruneunuseddref/pruneunuseddref.spv similarity index 52% rename from src/test/pruneunuseddref/pruneunuseddref_texture.spv rename to src/test/pruneunuseddref/pruneunuseddref.spv index fd0eebd5c4d382aac988dc197848d551e30cb1a3..e3e4301e9cb3c2c37201a961416e46656cfad3b4 100644 GIT binary patch delta 172 zcmX@Zwt$_NnMs+Qfq{{Mn}L@>b|UXfXLbf@1_p-G_|m-6;?$J*;>6s7oYW$q5Cba% zJA(|eTuEw0Noi3kNY0&s1!x2VgCYZkPA`@0@grfTtHf8awXGc05s|u4gdfE delta 88 zcmZ3$euj;gnMs+Qfq{{Mn}L@>W+LxPc~%B#1_p-G_|m-6;?xu%lfj*V1t`tHpa{fF ilPwwL85t%=GRku@Lgl14FJu&FWaR>Kr6-?dx(onPt_{Zk diff --git a/src/test/pruneunuseddref/pruneunuseddref_sampler.frag b/src/test/pruneunuseddref/pruneunuseddref_sampler.frag deleted file mode 100644 index 0bd71c8..0000000 --- a/src/test/pruneunuseddref/pruneunuseddref_sampler.frag +++ /dev/null @@ -1,12 +0,0 @@ -#version 450 - -layout(set = 0, binding = 0) uniform texture2D u_texture; - -layout(set = 0, binding = 1) uniform sampler u_unused; -layout(set = 0, binding = 2) uniform sampler u_used; - -void main() { - float g0 = textureProj(sampler2DShadow(u_texture, u_used), vec4(0.0, 0.0, 0.0, 0.0)); -} - - diff --git a/src/test/pruneunuseddref/pruneunuseddref_sampler.spv b/src/test/pruneunuseddref/pruneunuseddref_sampler.spv deleted file mode 100644 index 7f7ec49d82bc8f04f28e7c6853273bff1312746d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 844 zcmYk4T}uK{5QZneGR?0nP3=PKvXCw+f~dRL8$l}QE|$L%!Y&bqO) zjC1Cl_srKZn>pDw+p@A%?8stiSVo8mD>&pRW4eX%pJj_v4+&*V!>;`h}8fHy7GBjM=6rbg^p8-hAxm9yC;4^!Kljw@o_u(1-QSmm&pVvzm-B%XInyM> zTT-yJCbiiOa;4W{FXTb^XZ;At%fe+Jye;-tK6CC$nGv7)>QZKT)-8H$O5uP%2&1>Q olzym{V22xwnvY`9b4Qxsa9@OTzbhp#adO?MQ~Cn`)t0WMe}HE=-v9sr diff --git a/src/test/pruneunuseddref/pruneunuseddref_texture.frag b/src/test/pruneunuseddref/pruneunuseddref_texture.frag deleted file mode 100644 index 0e53004..0000000 --- a/src/test/pruneunuseddref/pruneunuseddref_texture.frag +++ /dev/null @@ -1,11 +0,0 @@ -#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; - -void main() { - float g0 = textureProj(sampler2DShadow(u_used, u_sampler), vec4(0.0, 0.0, 0.0, 0.0)); -} - From 02cd95ad12ac93034e8f81a3361f61be5a166ca3 Mon Sep 17 00:00:00 2001 From: davnotdev Date: Mon, 6 Apr 2026 11:34:22 -0700 Subject: [PATCH 3/9] Prune instructions including variable decorations and debug name --- src/pruneunuseddref.rs | 65 +++++++++++++++++++++++++++++++++--------- src/spv.rs | 1 + 2 files changed, 52 insertions(+), 14 deletions(-) diff --git a/src/pruneunuseddref.rs b/src/pruneunuseddref.rs index 549a100..6c9551b 100644 --- a/src/pruneunuseddref.rs +++ b/src/pruneunuseddref.rs @@ -3,16 +3,13 @@ use super::*; pub fn purneunuseddref(in_spv: &[u32]) -> Result, ()> { let spv = in_spv.to_owned(); - let mut instruction_bound = spv[SPV_HEADER_INSTRUCTION_BOUND_OFFSET]; + 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 mut instruction_inserts = vec![]; - // let word_inserts = vec![]; - let spv = spv.into_iter().skip(SPV_HEADER_LENGTH).collect::>(); let mut new_spv = spv.clone(); @@ -22,10 +19,12 @@ pub fn purneunuseddref(in_spv: &[u32]) -> Result, ()> { 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_image_id_map = HashSet::new(); let mut op_type_sampler_id_map = HashSet::new(); - let mut op_sampled_image_ids_map = HashSet::new(); + let mut op_sampled_image_id_map = HashSet::new(); let mut spv_idx = 0; while spv_idx < spv.len() { @@ -39,6 +38,8 @@ pub fn purneunuseddref(in_spv: &[u32]) -> Result, ()> { 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_IMAGE => { let result_id = spv[spv_idx + 1]; @@ -51,8 +52,8 @@ pub fn purneunuseddref(in_spv: &[u32]) -> Result, ()> { SPV_INSTRUCTION_OP_SAMPLED_IMAGE => { let image_id = spv[spv_idx + 3]; let sampler_id = spv[spv_idx + 4]; - op_sampled_image_ids_map.insert(image_id); - op_sampled_image_ids_map.insert(sampler_id); + op_sampled_image_id_map.insert(image_id); + op_sampled_image_id_map.insert(sampler_id); } _ => {} } @@ -92,7 +93,7 @@ pub fn purneunuseddref(in_spv: &[u32]) -> Result, ()> { .iter() .filter(|&idx| { let result_id = spv[idx + 2]; - op_sampled_image_ids_map.contains(&result_id) + op_sampled_image_id_map.contains(&result_id) }) .collect::>(); @@ -132,15 +133,51 @@ pub fn purneunuseddref(in_spv: &[u32]) -> Result, ()> { } } - // 7. Remove unused variables - println!("{:?} {:?}", variable_result_map, used_variable_idxs); - + // 8. Remove unused variables let unused_variable_idxs = variable_result_map .iter() - .filter_map(|(id, idx)| (!used_variable_idxs.contains(id)).then_some(idx)) + .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::>(); - println!("{:?}", unused_variable_idxs); + // 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); - todo!() + // 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; From 3e1735f69ecca77a2b7d9b8dd9c8b95a43f70d44 Mon Sep 17 00:00:00 2001 From: davnotdev Date: Mon, 6 Apr 2026 11:44:01 -0700 Subject: [PATCH 4/9] Implement pruneunuseddref tests --- src/bin/spv_webgpu_transform.rs | 2 +- src/pruneunuseddref.rs | 2 +- src/test.rs | 20 ++++++++++++++++-- .../pruneunuseddref_nested.frag | 6 +++++- .../pruneunuseddref_nested.spv | Bin 968 -> 924 bytes 5 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/bin/spv_webgpu_transform.rs b/src/bin/spv_webgpu_transform.rs index 1f7975f..0f1203a 100644 --- a/src/bin/spv_webgpu_transform.rs +++ b/src/bin/spv_webgpu_transform.rs @@ -30,7 +30,7 @@ fn main() { "storagecube" => { spirv_webgpu_transform::storagecubepatch(&spv, &mut out_correction_map).unwrap() } - "pruneunuseddref" => spirv_webgpu_transform::purneunuseddref(&spv).unwrap(), + "pruneunuseddref" => spirv_webgpu_transform::pruneunuseddref(&spv).unwrap(), mode => { eprintln!("unknown mode {:?}", mode); process::exit(1) diff --git a/src/pruneunuseddref.rs b/src/pruneunuseddref.rs index 6c9551b..fa12e4d 100644 --- a/src/pruneunuseddref.rs +++ b/src/pruneunuseddref.rs @@ -1,6 +1,6 @@ use super::*; -pub fn purneunuseddref(in_spv: &[u32]) -> Result, ()> { +pub fn pruneunuseddref(in_spv: &[u32]) -> Result, ()> { let spv = in_spv.to_owned(); let instruction_bound = spv[SPV_HEADER_INSTRUCTION_BOUND_OFFSET]; diff --git a/src/test.rs b/src/test.rs index e7893cf..1fa03da 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,19 @@ 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 +]; diff --git a/src/test/pruneunuseddref/pruneunuseddref_nested.frag b/src/test/pruneunuseddref/pruneunuseddref_nested.frag index 9d5ceae..02ca20a 100644 --- a/src/test/pruneunuseddref/pruneunuseddref_nested.frag +++ b/src/test/pruneunuseddref/pruneunuseddref_nested.frag @@ -6,7 +6,11 @@ layout(set = 0, binding = 1) uniform texture2D u_unused; layout(set = 0, binding = 2) uniform texture2D u_used; float inner(texture2D used) { - return textureProj(sampler2DShadow(used, u_sampler), vec4(0.0, 0.0, 0.0, 0.0)); + // 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) { diff --git a/src/test/pruneunuseddref/pruneunuseddref_nested.spv b/src/test/pruneunuseddref/pruneunuseddref_nested.spv index 75e2b46729b5ff8c2fef6b05fc21f23e4b9d608d..b9abd2dd6a92bdf3b7479938f64757682bf9cded 100644 GIT binary patch delta 190 zcmW-byAi@L5JVRvP(;Aj1}EfD00qzh5mitGHIPySWjLoFhWC6kZ|-(=T4|{7`stcS zpJ`S}Dzqut(C5&=3GKWE>zvtmzGK!C@3eIH%IKBs=|`~4P#+YpXY;-K+8P$HggdDw Ubf9ng4bQ|L^sOI%QeQI3AMoJ`-T(jq delta 256 zcmYk1%MHRX5JU&ZU~%BUHwlptcPIdfD&S5R+$n-1DS`%arz*r7D@a-CWjC|qk=>?G zdRO65YpR8$35 Date: Mon, 6 Apr 2026 11:50:59 -0700 Subject: [PATCH 5/9] Add pruneunuseddref ffi --- ffi/bin/spv_webgpu_transform.c | 159 +++++++++++++++++---------------- ffi/spirv_webgpu_transform.h | 2 + ffi/src/lib.rs | 28 +++++- 3 files changed, 111 insertions(+), 78 deletions(-) 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, From 1fb5e53eed58854607d5b19c577d21697e110ba3 Mon Sep 17 00:00:00 2001 From: davnotdev Date: Mon, 6 Apr 2026 12:00:22 -0700 Subject: [PATCH 6/9] Update README.md --- README.md | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 9d7c187..97ad962 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,25 @@ void main() { - You *can* nest `imageCube` usages in functions, but this will not translate to WGSL - `imageCubeArray` is not supported +## Unused Image Sampler Prune + +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` | ✅ | ✅ | ✅ | + +### Additional Notes + +- Does not operate on combined image samplers + ## Library Usage Add this to your `Cargo.toml`: From 90c42702543acda0fffec8d9b74bcdab33228bbf Mon Sep 17 00:00:00 2001 From: davnotdev Date: Mon, 6 Apr 2026 12:02:37 -0700 Subject: [PATCH 7/9] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 97ad962..f6f1aea 100644 --- a/README.md +++ b/README.md @@ -160,7 +160,7 @@ void main() { - You *can* nest `imageCube` usages in functions, but this will not translate to WGSL - `imageCubeArray` is not supported -## Unused Image Sampler Prune +## 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`. From 735c3e6066d5bc09fbd9d037dffb99a9af5dc8a2 Mon Sep 17 00:00:00 2001 From: davnotdev Date: Mon, 6 Apr 2026 12:25:32 -0700 Subject: [PATCH 8/9] Pruning should not affect storage textures --- README.md | 12 +++--- src/pruneunuseddref.rs | 35 ++++++++++++------ src/test.rs | 6 +++ src/test/pruneunuseddref/compile.sh | 1 + .../pruneunuseddref_storage.frag | 16 ++++++++ .../pruneunuseddref_storage.spv | Bin 0 -> 1204 bytes 6 files changed, 53 insertions(+), 17 deletions(-) create mode 100644 src/test/pruneunuseddref/pruneunuseddref_storage.frag create mode 100644 src/test/pruneunuseddref/pruneunuseddref_storage.spv diff --git a/README.md b/README.md index f6f1aea..acaef86 100644 --- a/README.md +++ b/README.md @@ -170,14 +170,16 @@ This patch prunes samplers and textures that are unused. ### Tests -| Test | `spirv-val` | Naga | Tint | -| ----------------------------- | ----------- | ------ | ---- | -| `pruneunuseddref.frag` | ✅ | ✅ | ✅ | -| `pruneunuseddref_nested.frag` | ✅ | ✅ | ✅ | +| Test | `spirv-val` | Naga | Tint | +| ------------------------------ | ----------- | ------ | ---- | +| `pruneunuseddref.frag` | ✅ | ✅ | ✅ | +| `pruneunuseddref_nested.frag` | ✅ | ✅ | ✅ | +| `pruneunuseddref_storage.frag` | ✅ | ✅ | ✅ | ### Additional Notes -- Does not operate on combined image samplers +- Does not prune combined image samplers +- Does not prune storage textures since they are separate category of texture ## Library Usage diff --git a/src/pruneunuseddref.rs b/src/pruneunuseddref.rs index fa12e4d..0febd59 100644 --- a/src/pruneunuseddref.rs +++ b/src/pruneunuseddref.rs @@ -15,6 +15,7 @@ pub fn pruneunuseddref(in_spv: &[u32]) -> Result, ()> { // 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![]; @@ -22,7 +23,6 @@ pub fn pruneunuseddref(in_spv: &[u32]) -> Result, ()> { let mut op_decorate_idxs = vec![]; let mut op_name_idxs = vec![]; - let mut op_type_image_id_map = HashSet::new(); let mut op_type_sampler_id_map = HashSet::new(); let mut op_sampled_image_id_map = HashSet::new(); @@ -34,6 +34,7 @@ pub fn pruneunuseddref(in_spv: &[u32]) -> Result, ()> { 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), @@ -41,10 +42,6 @@ pub fn pruneunuseddref(in_spv: &[u32]) -> Result, ()> { SPV_INSTRUCTION_OP_DECORATE => op_decorate_idxs.push(spv_idx), SPV_INSTRUCTION_OP_NAME => op_name_idxs.push(spv_idx), - SPV_INSTRUCTION_OP_TYPE_IMAGE => { - let result_id = spv[spv_idx + 1]; - op_type_image_id_map.insert(result_id); - } SPV_INSTRUCTION_OP_TYPE_SAMPLER => { let result_id = spv[spv_idx + 1]; op_type_sampler_id_map.insert(result_id); @@ -62,18 +59,32 @@ pub fn pruneunuseddref(in_spv: &[u32]) -> Result, ()> { } // 2. Find all OpTypePointer to OpTypeImage and OpTypeSampler - let tp_func = |map: &HashSet, tp_idx| { - let result_id = spv[tp_idx + 1]; - let underlying_type_id = spv[tp_idx + 3]; - map.contains(&underlying_type_id).then_some(result_id) - }; let image_type_pointers_map = op_type_pointer_idxs .iter() - .filter_map(|&tp_idx| tp_func(&op_type_image_id_map, tp_idx)) + .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| tp_func(&op_type_sampler_id_map, tp_idx)) + .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 diff --git a/src/test.rs b/src/test.rs index 1fa03da..19e1f76 100644 --- a/src/test.rs +++ b/src/test.rs @@ -210,3 +210,9 @@ test_with_spv_and_fn_no_correction![ "./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/pruneunuseddref/compile.sh b/src/test/pruneunuseddref/compile.sh index 5bb6f0a..78df48c 100755 --- a/src/test/pruneunuseddref/compile.sh +++ b/src/test/pruneunuseddref/compile.sh @@ -2,4 +2,5 @@ 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_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 0000000000000000000000000000000000000000..59520c689c3d579af9f2b020fce2a7e90a2f8de6 GIT binary patch literal 1204 zcmYk4TTdH7424ZDK%r1B1qu{MxIPr2J`{w6+P7+7BGHS)8(N`csa6V0lx$G@_xM$P zLE?O~Gcb-E+s9|dUVG}by(zN^o3%MRvPu@MCd7nII-7KUcTPrS@8rwrXBhKVcSJbv ztZ5Bd^zJ4vNGvE%tc&Hmr3V)+8qqiQTeZ*rTx6f(q2Byx@G>5 z!JCeFdC~2Udoh8EalNK$OsmEpbw_Si{x}_vvL10$Jra*@?gssAC?9OgVa0u#-QKFM zkSRN;&J~=D=q`5{=gRDFIUcH0v&7eH%V#Pf-X$p^rapP*72?-?-Sx8WbMyt5?}JPH z%5AtF$s^?HInL83`I^rYhy1co@V47Z#aWdS4ww z;9mHc9UvR3I428l-XZ2XZKQSER_X zzJ&a$6f9P1Z3+vu5DBw8tU^1}8Sdiy5Z>2+Ax&Ah?1^`e{i>Mxzm+n3V&=XeWyV)J#q5@)aKL{G zV+Lzd=0vT4eef7He~HB`d7}Y`do7&v8&d8i&t2ctDSd&{2lLyOGCN|<>`CGL@O#?# SSiGmMxSV5V Date: Mon, 6 Apr 2026 18:53:48 -0700 Subject: [PATCH 9/9] Infinite loop bug in function tracing --- src/util/function.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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; }