diff --git a/tests/out/spv/debug-symbol-simple.spvasm b/tests/out/spv/debug-symbol-simple.spvasm index 67ad8beb29..8f64c324ce 100644 --- a/tests/out/spv/debug-symbol-simple.spvasm +++ b/tests/out/spv/debug-symbol-simple.spvasm @@ -8,7 +8,7 @@ OpMemoryModel Logical GLSL450 OpEntryPoint Vertex %24 "vs_main" %15 %18 %20 %22 OpEntryPoint Fragment %57 "fs_main" %51 %54 %56 OpExecutionMode %57 OriginUpperLeft -%3 = OpString "debug-symbol-simple" +%3 = OpString "debug-symbol-simple.wgsl" OpSource Unknown 0 %3 "struct VertexInput { @location(0) position: vec3, @location(1) color: vec3, diff --git a/tests/out/spv/debug-symbol-terrain.spvasm b/tests/out/spv/debug-symbol-terrain.spvasm index ac83cc1239..8c27f3ab9e 100644 --- a/tests/out/spv/debug-symbol-terrain.spvasm +++ b/tests/out/spv/debug-symbol-terrain.spvasm @@ -14,7 +14,7 @@ OpEntryPoint Fragment %587 "fs_main" %580 %582 %584 %586 OpExecutionMode %348 LocalSize 64 1 1 OpExecutionMode %470 OriginUpperLeft OpExecutionMode %587 OriginUpperLeft -%3 = OpString "debug-symbol-terrain" +%3 = OpString "debug-symbol-terrain.wgsl" OpSource Unknown 0 %3 "// Taken from https://github.com/sotrh/learn-wgpu/blob/11820796f5e1dbce42fb1119f04ddeb4b167d2a0/code/intermediate/tutorial13-terrain/src/terrain.wgsl // ============================ // Terrain Generation diff --git a/tests/snapshots.rs b/tests/snapshots.rs index b4d47dfc86..bc5fea08f1 100644 --- a/tests/snapshots.rs +++ b/tests/snapshots.rs @@ -7,6 +7,7 @@ use std::{ path::{Path, PathBuf}, }; +const CRATE_ROOT: &str = env!("CARGO_MANIFEST_DIR"); const BASE_DIR_IN: &str = "tests/in"; const BASE_DIR_OUT: &str = "tests/out"; @@ -88,21 +89,176 @@ struct Parameters { glsl_multiview: Option, } +/// Information about a shader input file. +#[derive(Debug)] +struct Input { + /// The subdirectory of `tests/in` to which this input belongs, if any. + /// + /// If the subdirectory is omitted, we assume that the output goes + /// to "wgsl". + subdirectory: Option, + + /// The input filename name, without a directory. + file_name: PathBuf, + + /// True if output filenames should add the output extension on top of + /// `file_name`'s existing extension, rather than replacing it. + /// + /// This is used by `convert_glsl_folder`, which wants to take input files + /// like `210-bevy-2d-shader.frag` and just add `.wgsl` to it, producing + /// `210-bevy-2d-shader.frag.wgsl`. + keep_input_extension: bool, +} + +impl Input { + /// Read an input file and its corresponding parameters file. + /// + /// Given `input`, the relative path of a shader input file, return + /// a `Source` value containing its path, code, and parameters. + /// + /// The `input` path is interpreted relative to the `BASE_DIR_IN` + /// subdirectory of the directory given by the `CARGO_MANIFEST_DIR` + /// environment variable. + fn new(subdirectory: Option<&str>, name: &str, extension: &str) -> Input { + Input { + subdirectory: subdirectory.map(PathBuf::from), + // Don't wipe out any extensions on `name`, as + // `with_extension` would do. + file_name: PathBuf::from(format!("{name}.{extension}")), + keep_input_extension: false, + } + } + + /// Return an iterator that produces an `Input` for each entry in `subdirectory`. + fn files_in_dir(subdirectory: &str) -> impl Iterator + 'static { + let subdirectory = subdirectory.to_string(); + let mut input_directory = Path::new(env!("CARGO_MANIFEST_DIR")).join(BASE_DIR_IN); + input_directory.push(&subdirectory); + match std::fs::read_dir(&input_directory) { + Ok(entries) => entries.map(move |result| { + let entry = result.expect("error reading directory"); + let file_name = PathBuf::from(entry.file_name()); + let extension = file_name + .extension() + .expect("all files in snapshot input directory should have extensions"); + let input = Input::new( + Some(&subdirectory), + &file_name.file_stem().unwrap().to_str().unwrap(), + &extension.to_str().unwrap(), + ); + input + }), + Err(err) => { + panic!( + "Error opening directory '{}': {}", + input_directory.display(), + err + ); + } + } + } + + /// Return the path to the input directory. + fn input_directory(&self) -> PathBuf { + let mut dir = Path::new(CRATE_ROOT).join(BASE_DIR_IN); + if let Some(ref subdirectory) = self.subdirectory { + dir.push(subdirectory); + } + dir + } + + /// Return the path to the output directory. + fn output_directory(&self, subdirectory: &str) -> PathBuf { + let mut dir = Path::new(CRATE_ROOT).join(BASE_DIR_OUT); + dir.push(subdirectory); + dir + } + + /// Return the path to the input file. + fn input_path(&self) -> PathBuf { + let mut input = self.input_directory(); + input.push(&self.file_name); + input + } + + fn output_path(&self, subdirectory: &str, extension: &str) -> PathBuf { + let mut output = self.output_directory(subdirectory); + if self.keep_input_extension { + let mut file_name = self.file_name.as_os_str().to_owned(); + file_name.push("."); + file_name.push(extension); + output.push(&file_name); + } else { + output.push(&self.file_name); + output.set_extension(extension); + } + output + } + + /// Return the contents of the input file as a string. + fn read_source(&self) -> String { + println!("Processing '{}'", self.file_name.display()); + let input_path = self.input_path(); + match fs::read_to_string(&input_path) { + Ok(source) => source, + Err(err) => { + panic!( + "Couldn't read shader input file `{}`: {}", + input_path.display(), + err + ); + } + } + } + + /// Return the contents of the input file as a vector of bytes. + fn read_bytes(&self) -> Vec { + println!("Processing '{}'", self.file_name.display()); + let input_path = self.input_path(); + match fs::read(&input_path) { + Ok(bytes) => bytes, + Err(err) => { + panic!( + "Couldn't read shader input file `{}`: {}", + input_path.display(), + err + ); + } + } + } + + /// Return this input's parameter file, parsed. + fn read_parameters(&self) -> Parameters { + let mut param_path = self.input_path(); + param_path.set_extension("param.ron"); + match fs::read_to_string(¶m_path) { + Ok(string) => ron::de::from_str(&string).expect(&format!( + "Couldn't parse param file: {}", + param_path.display() + )), + Err(_) => Parameters::default(), + } + } + + /// Write `data` to a file corresponding to this input file in + /// `subdirectory`, with `extension`. + fn write_output_file(&self, subdirectory: &str, extension: &str, data: impl AsRef<[u8]>) { + let output_path = self.output_path(subdirectory, extension); + if let Err(err) = fs::write(&output_path, data) { + panic!("Error writing {}: {}", output_path.display(), err); + } + } +} + #[allow(unused_variables)] fn check_targets( + input: &Input, module: &mut naga::Module, - name: &str, targets: Targets, source_code: Option<&str>, ) { - let root = env!("CARGO_MANIFEST_DIR"); - let filepath = format!("{root}/{BASE_DIR_IN}/{name}.param.ron"); - let params = match fs::read_to_string(&filepath) { - Ok(string) => { - ron::de::from_str(&string).expect(&format!("Couldn't parse param file: {}", filepath)) - } - Err(_) => Parameters::default(), - }; + let params = input.read_parameters(); + let name = &input.file_name; let capabilities = if params.god_mode { naga::valid::Capabilities::all() @@ -110,20 +266,21 @@ fn check_targets( naga::valid::Capabilities::default() }; - let dest = PathBuf::from(root).join(BASE_DIR_OUT); - #[cfg(feature = "serialize")] { if targets.contains(Targets::IR) { let config = ron::ser::PrettyConfig::default().new_line("\n".to_string()); let string = ron::ser::to_string_pretty(module, config).unwrap(); - fs::write(dest.join(format!("ir/{name}.ron")), string).unwrap(); + input.write_output_file("ir", "ron", string); } } let info = naga::valid::Validator::new(naga::valid::ValidationFlags::all(), capabilities) .validate(module) - .expect(&format!("Naga module validation failed on test '{name}'")); + .expect(&format!( + "Naga module validation failed on test '{}'", + name.display() + )); #[cfg(feature = "compact")] let info = { @@ -134,14 +291,15 @@ fn check_targets( if targets.contains(Targets::IR) { let config = ron::ser::PrettyConfig::default().new_line("\n".to_string()); let string = ron::ser::to_string_pretty(module, config).unwrap(); - fs::write(dest.join(format!("ir/{name}.compact.ron")), string).unwrap(); + input.write_output_file("ir", "compact.ron", string); } } naga::valid::Validator::new(naga::valid::ValidationFlags::all(), capabilities) .validate(module) .expect(&format!( - "Post-compaction module validation failed on test '{name}'" + "Post-compaction module validation failed on test '{}'", + name.display() )) }; @@ -150,7 +308,7 @@ fn check_targets( if targets.contains(Targets::ANALYSIS) { let config = ron::ser::PrettyConfig::default().new_line("\n".to_string()); let string = ron::ser::to_string_pretty(&info, config).unwrap(); - fs::write(dest.join(format!("analysis/{name}.info.ron")), string).unwrap(); + input.write_output_file("analysis", "info.ron", string); } } @@ -167,10 +325,9 @@ fn check_targets( if targets.contains(Targets::SPIRV) { write_output_spv( + input, module, &info, - &dest, - name, debug_info, ¶ms.spv, params.bounds_check_policies, @@ -181,10 +338,9 @@ fn check_targets( { if targets.contains(Targets::METAL) { write_output_msl( + input, module, &info, - &dest, - name, ¶ms.msl, ¶ms.msl_pipeline, params.bounds_check_policies, @@ -199,10 +355,9 @@ fn check_targets( continue; } write_output_glsl( + input, module, &info, - &dest, - name, ep.stage, &ep.name, ¶ms.glsl, @@ -216,29 +371,28 @@ fn check_targets( { if targets.contains(Targets::DOT) { let string = naga::back::dot::write(module, Some(&info), Default::default()).unwrap(); - fs::write(dest.join(format!("dot/{name}.dot")), string).unwrap(); + input.write_output_file("dot", "dot", string); } } #[cfg(all(feature = "deserialize", feature = "hlsl-out"))] { if targets.contains(Targets::HLSL) { - write_output_hlsl(module, &info, &dest, name, ¶ms.hlsl); + write_output_hlsl(input, module, &info, ¶ms.hlsl); } } #[cfg(all(feature = "deserialize", feature = "wgsl-out"))] { if targets.contains(Targets::WGSL) { - write_output_wgsl(module, &info, &dest, name, ¶ms.wgsl); + write_output_wgsl(input, module, &info, ¶ms.wgsl); } } } #[cfg(feature = "spv-out")] fn write_output_spv( + input: &Input, module: &naga::Module, info: &naga::valid::ModuleInfo, - destination: &Path, - file_name: &str, debug_info: Option, params: &SpirvOutParameters, bounds_check_policies: naga::proc::BoundsCheckPolicies, @@ -278,38 +432,31 @@ fn write_output_spv( shader_stage: ep.stage, }; write_output_spv_inner( + input, module, info, &options, Some(&pipeline_options), - destination, - format!("spv/{}.{}.spvasm", file_name, ep.name), + &format!("{}.spvasm", ep.name), ); } } else { - write_output_spv_inner( - module, - info, - &options, - None, - destination, - format!("spv/{file_name}.spvasm"), - ); + write_output_spv_inner(input, module, info, &options, None, "spvasm"); } } #[cfg(feature = "spv-out")] fn write_output_spv_inner( + input: &Input, module: &naga::Module, info: &naga::valid::ModuleInfo, options: &naga::back::spv::Options<'_>, pipeline_options: Option<&naga::back::spv::PipelineOptions>, - destination: &Path, - path: String, + extension: &str, ) { use naga::back::spv; use rspirv::binary::Disassemble; - println!("Writing SPIR-V {}", path); + println!("Writing SPIR-V for {:?}", input.file_name); let spv = spv::write_vec(module, info, options, pipeline_options).unwrap(); let dis = rspirv::dr::load_words(spv) .expect("Produced invalid SPIR-V") @@ -321,15 +468,14 @@ fn write_output_spv_inner( } else { dis }; - fs::write(destination.join(path), dis).unwrap(); + input.write_output_file("spv", extension, dis); } #[cfg(feature = "msl-out")] fn write_output_msl( + input: &Input, module: &naga::Module, info: &naga::valid::ModuleInfo, - destination: &Path, - file_name: &str, options: &naga::back::msl::Options, pipeline_options: &naga::back::msl::PipelineOptions, bounds_check_policies: naga::proc::BoundsCheckPolicies, @@ -349,16 +495,15 @@ fn write_output_msl( } } - fs::write(destination.join(format!("msl/{file_name}.msl")), string).unwrap(); + input.write_output_file("msl", "msl", string); } #[cfg(feature = "glsl-out")] #[allow(clippy::too_many_arguments)] fn write_output_glsl( + input: &Input, module: &naga::Module, info: &naga::valid::ModuleInfo, - destination: &Path, - file_name: &str, stage: naga::ShaderStage, ep_name: &str, options: &naga::back::glsl::Options, @@ -387,19 +532,15 @@ fn write_output_glsl( .expect("GLSL init failed"); writer.write().expect("GLSL write failed"); - fs::write( - destination.join(format!("glsl/{file_name}.{ep_name}.{stage:?}.glsl")), - buffer, - ) - .unwrap(); + let extension = format!("{ep_name}.{stage:?}.glsl"); + input.write_output_file("glsl", &extension, buffer); } #[cfg(feature = "hlsl-out")] fn write_output_hlsl( + input: &Input, module: &naga::Module, info: &naga::valid::ModuleInfo, - destination: &Path, - file_name: &str, options: &naga::back::hlsl::Options, ) { use naga::back::hlsl; @@ -411,7 +552,7 @@ fn write_output_hlsl( let mut writer = hlsl::Writer::new(&mut buffer, options); let reflection_info = writer.write(module, info).expect("HLSL write failed"); - fs::write(destination.join(format!("hlsl/{file_name}.hlsl")), buffer).unwrap(); + input.write_output_file("hlsl", "hlsl", buffer); // We need a config file for validation script // This file contains an info about profiles (shader stages) contains inside generated shader @@ -437,17 +578,14 @@ fn write_output_hlsl( }); } - config - .to_file(destination.join(format!("hlsl/{file_name}.ron"))) - .unwrap(); + config.to_file(&input.output_path("hlsl", "ron")).unwrap(); } #[cfg(feature = "wgsl-out")] fn write_output_wgsl( + input: &Input, module: &naga::Module, info: &naga::valid::ModuleInfo, - destination: &Path, - file_name: &str, params: &WgslOutParameters, ) { use naga::back::wgsl; @@ -459,7 +597,7 @@ fn write_output_wgsl( let string = wgsl::write_string(module, info, flags).expect("WGSL write failed"); - fs::write(destination.join(format!("wgsl/{file_name}.wgsl")), string).unwrap(); + input.write_output_file("wgsl", "wgsl", string); } #[cfg(feature = "wgsl-in")] @@ -467,7 +605,6 @@ fn write_output_wgsl( fn convert_wgsl() { let _ = env_logger::try_init(); - let root = env!("CARGO_MANIFEST_DIR"); let inputs = [ // TODO: merge array-in-ctor and array-in-function-return-type tests after fix HLSL issue https://github.com/gfx-rs/naga/issues/1930 ( @@ -645,13 +782,12 @@ fn convert_wgsl() { ]; for &(name, targets) in inputs.iter() { - println!("Processing '{name}'"); // WGSL shaders lives in root dir as a privileged. - let file = fs::read_to_string(format!("{root}/{BASE_DIR_IN}/{name}.wgsl")) - .expect("Couldn't find wgsl file"); - match naga::front::wgsl::parse_str(&file) { - Ok(mut module) => check_targets(&mut module, name, targets, None), - Err(e) => panic!("{}", e.emit_to_string(&file)), + let input = Input::new(None, name, "wgsl"); + let source = input.read_source(); + match naga::front::wgsl::parse_str(&source) { + Ok(mut module) => check_targets(&input, &mut module, targets, None), + Err(e) => panic!("{}", e.emit_to_string(&source)), } } @@ -662,13 +798,12 @@ fn convert_wgsl() { ("debug-symbol-terrain", Targets::SPIRV), ]; for &(name, targets) in inputs.iter() { - println!("Processing '{name}'"); // WGSL shaders lives in root dir as a privileged. - let file = fs::read_to_string(format!("{root}/{BASE_DIR_IN}/{name}.wgsl")) - .expect("Couldn't find wgsl file"); - match naga::front::wgsl::parse_str(&file) { - Ok(mut module) => check_targets(&mut module, name, targets, Some(&file)), - Err(e) => panic!("{}", e.emit_to_string(&file)), + let input = Input::new(None, name, "wgsl"); + let source = input.read_source(); + match naga::front::wgsl::parse_str(&source) { + Ok(mut module) => check_targets(&input, &mut module, targets, Some(&source)), + Err(e) => panic!("{}", e.emit_to_string(&source)), } } } @@ -678,11 +813,9 @@ fn convert_wgsl() { fn convert_spv(name: &str, adjust_coordinate_space: bool, targets: Targets) { let _ = env_logger::try_init(); - let root = env!("CARGO_MANIFEST_DIR"); - - println!("Processing '{name}'"); + let input = Input::new(Some("spv"), name, "spv"); let mut module = naga::front::spv::parse_u8_slice( - &fs::read(format!("{root}/{BASE_DIR_IN}/spv/{name}.spv")).expect("Couldn't find spv file"), + &input.read_bytes(), &naga::front::spv::Options { adjust_coordinate_space, strict_capabilities: false, @@ -690,7 +823,7 @@ fn convert_spv(name: &str, adjust_coordinate_space: bool, targets: Targets) { }, ) .unwrap(); - check_targets(&mut module, name, targets, None); + check_targets(&input, &mut module, targets, None); } #[cfg(feature = "spv-in")] @@ -725,9 +858,8 @@ fn convert_spv_all() { #[cfg(feature = "glsl-in")] #[test] fn convert_glsl_variations_check() { - let root = env!("CARGO_MANIFEST_DIR"); - let file = fs::read_to_string(format!("{root}/{BASE_DIR_IN}/variations.glsl")) - .expect("Couldn't find glsl file"); + let input = Input::new(None, "variations", "glsl"); + let source = input.read_source(); let mut parser = naga::front::glsl::Frontend::default(); let mut module = parser .parse( @@ -735,10 +867,10 @@ fn convert_glsl_variations_check() { stage: naga::ShaderStage::Fragment, defines: Default::default(), }, - &file, + &source, ) .unwrap(); - check_targets(&mut module, "variations", Targets::GLSL, None); + check_targets(&input, &mut module, Targets::GLSL, None); } #[cfg(feature = "glsl-in")] @@ -747,23 +879,22 @@ fn convert_glsl_variations_check() { fn convert_glsl_folder() { let _ = env_logger::try_init(); - let root = env!("CARGO_MANIFEST_DIR"); - - for entry in std::fs::read_dir(format!("{root}/{BASE_DIR_IN}/glsl")).unwrap() { - let entry = entry.unwrap(); - let file_name = entry.file_name().into_string().unwrap(); - + for input in Input::files_in_dir("glsl") { + let input = Input { + keep_input_extension: true, + ..input + }; + let file_name = &input.file_name; if file_name.ends_with(".ron") { // No needed to validate ron files continue; } - println!("Processing {file_name}"); let mut parser = naga::front::glsl::Frontend::default(); let module = parser .parse( &naga::front::glsl::Options { - stage: match entry.path().extension().and_then(|s| s.to_str()).unwrap() { + stage: match file_name.extension().and_then(|s| s.to_str()).unwrap() { "vert" => naga::ShaderStage::Vertex, "frag" => naga::ShaderStage::Fragment, "comp" => naga::ShaderStage::Compute, @@ -771,7 +902,7 @@ fn convert_glsl_folder() { }, defines: Default::default(), }, - &fs::read_to_string(entry.path()).expect("Couldn't find glsl file"), + &input.read_source(), ) .unwrap(); @@ -784,14 +915,7 @@ fn convert_glsl_folder() { #[cfg(feature = "wgsl-out")] { - let dest = PathBuf::from(root).join(BASE_DIR_OUT); - write_output_wgsl( - &module, - &info, - &dest, - &file_name, - &WgslOutParameters::default(), - ); + write_output_wgsl(&input, &module, &info, &WgslOutParameters::default()); } } }