diff --git a/src/back/spv/writer.rs b/src/back/spv/writer.rs index 1af929eda1..dac71b19f9 100644 --- a/src/back/spv/writer.rs +++ b/src/back/spv/writer.rs @@ -1310,6 +1310,11 @@ impl Writer { self.logical_layout.in_words(words); Ok(()) } + + /// Return the set of capabilities the last module written used. + pub fn get_capabilities_used(&self) -> &crate::FastHashSet { + &self.capabilities_used + } } #[test] diff --git a/tests/spirv-capabilities.rs b/tests/spirv-capabilities.rs new file mode 100644 index 0000000000..c4664a90c9 --- /dev/null +++ b/tests/spirv-capabilities.rs @@ -0,0 +1,172 @@ +//! Test SPIR-V backend capability checks. + +#![cfg(all(feature = "wgsl-in", feature = "spv-out"))] + +use spirv::Capability as Ca; + +fn capabilities_used(source: &str) -> naga::FastHashSet { + use naga::back::spv; + use naga::valid; + + let module = naga::front::wgsl::parse_str(source).unwrap_or_else(|e| { + panic!( + "expected WGSL to parse successfully:\n{}", + e.emit_to_string(source) + ); + }); + + let info = valid::Validator::new(valid::ValidationFlags::all(), valid::Capabilities::all()) + .validate(&module) + .expect("validation failed"); + + let mut words = vec![]; + let mut writer = spv::Writer::new(&spv::Options::default()).unwrap(); + writer.write(&module, &info, &mut words).unwrap(); + writer.get_capabilities_used().clone() +} + +fn require(capabilities: &[Ca], source: &str) { + let caps_used = capabilities_used(source); + + let missing_caps: Vec<_> = capabilities + .iter() + .filter(|cap| !caps_used.contains(cap)) + .cloned() + .collect(); + if !missing_caps.is_empty() { + panic!( + "shader code should have requested these caps: {:?}", + missing_caps + ); + } +} + +fn require_and_forbid(required: &[Ca], forbidden: &[Ca], source: &str) { + let caps_used = capabilities_used(source); + + let missing_caps: Vec<_> = required + .iter() + .filter(|cap| !caps_used.contains(cap)) + .cloned() + .collect(); + if !missing_caps.is_empty() { + panic!( + "shader code should have requested these caps: {:?}", + missing_caps + ); + } + + let forbidden_caps: Vec<_> = forbidden + .iter() + .filter(|cap| caps_used.contains(cap)) + .cloned() + .collect(); + if !forbidden_caps.is_empty() { + panic!( + "shader code should not have requested these caps: {:?}", + forbidden_caps + ); + } +} + +#[test] +fn sampler1d() { + require( + &[Ca::Sampled1D], + r#" + [[group(0), binding(0)]] + var image_1d: texture_1d; + "#, + ); +} + +#[test] +fn storage1d() { + require( + &[Ca::Image1D], + r#" + [[group(0), binding(0)]] + var image_1d: texture_storage_1d; + "#, + ); +} + +#[test] +fn cube_array() { + // ImageCubeArray is only for storage cube array images, which WGSL doesn't + // support + require_and_forbid( + &[Ca::SampledCubeArray], + &[Ca::ImageCubeArray], + r#" + [[group(0), binding(0)]] + var image_cube: texture_cube_array; + "#, + ); +} + +#[test] +fn image_queries() { + require( + &[Ca::ImageQuery], + r#" + fn f(i: texture_2d) -> vec2 { + return textureDimensions(i); + } + "#, + ); + require( + &[Ca::ImageQuery], + r#" + fn f(i: texture_2d_array) -> i32 { + return textureNumLayers(i); + } + "#, + ); + require( + &[Ca::ImageQuery], + r#" + fn f(i: texture_2d) -> i32 { + return textureNumLevels(i); + } + "#, + ); + require( + &[Ca::ImageQuery], + r#" + fn f(i: texture_multisampled_2d) -> i32 { + return textureNumSamples(i); + } + "#, + ); +} + +#[test] +fn sample_rate_shading() { + require( + &[Ca::SampleRateShading], + r#" + [[stage(fragment)]] + fn f([[location(0), interpolate(perspective, sample)]] x: f32) { } + "#, + ); + + require( + &[Ca::SampleRateShading], + r#" + [[stage(fragment)]] + fn f([[builtin(sample_index)]] x: u32) { } + "#, + ); +} + +#[test] +fn geometry() { + require( + &[Ca::Geometry], + r#" + [[stage(fragment)]] + fn f([[builtin(primitive_index)]] x: u32) { } + "#, + ); +}