diff --git a/Cargo.lock b/Cargo.lock index 32cabf4..b57b54c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,20 +8,26 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + [[package]] name = "bit-set" -version = "0.8.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +checksum = "34ddef2995421ab6a5c779542c81ee77c115206f4ad9d5a8e05f4ff49716a3dd" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" -version = "0.8.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" [[package]] name = "bitflags" @@ -49,20 +55,25 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" -version = "0.1.1" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "codespan-reporting" -version = "0.11.1" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" dependencies = [ - "termcolor", "unicode-width", ] +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "equivalent" version = "1.0.2" @@ -77,9 +88,21 @@ checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" [[package]] name = "fixedbitset" -version = "0.4.2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] name = "getrandom" @@ -93,11 +116,35 @@ dependencies = [ "wasip2", ] +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "num-traits", + "zerocopy", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + [[package]] name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "foldhash 0.2.0", +] [[package]] name = "indexmap" @@ -106,7 +153,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.16.1", ] [[package]] @@ -125,39 +172,67 @@ version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + [[package]] name = "log" -version = "0.4.28" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" [[package]] name = "naga" -version = "23.1.0" +version = "29.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "364f94bc34f61332abebe8cad6f6cd82a5b65cff22c828d05d0968911462ca4f" +checksum = "0dd91265cc2454558f659b3b4b9640f0ddb8cc6521277f166b8a8c181c898079" dependencies = [ "arrayvec", "bit-set", "bitflags", + "cfg-if", "cfg_aliases", "codespan-reporting", + "half", + "hashbrown 0.16.1", "indexmap", + "libm", "log", + "num-traits", + "once_cell", "petgraph", "rustc-hash", "spirv", - "termcolor", "thiserror", ] +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + [[package]] name = "petgraph" -version = "0.6.5" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" dependencies = [ "fixedbitset", + "hashbrown 0.15.5", "indexmap", ] @@ -199,9 +274,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "spirv" -version = "0.3.0+sdk-1.3.268.0" +version = "0.4.0+sdk-1.4.341.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" +checksum = "d9571ea910ebd84c86af4b3ed27f9dbdc6ad06f17c5f96146b2b671e2976744f" dependencies = [ "bitflags", ] @@ -250,29 +325,20 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - [[package]] name = "thiserror" -version = "1.0.69" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.69" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -301,31 +367,27 @@ dependencies = [ ] [[package]] -name = "winapi-util" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" -dependencies = [ - "windows-sys", -] - -[[package]] -name = "windows-link" -version = "0.2.1" +name = "wit-bindgen" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] -name = "windows-sys" -version = "0.61.2" +name = "zerocopy" +version = "0.8.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +checksum = "bce33a6288fa3f072a8c2c7d0f2fdbb90e28298f0135c1f99b96c3db2efcc60b" dependencies = [ - "windows-link", + "zerocopy-derive", ] [[package]] -name = "wit-bindgen" -version = "0.46.0" +name = "zerocopy-derive" +version = "0.8.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "8fd425244944f4ab65ccff928e7323354c5a018c75838362fdce749dfad2ee1e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 16f794c..e4582f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,6 @@ keywords = ["gamedev", "graphics"] categories = ["graphics"] [dev-dependencies] -naga = { version = "23", features = ["spv-in", "wgsl-out"]} +naga = { version = "29", features = ["spv-in", "wgsl-out"]} spirv-tools = "0.13" diff --git a/README.md b/README.md index acaef86..229883d 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ When porting native games to the web using WebGPU, it becomes neccessary to tran Unfortunately, the WGSL specification lacks support for many features that have shader programmers have become accustomed to. This project aims to transform common but unsupported SPIRV shaders into a form that `naga` and `tint` can transpile. +This also includes an interface for some dodging unsupported features or features that may not run on older browser versions. + ## Feature Summary At the moment, the following transformations are supported: @@ -16,12 +18,15 @@ At the moment, the following transformations are supported: | Feature | `spirv-val` | `naga` | `tint` | | --------------------------------- | ----------- | ------ | ------ | | Combined Image Samplers | ✅ | ✅ | ✅ | +| Immediates (Push Constants) | ✅ | ✅\* | ✅ | | Mixed Depth / Comparison | ✅ | ⚠️\* | ❌ | | isnan / isinf Patching | ✅ | ✅ | ✅ | | Storage Cube Patching | ✅ | ✅ | ✅ | | Unused Image Sampler Pruning | ✅ | ✅ | ✅ | -> \* Simple cases are OK. +> (1)\* 99% OK, just one very specific padding related `naga` bug. + +> (2)\* Simple cases are OK. > With some [special patches](https://github.com/davnotdev/wgpu/tree/trunk-naga-patches), `naga` can process these. ## Combined Image Samplers @@ -61,6 +66,50 @@ layout(set = 0, binding = 3) uniform sampler u_sampler; | `test_arrayed.frag` | ✅ | ✅ | ✅ | | `test_mixed.frag` | ✅ | ✅ | ✅ | +## Immediates (Push Constants) + +Immediates, more commonly known as push constants came to the WebGPU spec late, so browser support is still unreliable (as of writing). +This patch replaces push constants with an equivalent uniform buffer. + +```glsl +layout(std430, push_constant) uniform PushBlock { + vec4 color; + mat4 transform; + float scale; + ... +} pc; + +// is converted into... + +layout(std140, set = N+1, binding = 0) uniform PushBlock { + vec4 color; + mat4 transform; + float scale; + ... +} pc; + +// where N is the max set in the shader. + +``` + +### Additional Notes + +- The converted uniform is placed in `set = N+1`, where `N` is the highest descriptor set already in use. +- Shaders that contain `mat2` (or any matrix with 2-row columns: `matCx2`) in the push constant block will not pass through `naga` specifically. +- To my knowledge, padding should not be a concern between uniforms, storage, and immediates. + +### Tests + +| Test | `spirv-val` | Naga | Tint | +| ----------------------- | ----------- | ------ | ---- | +| `immediates.frag` | ✅ | ✅ | ✅ | +| `mat2_direct.frag` | ✅ | ❌\* | ✅ | +| `array_of_mat2.frag` | ✅ | ✅ | ✅ | +| `nested_struct.frag` | ✅ | ✅ | ✅ | +| `row_major.frag` | ✅ | ✅ | ✅ | + +> \* naga's SPIR-V front-end rejects `MatrixStride 16` for `mat2x2`, this should be fixed soon (?). + ## Mixed Depth / Comparison The WGSL spec differentiates between `sampler` and `sampler_comparison` as well as `texture2d` and `texture_depth_2d`. diff --git a/ffi/spirv_webgpu_transform.h b/ffi/spirv_webgpu_transform.h index 533e0b3..d28041a 100644 --- a/ffi/spirv_webgpu_transform.h +++ b/ffi/spirv_webgpu_transform.h @@ -15,6 +15,8 @@ void spirv_webgpu_transform_combimgsampsplitter_alloc(uint32_t *in_spv, uint32_t void spirv_webgpu_transform_combimgsampsplitter_free(uint32_t *out_spv); void spirv_webgpu_transform_drefsplitter_alloc(uint32_t *in_spv, uint32_t in_count, uint32_t **out_spv, uint32_t *out_count, TransformCorrectionMap *correction_map); void spirv_webgpu_transform_drefsplitter_free(uint32_t *out_spv); +void spirv_webgpu_transform_immediatespatch_alloc(uint32_t *in_spv, uint32_t in_count, uint32_t **out_spv, uint32_t *out_count); +void spirv_webgpu_transform_immediatespatch_free(uint32_t *out_spv); void spirv_webgpu_transform_isnanisinfpatch_alloc(uint32_t *in_spv, uint32_t in_count, uint32_t **out_spv, uint32_t *out_count); 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); diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index ab8fbc2..8de424d 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -2,8 +2,8 @@ use core::{ffi, ptr, slice}; use spirv_webgpu_transform::{ - CorrectionMap, combimgsampsplitter, drefsplitter, isnanisinfpatch, mirrorpatch, - pruneunuseddref, storagecubepatch, + CorrectionMap, combimgsampsplitter, drefsplitter, immediatespatch, isnanisinfpatch, + mirrorpatch, pruneunuseddref, storagecubepatch, }; type TransformCorrectionMap = *mut ffi::c_void; @@ -92,6 +92,32 @@ pub unsafe extern "C" fn spirv_webgpu_transform_drefsplitter_free(out_spv: *mut unsafe { drop(Box::from_raw(out_spv)) } } +#[unsafe(no_mangle)] +pub unsafe extern "C" fn spirv_webgpu_transform_immediatespatch_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 immediatespatch(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_immediatespatch_free(out_spv: *mut u32) { + unsafe { drop(Box::from_raw(out_spv)) } +} + #[unsafe(no_mangle)] pub unsafe extern "C" fn spirv_webgpu_transform_isnanisinfpatch_alloc( in_spv: *const u32, diff --git a/src/bin/spv_webgpu_transform.rs b/src/bin/spv_webgpu_transform.rs index 0f1203a..6183d1d 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); } @@ -31,6 +31,7 @@ fn main() { spirv_webgpu_transform::storagecubepatch(&spv, &mut out_correction_map).unwrap() } "pruneunuseddref" => spirv_webgpu_transform::pruneunuseddref(&spv).unwrap(), + "immediates" => spirv_webgpu_transform::immediatespatch(&spv).unwrap(), mode => { eprintln!("unknown mode {:?}", mode); process::exit(1) diff --git a/src/immediatespatch.rs b/src/immediatespatch.rs new file mode 100644 index 0000000..c1cc995 --- /dev/null +++ b/src/immediatespatch.rs @@ -0,0 +1,279 @@ +use super::*; + +mod layout; +mod type_registry; + +use layout::*; +use type_registry::*; + +/// Use [u8_slice_to_u32_vec] to convert a `&[u8]` into a `Vec`. +/// Does not produce any side effects or corrections. +pub fn immediatespatch(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 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 of instructions we need + let mut op_variable_idxs = vec![]; + let mut op_type_pointer_idxs = vec![]; + let mut op_type_struct_idxs = vec![]; + let mut op_type_array_idxs = vec![]; + let mut op_type_matrix_idxs = vec![]; + let mut op_type_vector_idxs = vec![]; + let mut op_type_float_idxs = vec![]; + let mut op_type_int_idxs = vec![]; + let mut op_constant_idxs = vec![]; + let mut op_decorate_idxs = vec![]; + let mut op_member_decorate_idxs = vec![]; + + 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_VARIABLE => op_variable_idxs.push(spv_idx), + SPV_INSTRUCTION_OP_TYPE_POINTER => op_type_pointer_idxs.push(spv_idx), + SPV_INSTRUCTION_OP_TYPE_STRUCT => op_type_struct_idxs.push(spv_idx), + SPV_INSTRUCTION_OP_TYPE_ARRAY => op_type_array_idxs.push(spv_idx), + SPV_INSTRUCTION_OP_TYPE_MATRIX => op_type_matrix_idxs.push(spv_idx), + SPV_INSTRUCTION_OP_TYPE_VECTOR => op_type_vector_idxs.push(spv_idx), + SPV_INSTRUCTION_OP_TYPE_FLOAT => op_type_float_idxs.push(spv_idx), + SPV_INSTRUCTION_OP_TYPE_INT => op_type_int_idxs.push(spv_idx), + SPV_INSTRUCTION_OP_CONSTANT => op_constant_idxs.push(spv_idx), + SPV_INSTRUCTION_OP_DECORATE => op_decorate_idxs.push(spv_idx), + SPV_INSTRUCTION_OP_MEMBER_DECORATE => op_member_decorate_idxs.push(spv_idx), + _ => {} + } + + spv_idx += word_count as usize; + } + + // 2. Find all `OpVariable` that is a `PushConstant` + let pc_variables = op_variable_idxs + .iter() + .filter_map(|&v_idx| { + let result_type_id = spv[v_idx + 1]; + let result_id = spv[v_idx + 2]; + let storage_class = spv[v_idx + 3]; + (storage_class == SPV_STORAGE_CLASS_PUSH_CONSTANT).then_some(( + v_idx, + result_type_id, + result_id, + )) + }) + .collect::>(); + + if pc_variables.is_empty() { + return Ok(in_spv.to_vec()); + } + + // 3. Find underlying type of variables + let block_struct_ids = pc_variables + .iter() + .map(|&(_, ptr_id, _)| { + op_type_pointer_idxs + .iter() + .find_map(|&tp_idx| { + let result_id = spv[tp_idx + 1]; + let underlying_type_id = spv[tp_idx + 3]; + (result_id == ptr_id).then_some(underlying_type_id) + }) + .expect("OpVariable PushConstant referenced an undefined OpTypePointer") + }) + .collect::>(); + + // 4. Build a registry of every relevant OpType* + let type_registry = build_type_registry(BuildTypeRegistryIn { + spv: &spv, + op_type_float_idxs: &op_type_float_idxs, + op_type_int_idxs: &op_type_int_idxs, + op_type_vector_idxs: &op_type_vector_idxs, + op_type_matrix_idxs: &op_type_matrix_idxs, + op_type_array_idxs: &op_type_array_idxs, + op_type_struct_idxs: &op_type_struct_idxs, + op_constant_idxs: &op_constant_idxs, + }); + + // 5. Rewrite Offset / ArrayStride / MatrixStride decoration + for &block_struct_id in &block_struct_ids { + relayout_type_recursive( + &spv, + &mut new_spv, + block_struct_id, + &type_registry, + &op_decorate_idxs, + &op_member_decorate_idxs, + ); + } + + // 6. Correct OpTypePointer and OpVariable PushConstant -> Uniform + // TODO: I believe having two of the same OpTypePointer is a validation error + for &tp_idx in &op_type_pointer_idxs { + let storage_class = spv[tp_idx + 2]; + if storage_class == SPV_STORAGE_CLASS_PUSH_CONSTANT { + new_spv[tp_idx + 2] = SPV_STORAGE_CLASS_UNIFORM; + } + } + for &(v_idx, _, _) in &pc_variables { + new_spv[v_idx + 3] = SPV_STORAGE_CLASS_UNIFORM; + } + + // 7. Place new uniforms in the set after the last set. + let first_op_decorate_idx = op_decorate_idxs.first().copied(); + let next_set = op_decorate_idxs + .iter() + .filter_map(|&d_idx| { + let decoration_id = spv[d_idx + 2]; + let decoration_value = spv[d_idx + 3]; + (decoration_id == SPV_DECORATION_DESCRIPTOR_SET).then_some(decoration_value) + }) + .max() + .map(|max| max + 1) + .unwrap_or(0); + + for (binding_idx, &(_, _, var_id)) in pc_variables.iter().enumerate() { + instruction_inserts.push(InstructionInsert { + previous_spv_idx: first_op_decorate_idx + .expect("Push constant block has no OpDecorate (missing Block decoration?)"), + instruction: vec![ + encode_word(4, SPV_INSTRUCTION_OP_DECORATE), + var_id, + SPV_DECORATION_DESCRIPTOR_SET, + next_set, + encode_word(4, SPV_INSTRUCTION_OP_DECORATE), + var_id, + SPV_DECORATION_BINDING, + binding_idx as u32, + ], + }); + } + + // 8. Insert New Instructions + insert_new_instructions(&spv, &mut new_spv, &word_inserts, &instruction_inserts); + + // 9. Remove Instructions that have been Whited Out. + prune_noops(&mut new_spv); + + // 10. Write New Header and New Code + Ok(fuse_final(spv_header, new_spv, instruction_bound)) +} + +// Recursively patch Offset / ArrayStride / MatrixStride decorations using our type registry. +fn relayout_type_recursive( + spv: &[u32], + new_spv: &mut [u32], + type_id: u32, + registry: &TypeRegistry, + op_decorate_idxs: &[usize], + op_member_decorate_idxs: &[usize], +) { + let ty = match registry.get(&type_id) { + Some(t) => t, + None => return, + }; + + match &ty.kind { + TypeKind::Struct { members } => { + let layout = layout_struct(members, LayoutRule::Std140); + + for (i, new_offset) in layout.member_offsets.iter().enumerate() { + patch_member_decoration_literal( + spv, + new_spv, + op_member_decorate_idxs, + type_id, + i as u32, + SPV_DECORATION_OFFSET, + *new_offset, + ); + } + + for (i, member) in members.iter().enumerate() { + let matrix_kind = match &member.kind { + TypeKind::Matrix { .. } => Some(&member.kind), + TypeKind::Array { element, .. } => match &element.kind { + TypeKind::Matrix { .. } => Some(&element.kind), + _ => None, + }, + _ => None, + }; + if let Some(TypeKind::Matrix { column, .. }) = matrix_kind { + let col_count = column_vec_count(column); + let scalar_w = column_scalar_width(column); + let new_stride = matrix_stride(col_count, scalar_w, LayoutRule::Std140); + patch_member_decoration_literal( + spv, + new_spv, + op_member_decorate_idxs, + type_id, + i as u32, + SPV_DECORATION_MATRIX_STRIDE, + new_stride, + ); + } + relayout_type_recursive( + spv, + new_spv, + member.id, + registry, + op_decorate_idxs, + op_member_decorate_idxs, + ); + } + } + + TypeKind::Array { element, .. } => { + let new_stride = array_stride(&element.kind, LayoutRule::Std140); + for &d_idx in op_decorate_idxs { + let target_id = spv[d_idx + 1]; + let decoration_id = spv[d_idx + 2]; + if target_id == type_id && decoration_id == SPV_DECORATION_ARRAY_STRIDE { + new_spv[d_idx + 3] = new_stride; + } + } + // Ensure arrays of arrays and array of structs are updated. + relayout_type_recursive( + spv, + new_spv, + element.id, + registry, + op_decorate_idxs, + op_member_decorate_idxs, + ); + } + // No effect from scalars, vectors, and matrices. + _ => {} + } +} + +fn patch_member_decoration_literal( + spv: &[u32], + new_spv: &mut [u32], + op_member_decorate_idxs: &[usize], + target_id: u32, + member: u32, + decoration: u32, + new_value: u32, +) { + for &md_idx in op_member_decorate_idxs { + let md_target_id = spv[md_idx + 1]; + let md_member = spv[md_idx + 2]; + let md_decoration = spv[md_idx + 3]; + if md_target_id == target_id && md_member == member && md_decoration == decoration { + new_spv[md_idx + 4] = new_value; + } + } +} diff --git a/src/immediatespatch/layout.rs b/src/immediatespatch/layout.rs new file mode 100644 index 0000000..6988c55 --- /dev/null +++ b/src/immediatespatch/layout.rs @@ -0,0 +1,160 @@ +// SPIR-V / Vulkan buffer layout rules. +// +// The conversion this patch performs is std430 -> std140: +// bump array / struct base alignment to 16, and bump `ArrayStride` / `MatrixStride` to >=16. + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(super) enum LayoutRule { + /// "Standard Storage Buffer Layout" + /// Push constants use this one. + #[allow(dead_code)] + Std430, + /// "Standard Uniform Buffer Layout" + /// Arrays and structs have their base alignment rounded up to a multiple of 16. + Std140, +} + +#[derive(Debug, Clone)] +pub(super) struct Type { + pub id: u32, + pub kind: TypeKind, +} + +#[derive(Debug, Clone)] +pub(super) enum TypeKind { + Scalar { width_bytes: u32 }, + Vector { component: Box, count: u32 }, + Matrix { column: Box, cols: u32 }, + Array { element: Box, len: u32 }, + Struct { members: Vec }, +} + +pub(super) struct StructLayout { + pub member_offsets: Vec, + pub size: u32, + #[allow(dead_code)] + pub align: u32, +} + +const fn round_up(x: u32, a: u32) -> u32 { + (x + a - 1) & !(a - 1) +} + +pub(super) fn base_align(t: &TypeKind, rule: LayoutRule) -> u32 { + let inner = match t { + // §15.6.4: "A scalar has a base alignment equal to its scalar alignment." + TypeKind::Scalar { width_bytes } => *width_bytes, + // §15.6.4: vec2 = 2N, vec3 / vec4 = 4N (where N is the scalar alignment of the component type). + TypeKind::Vector { component, count } => match count { + 2 => 2 * base_align(&component.kind, rule), + 3 | 4 => 4 * base_align(&component.kind, rule), + n => panic!("Unsupported vector component count: {}", n), + }, + // §15.6.4: "A column-major matrix has a base alignment equal to the base alignment of the column vector type." + // RowMajor is handled at MatrixStride emission time; the type itself describes columns. + TypeKind::Matrix { column, .. } => base_align(&column.kind, rule), + // §15.6.4: "An array has a base alignment equal to the base alignment of its element type" — modulo the std140 round-up below. + TypeKind::Array { element, .. } => base_align(&element.kind, rule), + // §15.6.4: "A structure has a base alignment equal to the largest base alignment of any of its members" — modulo the std140 round-up below. + TypeKind::Struct { members } => members + .iter() + .map(|m| base_align(&m.kind, rule)) + .max() + .unwrap_or(4), + }; + // §15.6.4 "Standard Uniform Buffer Layout": + // - Array's base alignment is rounded up to a multiple of 16. + // - Struct's base alignment is rounded up to a multiple of 16. + // The Standard Storage Buffer Layout (and push constants) omit this rule. + match (rule, t) { + (LayoutRule::Std140, TypeKind::Array { .. } | TypeKind::Struct { .. }) => inner.max(16), + _ => inner, + } +} + +pub(super) fn size_of(t: &TypeKind, rule: LayoutRule) -> u32 { + match t { + TypeKind::Scalar { width_bytes } => *width_bytes, + // Tight component packing. + // vec3 occupies 3N bytes; the alignment padding for the next member is supplied by the consumer at offset assignment time. + TypeKind::Vector { component, count } => count * size_of(&component.kind, rule), + TypeKind::Matrix { column, cols } => { + let col_count = column_vec_count(column); + let scalar_w = column_scalar_width(column); + cols * matrix_stride(col_count, scalar_w, rule) + } + TypeKind::Array { element, len } => array_stride(&element.kind, rule) * len, + TypeKind::Struct { members } => layout_struct(members, rule).size, + } +} + +// "An array's `ArrayStride` is equal to its element's consumed size rounded up to the array's base alignment." Under Standard +// Uniform Buffer Layout the element's base alignment is itself ≥16, so the resulting stride is also ≥16. +pub(super) fn array_stride(elem: &TypeKind, rule: LayoutRule) -> u32 { + let align = base_align(elem, rule); + let raw = round_up(size_of(elem, rule), align); + match rule { + LayoutRule::Std140 => raw.max(16), + LayoutRule::Std430 => raw, + } +} + +// `column_vec_count` is the component count of one column for a ColMajor matrix, or one row for a RowMajor matrix. +// The stride is the size of that column / row rounded up to its own vector base alignment and to 16 under std140. +pub(super) fn matrix_stride(column_vec_count: u32, scalar_w: u32, rule: LayoutRule) -> u32 { + let vec_align = match column_vec_count { + 1 => scalar_w, + 2 => 2 * scalar_w, + 3 | 4 => 4 * scalar_w, + n => panic!("Unsupported matrix column dimension: {}", n), + }; + let raw = round_up(column_vec_count * scalar_w, vec_align); + match rule { + LayoutRule::Std140 => raw.max(16), + LayoutRule::Std430 => raw, + } +} + +// Compute member offsets and total size for an `OpTypeStruct`. +// +// "The members are assigned consecutive offsets starting from zero, with each member's offset adjusted upwards to satisfy its base alignment." +// "The structure's size is the offset of the last member, plus the size of the last member, rounded up to a multiple of the structure's base alignment." +pub(super) fn layout_struct(members: &[Type], rule: LayoutRule) -> StructLayout { + let mut offset = 0u32; + let mut offsets = Vec::with_capacity(members.len()); + let mut align = 4u32; + + for m in members { + let a = base_align(&m.kind, rule); + offset = round_up(offset, a); + offsets.push(offset); + offset += size_of(&m.kind, rule); + align = align.max(a); + } + + let size = round_up(offset, align); + StructLayout { + member_offsets: offsets, + size, + align, + } +} + +pub(super) fn column_vec_count(column: &Type) -> u32 { + match &column.kind { + TypeKind::Vector { count, .. } => *count, + TypeKind::Scalar { .. } => 1, + _ => panic!("Matrix column type must be a scalar or vector"), + } +} + +pub(super) fn column_scalar_width(column: &Type) -> u32 { + match &column.kind { + TypeKind::Vector { component, .. } => match &component.kind { + TypeKind::Scalar { width_bytes } => *width_bytes, + _ => panic!("Matrix column vector component must be scalar"), + }, + TypeKind::Scalar { width_bytes } => *width_bytes, + _ => panic!("Matrix column type must be a scalar or vector"), + } +} diff --git a/src/immediatespatch/type_registry.rs b/src/immediatespatch/type_registry.rs new file mode 100644 index 0000000..519533e --- /dev/null +++ b/src/immediatespatch/type_registry.rs @@ -0,0 +1,137 @@ +use super::*; + +pub type TypeRegistry = HashMap; +pub struct BuildTypeRegistryIn<'a> { + pub spv: &'a [u32], + pub op_type_float_idxs: &'a [usize], + pub op_type_int_idxs: &'a [usize], + pub op_type_vector_idxs: &'a [usize], + pub op_type_matrix_idxs: &'a [usize], + pub op_type_array_idxs: &'a [usize], + pub op_type_struct_idxs: &'a [usize], + pub op_constant_idxs: &'a [usize], +} + +pub fn build_type_registry(build_in: BuildTypeRegistryIn) -> TypeRegistry { + let BuildTypeRegistryIn { + spv, + op_type_float_idxs, + op_type_int_idxs, + op_type_vector_idxs, + op_type_matrix_idxs, + op_type_array_idxs, + op_type_struct_idxs, + op_constant_idxs, + } = build_in; + let mut all_idxs = op_type_float_idxs + .iter() + .chain(op_type_int_idxs.iter()) + .chain(op_type_vector_idxs.iter()) + .chain(op_type_matrix_idxs.iter()) + .chain(op_type_array_idxs.iter()) + .chain(op_type_struct_idxs.iter()) + .copied() + .collect::>(); + // Make sure to walk in order + all_idxs.sort(); + + let mut reg: TypeRegistry = HashMap::new(); + + for idx in all_idxs { + let op = spv[idx]; + let word_count = hiword(op) as usize; + let instruction = loword(op); + let id = spv[idx + 1]; + + match instruction { + SPV_INSTRUCTION_OP_TYPE_FLOAT | SPV_INSTRUCTION_OP_TYPE_INT => { + let width_bytes = spv[idx + 2] / 8; + reg.insert( + id, + Type { + id, + kind: TypeKind::Scalar { width_bytes }, + }, + ); + } + SPV_INSTRUCTION_OP_TYPE_VECTOR => { + let comp_id = spv[idx + 2]; + let count = spv[idx + 3]; + if let Some(component) = reg.get(&comp_id).cloned() { + reg.insert( + id, + Type { + id, + kind: TypeKind::Vector { + component: Box::new(component), + count, + }, + }, + ); + } + } + SPV_INSTRUCTION_OP_TYPE_MATRIX => { + let col_id = spv[idx + 2]; + let cols = spv[idx + 3]; + if let Some(column) = reg.get(&col_id).cloned() { + reg.insert( + id, + Type { + id, + kind: TypeKind::Matrix { + column: Box::new(column), + cols, + }, + }, + ); + } + } + SPV_INSTRUCTION_OP_TYPE_ARRAY => { + let elem_id = spv[idx + 2]; + let len_id = spv[idx + 3]; + let maybe_len = op_constant_idxs.iter().find_map(|&c_idx| { + let result_id = spv[c_idx + 2]; + let literal_value = spv[c_idx + 3]; + (result_id == len_id).then_some(literal_value) + }); + if let (Some(element), Some(len)) = (reg.get(&elem_id).cloned(), maybe_len) { + reg.insert( + id, + Type { + id, + kind: TypeKind::Array { + element: Box::new(element), + len, + }, + }, + ); + } + } + SPV_INSTRUCTION_OP_TYPE_STRUCT => { + let mut members = Vec::with_capacity(word_count.saturating_sub(2)); + let mut complete = true; + for m_word in 2..word_count { + let member_id = spv[idx + m_word]; + if let Some(member) = reg.get(&member_id).cloned() { + members.push(member); + } else { + complete = false; + break; + } + } + if complete { + reg.insert( + id, + Type { + id, + kind: TypeKind::Struct { members }, + }, + ); + } + } + _ => {} + } + } + + reg +} diff --git a/src/lib.rs b/src/lib.rs index 92ade24..2bf78a8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,17 +1,23 @@ -//! ## +//! # SPIRV WebGPU Transforms +//! +//! See [https://github.com/davnotdev/spirv-webgpu-transform](https://github.com/davnotdev/spirv-webgpu-transform) for more details. //! //! ## Features //! //! 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 | ✅ | ✅ | ✅ | +//! | Immediates (Push Constants) | ✅ | ✅\* | ✅ | +//! | Mixed Depth / Comparison | ✅ | ⚠️\* | ❌ | +//! | isnan / isinf Patching | ✅ | ✅ | ✅ | +//! | Storage Cube Patching | ✅ | ✅ | ✅ | +//! | Unused Image Sampler Pruning | ✅ | ✅ | ✅ | +//! +//! > (1)\* 99% OK, just one very specific padding related `naga` bug. //! -//! > \* Simple cases are OK. +//! > (2)\* Simple cases are OK. //! > With some [special patches](https://github.com/davnotdev/wgpu/tree/trunk-naga-patches), `naga` can process these. //! //! ## Using the result @@ -21,14 +27,11 @@ //! 1. Know which set bindings were affected, use the output [`CorrectionMap`] for this purpose. //! 2. Ensure that your vertex and fragment shaders shader the same binding layout, use [`mirrorpatch`] for this purpose //! -//! ## For more details -//! -//! See [https://github.com/davnotdev/spirv-webgpu-transform](https://github.com/davnotdev/spirv-webgpu-transform) for more details. -//! use std::collections::{HashMap, HashSet}; mod correction; +mod immediatespatch; mod isnanisinfpatch; mod mirrorpatch; mod pruneunuseddref; @@ -45,6 +48,7 @@ use spv::*; use util::*; pub use correction::*; +pub use immediatespatch::*; pub use isnanisinfpatch::*; pub use mirrorpatch::*; pub use pruneunuseddref::*; diff --git a/src/pruneunuseddref.rs b/src/pruneunuseddref.rs index 0febd59..6c2665c 100644 --- a/src/pruneunuseddref.rs +++ b/src/pruneunuseddref.rs @@ -70,7 +70,7 @@ pub fn pruneunuseddref(in_spv: &[u32]) -> Result, ()> { let type_id = spv[ti_idx + 1]; let image_sampled = spv[ti_idx + 7]; - // `!= 2` filters for storage textures which shouldn't be pruned. + // `!= 2` filters for storage textures which shouldn't be pruned. image_sampled != 2 && type_id == underlying_type_id }) .then_some(result_id) diff --git a/src/splitcombined.rs b/src/splitcombined.rs index b466c32..789be03 100644 --- a/src/splitcombined.rs +++ b/src/splitcombined.rs @@ -70,6 +70,7 @@ pub fn combimgsampsplitter( } SPV_INSTRUCTION_OP_TYPE_SAMPLED_IMAGE => op_type_sampled_image_idxs.push(spv_idx), SPV_INSTRUCTION_OP_TYPE_POINTER => { + #[allow(clippy::collapsible_match)] if spv[spv_idx + 2] == SPV_STORAGE_CLASS_UNIFORM_CONSTANT { op_type_pointer_idxs.push(spv_idx); } diff --git a/src/spv.rs b/src/spv.rs index 216e6a3..cb17b41 100644 --- a/src/spv.rs +++ b/src/spv.rs @@ -10,9 +10,12 @@ pub const SPV_INSTRUCTION_OP_TYPE_BOOL: u16 = 20; pub const SPV_INSTRUCTION_OP_TYPE_INT: u16 = 21; pub const SPV_INSTRUCTION_OP_TYPE_FLOAT: u16 = 22; pub const SPV_INSTRUCTION_OP_TYPE_VECTOR: u16 = 23; +pub const SPV_INSTRUCTION_OP_TYPE_MATRIX: u16 = 24; pub const SPV_INSTRUCTION_OP_TYPE_IMAGE: u16 = 25; pub const SPV_INSTRUCTION_OP_TYPE_SAMPLER: u16 = 26; pub const SPV_INSTRUCTION_OP_TYPE_SAMPLED_IMAGE: u16 = 27; +pub const SPV_INSTRUCTION_OP_TYPE_ARRAY: u16 = 28; +pub const SPV_INSTRUCTION_OP_TYPE_STRUCT: u16 = 30; pub const SPV_INSTRUCTION_OP_TYPE_POINTER: u16 = 32; pub const SPV_INSTRUCTION_OP_TYPE_FUNCTION: u16 = 33; pub const SPV_INSTRUCTION_OP_CONSTANT: u16 = 43; @@ -25,6 +28,7 @@ pub const SPV_INSTRUCTION_OP_LOAD: u16 = 61; pub const SPV_INSTRUCTION_OP_STORE: u16 = 62; pub const SPV_INSTRUCTION_OP_ACCESS_CHAIN: u16 = 65; pub const SPV_INSTRUCTION_OP_DECORATE: u16 = 71; +pub const SPV_INSTRUCTION_OP_MEMBER_DECORATE: u16 = 72; pub const SPV_INSTRUCTION_OP_COMPOSITE_CONSTRUCT: u16 = 80; pub const SPV_INSTRUCTION_OP_SAMPLED_IMAGE: u16 = 86; pub const SPV_INSTRUCTION_OP_BITCAST: u16 = 124; @@ -73,9 +77,14 @@ pub const SPV_INSTRUCTION_OP_BRANCH: u16 = 249; pub const SPV_INSTRUCTION_OP_BRANCH_CONDITIONAL: u16 = 250; pub const SPV_STORAGE_CLASS_UNIFORM_CONSTANT: u32 = 0; +pub const SPV_STORAGE_CLASS_UNIFORM: u32 = 2; pub const SPV_STORAGE_CLASS_FUNCTION: u32 = 7; +pub const SPV_STORAGE_CLASS_PUSH_CONSTANT: u32 = 9; +pub const SPV_DECORATION_ARRAY_STRIDE: u32 = 6; +pub const SPV_DECORATION_MATRIX_STRIDE: u32 = 7; pub const SPV_DECORATION_BINDING: u32 = 33; pub const SPV_DECORATION_DESCRIPTOR_SET: u32 = 34; +pub const SPV_DECORATION_OFFSET: u32 = 35; pub const SPV_FUNCTION_CONTROL_INLINE: u32 = 1; pub const SPV_SIGNEDNESS_UNSIGNED: u32 = 0; pub const SPV_SIGNEDNESS_SIGNED: u32 = 1; diff --git a/src/test.rs b/src/test.rs index 19e1f76..d8fd3d6 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,6 +1,6 @@ use super::{ - combimgsampsplitter, drefsplitter, isnanisinfpatch, mirrorpatch, pruneunuseddref, - storagecubepatch, u8_slice_to_u32_vec, u32_slice_to_u8_vec, + combimgsampsplitter, drefsplitter, immediatespatch, isnanisinfpatch, mirrorpatch, + pruneunuseddref, storagecubepatch, u8_slice_to_u32_vec, u32_slice_to_u8_vec, }; use naga::{back, front, valid}; @@ -48,14 +48,7 @@ fn try_spv_to_wgsl(spv: &[u32], flags: u8) { if flags & NAGA_VALIDATE != 0 { let module = front::spv::parse_u8_slice(&spv_u8, &front::spv::Options::default()).unwrap(); - - let mut caps = valid::Capabilities::default(); - caps.set( - valid::Capabilities::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, - true, - ); - caps.set(valid::Capabilities::SAMPLER_NON_UNIFORM_INDEXING, true); - + let caps = valid::Capabilities::default(); let mut info = valid::Validator::new(valid::ValidationFlags::all(), caps); let info = info.validate(&module).unwrap(); @@ -216,3 +209,37 @@ test_with_spv_and_fn_no_correction![ "./test/pruneunuseddref/pruneunuseddref_storage.spv", pruneunuseddref ]; + +// --- +test_with_spv_and_fn_no_correction![ + immediatespatch_immediatespatch_immediates, + DO_ALL, + "./test/immediatespatch/immediates.spv", + immediatespatch +]; +// TODO: This is valid, just not supported in current naga. +// Someone check on this in a month or so, I think the fix has been merged. +test_with_spv_and_fn_no_correction![ + immediatespatch_mat2_direct, + SPV_VALIDATE, + "./test/immediatespatch/mat2_direct.spv", + immediatespatch +]; +test_with_spv_and_fn_no_correction![ + immediatespatch_array_of_mat2, + DO_ALL, + "./test/immediatespatch/array_of_mat2.spv", + immediatespatch +]; +test_with_spv_and_fn_no_correction![ + immediatespatch_nested_struct, + DO_ALL, + "./test/immediatespatch/nested_struct.spv", + immediatespatch +]; +test_with_spv_and_fn_no_correction![ + immediatespatch_row_major, + DO_ALL, + "./test/immediatespatch/row_major.spv", + immediatespatch +]; diff --git a/src/test/compile.sh b/src/test/compile.sh index b622858..28bca60 100755 --- a/src/test/compile.sh +++ b/src/test/compile.sh @@ -6,3 +6,4 @@ set -e (cd isnanisinfpatch; ./compile.sh) (cd storagecubepatch; ./compile.sh) (cd pruneunuseddref; ./compile.sh) +(cd immediatespatch; ./compile.sh) diff --git a/src/test/immediatespatch/array_of_mat2.frag b/src/test/immediatespatch/array_of_mat2.frag new file mode 100644 index 0000000..e123f31 --- /dev/null +++ b/src/test/immediatespatch/array_of_mat2.frag @@ -0,0 +1,10 @@ +#version 450 + +layout(push_constant) uniform PC { + mat2 mats[2]; +} pc; +layout(location = 0) out vec4 o_color; + +void main() { + o_color = vec4(pc.mats[0][0] + pc.mats[1][0], 0.0, 1.0); +} diff --git a/src/test/immediatespatch/array_of_mat2.spv b/src/test/immediatespatch/array_of_mat2.spv new file mode 100644 index 0000000..4a88173 Binary files /dev/null and b/src/test/immediatespatch/array_of_mat2.spv differ diff --git a/src/test/immediatespatch/compile.sh b/src/test/immediatespatch/compile.sh new file mode 100755 index 0000000..a1a1cbe --- /dev/null +++ b/src/test/immediatespatch/compile.sh @@ -0,0 +1,7 @@ +set -e + +glslc -O0 immediates.frag -o immediates.spv +glslc -O0 mat2_direct.frag -o mat2_direct.spv +glslc -O0 array_of_mat2.frag -o array_of_mat2.spv +glslc -O0 nested_struct.frag -o nested_struct.spv +glslc -O0 row_major.frag -o row_major.spv diff --git a/src/test/immediatespatch/immediates.frag b/src/test/immediatespatch/immediates.frag new file mode 100644 index 0000000..624aca2 --- /dev/null +++ b/src/test/immediatespatch/immediates.frag @@ -0,0 +1,22 @@ +#version 450 + +struct Inner { + vec3 v; + float arr[3]; +}; + +layout(push_constant) uniform PushBlock { + vec4 color; + mat4 transform; + float scale; + Inner inner; +} pc; + +layout(location = 0) in vec4 i_pos; +layout(location = 0) out vec4 o_color; + +void main() { + o_color = pc.color * pc.scale + + pc.transform * i_pos + + vec4(pc.inner.v, pc.inner.arr[1]); +} diff --git a/src/test/immediatespatch/immediates.spv b/src/test/immediatespatch/immediates.spv new file mode 100644 index 0000000..c0018ca Binary files /dev/null and b/src/test/immediatespatch/immediates.spv differ diff --git a/src/test/immediatespatch/mat2_direct.frag b/src/test/immediatespatch/mat2_direct.frag new file mode 100644 index 0000000..18b276d --- /dev/null +++ b/src/test/immediatespatch/mat2_direct.frag @@ -0,0 +1,12 @@ +#version 450 +layout(push_constant) uniform PC { + mat2 m; + float x; +} pc; +layout(location = 0) out vec4 o_color; + +void main() { + vec2 col0 = pc.m[0]; + vec2 col1 = pc.m[1]; + o_color = vec4(col0, col1) * pc.x; +} diff --git a/src/test/immediatespatch/mat2_direct.spv b/src/test/immediatespatch/mat2_direct.spv new file mode 100644 index 0000000..97181fb Binary files /dev/null and b/src/test/immediatespatch/mat2_direct.spv differ diff --git a/src/test/immediatespatch/nested_struct.frag b/src/test/immediatespatch/nested_struct.frag new file mode 100644 index 0000000..2e590c3 --- /dev/null +++ b/src/test/immediatespatch/nested_struct.frag @@ -0,0 +1,20 @@ +#version 450 + +struct Level2 { + float a; + vec3 b; +}; +struct Level1 { + Level2 deep; + vec2 c; +}; +layout(push_constant) uniform PC { + Level1 outer; + float scale; +} pc; +layout(location = 0) out vec4 o_color; + +void main() { + vec3 v = pc.outer.deep.b * pc.outer.deep.a; + o_color = vec4(v.x + pc.outer.c.x, v.y + pc.outer.c.y, v.z + pc.scale, 1.0); +} diff --git a/src/test/immediatespatch/nested_struct.spv b/src/test/immediatespatch/nested_struct.spv new file mode 100644 index 0000000..49249ab Binary files /dev/null and b/src/test/immediatespatch/nested_struct.spv differ diff --git a/src/test/immediatespatch/row_major.frag b/src/test/immediatespatch/row_major.frag new file mode 100644 index 0000000..e67b136 --- /dev/null +++ b/src/test/immediatespatch/row_major.frag @@ -0,0 +1,11 @@ +#version 450 +layout(push_constant) uniform PC { + layout(row_major) mat4 m; + vec4 offset; +} pc; +layout(location = 0) in vec4 input_vec; +layout(location = 0) out vec4 o_color; + +void main() { + o_color = pc.m * input_vec + pc.offset; +} diff --git a/src/test/immediatespatch/row_major.spv b/src/test/immediatespatch/row_major.spv new file mode 100644 index 0000000..a4d227c Binary files /dev/null and b/src/test/immediatespatch/row_major.spv differ