// A lot of the code can be unused based on configuration flags, // the corresponding warnings aren't helpful. #![allow(dead_code, unused_imports)] use std::{ fs, 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"; bitflags::bitflags! { #[derive(Clone, Copy)] struct Targets: u32 { /// A serialization of the `naga::Module`, in RON format. const IR = 1; /// A serialization of the `naga::valid::ModuleInfo`, in RON format. const ANALYSIS = 1 << 1; const SPIRV = 1 << 2; const METAL = 1 << 3; const GLSL = 1 << 4; const DOT = 1 << 5; const HLSL = 1 << 6; const WGSL = 1 << 7; const NO_VALIDATION = 1 << 8; } } #[derive(serde::Deserialize)] struct SpvOutVersion(u8, u8); impl Default for SpvOutVersion { fn default() -> Self { SpvOutVersion(1, 1) } } #[derive(Default, serde::Deserialize)] struct SpirvOutParameters { version: SpvOutVersion, #[serde(default)] capabilities: naga::FastHashSet, #[serde(default)] debug: bool, #[serde(default)] adjust_coordinate_space: bool, #[serde(default)] force_point_size: bool, #[serde(default)] clamp_frag_depth: bool, #[serde(default)] separate_entry_points: bool, #[serde(default)] #[cfg(all(feature = "deserialize", spv_out))] binding_map: naga::back::spv::BindingMap, } #[derive(Default, serde::Deserialize)] struct WgslOutParameters { #[serde(default)] explicit_types: bool, } #[derive(Default, serde::Deserialize)] struct Parameters { #[serde(default)] god_mode: bool, #[cfg(feature = "deserialize")] #[serde(default)] bounds_check_policies: naga::proc::BoundsCheckPolicies, #[serde(default)] spv: SpirvOutParameters, #[cfg(all(feature = "deserialize", msl_out))] #[serde(default)] msl: naga::back::msl::Options, #[cfg(all(feature = "deserialize", msl_out))] #[serde(default)] msl_pipeline: naga::back::msl::PipelineOptions, #[cfg(all(feature = "deserialize", glsl_out))] #[serde(default)] glsl: naga::back::glsl::Options, #[serde(default)] glsl_exclude_list: naga::FastHashSet, #[cfg(all(feature = "deserialize", hlsl_out))] #[serde(default)] hlsl: naga::back::hlsl::Options, #[serde(default)] wgsl: WgslOutParameters, #[cfg(all(feature = "deserialize", glsl_out))] #[serde(default)] glsl_multiview: Option, #[cfg(all(feature = "deserialize", any(hlsl_out, msl_out, spv_out, glsl_out)))] #[serde(default)] pipeline_constants: naga::back::PipelineConstants, } /// 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) .unwrap_or_else(|_| panic!("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); } } } #[cfg(hlsl_out)] type FragmentEntryPoint<'a> = naga::back::hlsl::FragmentEntryPoint<'a>; #[cfg(not(hlsl_out))] type FragmentEntryPoint<'a> = (); #[allow(unused_variables)] fn check_targets( input: &Input, module: &mut naga::Module, targets: Targets, source_code: Option<&str>, // For testing hlsl generation when fragment shader doesn't consume all vertex outputs. frag_ep: Option, ) { if frag_ep.is_some() && !targets.contains(Targets::HLSL) { panic!("Providing FragmentEntryPoint only makes sense when testing hlsl-out"); } let params = input.read_parameters(); let name = &input.file_name; let (capabilities, subgroup_stages, subgroup_operations) = if params.god_mode { ( naga::valid::Capabilities::all(), naga::valid::ShaderStages::all(), naga::valid::SubgroupOperationSet::all(), ) } else { ( naga::valid::Capabilities::default(), naga::valid::ShaderStages::empty(), naga::valid::SubgroupOperationSet::empty(), ) }; #[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(); input.write_output_file("ir", "ron", string); } } let validation_flags = if targets.contains(Targets::NO_VALIDATION) { naga::valid::ValidationFlags::empty() } else { naga::valid::ValidationFlags::all() }; let info = naga::valid::Validator::new(validation_flags, capabilities) .subgroup_stages(subgroup_stages) .subgroup_operations(subgroup_operations) .validate(module) .unwrap_or_else(|err| { panic!( "Naga module validation failed on test `{}`:\n{:?}", name.display(), err ); }); #[cfg(feature = "compact")] let info = { naga::compact::compact(module); #[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(); input.write_output_file("ir", "compact.ron", string); } } naga::valid::Validator::new(validation_flags, capabilities) .subgroup_stages(subgroup_stages) .subgroup_operations(subgroup_operations) .validate(module) .unwrap_or_else(|err| { panic!( "Post-compaction module validation failed on test '{}':\n<{:?}", name.display(), err, ) }) }; #[cfg(feature = "serialize")] { 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(); input.write_output_file("analysis", "info.ron", string); } } #[cfg(all(feature = "deserialize", spv_out))] { let debug_info = source_code.map(|code| naga::back::spv::DebugInfo { source_code: code, file_name: name.as_ref(), // wgpu#6266: we technically know all the information here to // produce the valid language but it's not too important for // validation purposes language: naga::back::spv::SourceLanguage::Unknown, }); if targets.contains(Targets::SPIRV) { write_output_spv( input, module, &info, debug_info, ¶ms.spv, params.bounds_check_policies, ¶ms.pipeline_constants, ); } } #[cfg(all(feature = "deserialize", msl_out))] { if targets.contains(Targets::METAL) { write_output_msl( input, module, &info, ¶ms.msl, ¶ms.msl_pipeline, params.bounds_check_policies, ¶ms.pipeline_constants, ); } } #[cfg(all(feature = "deserialize", glsl_out))] { if targets.contains(Targets::GLSL) { for ep in module.entry_points.iter() { if params.glsl_exclude_list.contains(&ep.name) { continue; } write_output_glsl( input, module, &info, ep.stage, &ep.name, ¶ms.glsl, params.bounds_check_policies, params.glsl_multiview, ¶ms.pipeline_constants, ); } } } #[cfg(dot_out)] { if targets.contains(Targets::DOT) { let string = naga::back::dot::write(module, Some(&info), Default::default()).unwrap(); input.write_output_file("dot", "dot", string); } } #[cfg(all(feature = "deserialize", hlsl_out))] { if targets.contains(Targets::HLSL) { write_output_hlsl( input, module, &info, ¶ms.hlsl, ¶ms.pipeline_constants, frag_ep, ); } } #[cfg(all(feature = "deserialize", wgsl_out))] { if targets.contains(Targets::WGSL) { write_output_wgsl(input, module, &info, ¶ms.wgsl); } } } #[cfg(spv_out)] fn write_output_spv( input: &Input, module: &naga::Module, info: &naga::valid::ModuleInfo, debug_info: Option, params: &SpirvOutParameters, bounds_check_policies: naga::proc::BoundsCheckPolicies, pipeline_constants: &naga::back::PipelineConstants, ) { use naga::back::spv; use rspirv::binary::Disassemble; let mut flags = spv::WriterFlags::LABEL_VARYINGS; flags.set(spv::WriterFlags::DEBUG, params.debug); flags.set( spv::WriterFlags::ADJUST_COORDINATE_SPACE, params.adjust_coordinate_space, ); flags.set(spv::WriterFlags::FORCE_POINT_SIZE, params.force_point_size); flags.set(spv::WriterFlags::CLAMP_FRAG_DEPTH, params.clamp_frag_depth); let options = spv::Options { lang_version: (params.version.0, params.version.1), flags, capabilities: if params.capabilities.is_empty() { None } else { Some(params.capabilities.clone()) }, bounds_check_policies, binding_map: params.binding_map.clone(), zero_initialize_workgroup_memory: spv::ZeroInitializeWorkgroupMemoryMode::Polyfill, debug_info, }; let (module, info) = naga::back::pipeline_constants::process_overrides(module, info, pipeline_constants) .expect("override evaluation failed"); if params.separate_entry_points { for ep in module.entry_points.iter() { let pipeline_options = spv::PipelineOptions { entry_point: ep.name.clone(), shader_stage: ep.stage, }; write_output_spv_inner( input, &module, &info, &options, Some(&pipeline_options), &format!("{}.spvasm", ep.name), ); } } else { write_output_spv_inner(input, &module, &info, &options, None, "spvasm"); } } #[cfg(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>, extension: &str, ) { use naga::back::spv; use rspirv::binary::Disassemble; println!("Generating 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") .disassemble(); // HACK escape CR/LF if source code is in side. let dis = if options.debug_info.is_some() { let dis = dis.replace("\\r", "\r"); dis.replace("\\n", "\n") } else { dis }; input.write_output_file("spv", extension, dis); } #[cfg(msl_out)] fn write_output_msl( input: &Input, module: &naga::Module, info: &naga::valid::ModuleInfo, options: &naga::back::msl::Options, pipeline_options: &naga::back::msl::PipelineOptions, bounds_check_policies: naga::proc::BoundsCheckPolicies, pipeline_constants: &naga::back::PipelineConstants, ) { use naga::back::msl; println!("generating MSL"); let (module, info) = naga::back::pipeline_constants::process_overrides(module, info, pipeline_constants) .expect("override evaluation failed"); let mut options = options.clone(); options.bounds_check_policies = bounds_check_policies; let (string, tr_info) = msl::write_string(&module, &info, &options, pipeline_options) .unwrap_or_else(|err| panic!("Metal write failed: {err}")); for (ep, result) in module.entry_points.iter().zip(tr_info.entry_point_names) { if let Err(error) = result { panic!("Failed to translate '{}': {}", ep.name, error); } } input.write_output_file("msl", "msl", string); } #[cfg(glsl_out)] #[allow(clippy::too_many_arguments)] fn write_output_glsl( input: &Input, module: &naga::Module, info: &naga::valid::ModuleInfo, stage: naga::ShaderStage, ep_name: &str, options: &naga::back::glsl::Options, bounds_check_policies: naga::proc::BoundsCheckPolicies, multiview: Option, pipeline_constants: &naga::back::PipelineConstants, ) { use naga::back::glsl; println!("generating GLSL"); let pipeline_options = glsl::PipelineOptions { shader_stage: stage, entry_point: ep_name.to_string(), multiview, }; let mut buffer = String::new(); let (module, info) = naga::back::pipeline_constants::process_overrides(module, info, pipeline_constants) .expect("override evaluation failed"); let mut writer = glsl::Writer::new( &mut buffer, &module, &info, options, &pipeline_options, bounds_check_policies, ) .expect("GLSL init failed"); writer.write().expect("GLSL write failed"); let extension = format!("{ep_name}.{stage:?}.glsl"); input.write_output_file("glsl", &extension, buffer); } #[cfg(hlsl_out)] fn write_output_hlsl( input: &Input, module: &naga::Module, info: &naga::valid::ModuleInfo, options: &naga::back::hlsl::Options, pipeline_constants: &naga::back::PipelineConstants, frag_ep: Option, ) { use naga::back::hlsl; use std::fmt::Write as _; println!("generating HLSL"); let (module, info) = naga::back::pipeline_constants::process_overrides(module, info, pipeline_constants) .expect("override evaluation failed"); let mut buffer = String::new(); let mut writer = hlsl::Writer::new(&mut buffer, options); let reflection_info = writer .write(&module, &info, frag_ep.as_ref()) .expect("HLSL write failed"); 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 // This info will be passed to dxc let mut config = hlsl_snapshots::Config::empty(); for (index, ep) in module.entry_points.iter().enumerate() { let name = match reflection_info.entry_point_names[index] { Ok(ref name) => name, Err(_) => continue, }; match ep.stage { naga::ShaderStage::Vertex => &mut config.vertex, naga::ShaderStage::Fragment => &mut config.fragment, naga::ShaderStage::Compute => &mut config.compute, } .push(hlsl_snapshots::ConfigItem { entry_point: name.clone(), target_profile: format!( "{}_{}", ep.stage.to_hlsl_str(), options.shader_model.to_str() ), }); } config.to_file(input.output_path("hlsl", "ron")).unwrap(); } #[cfg(wgsl_out)] fn write_output_wgsl( input: &Input, module: &naga::Module, info: &naga::valid::ModuleInfo, params: &WgslOutParameters, ) { use naga::back::wgsl; println!("generating WGSL"); let mut flags = wgsl::WriterFlags::empty(); flags.set(wgsl::WriterFlags::EXPLICIT_TYPES, params.explicit_types); let string = wgsl::write_string(module, info, flags).expect("WGSL write failed"); input.write_output_file("wgsl", "wgsl", string); } #[cfg(feature = "wgsl-in")] #[test] fn convert_wgsl() { let _ = env_logger::try_init(); 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 ( "array-in-ctor", Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, ), ( "array-in-function-return-type", Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::WGSL, ), ( "empty", Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, ), ( "quad", Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::DOT | Targets::HLSL | Targets::WGSL, ), ( "bits", Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, ), ( "bitcast", Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, ), ( "boids", Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, ), ( "skybox", Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, ), ( "collatz", Targets::SPIRV | Targets::METAL | Targets::IR | Targets::ANALYSIS | Targets::HLSL | Targets::WGSL, ), ( "shadow", Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, ), ( "image", Targets::SPIRV | Targets::METAL | Targets::HLSL | Targets::WGSL | Targets::GLSL, ), ("extra", Targets::SPIRV | Targets::METAL | Targets::WGSL), ("push-constants", Targets::GLSL | Targets::HLSL), ( "operators", Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, ), ( "functions", Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, ), ( "fragment-output", Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, ), ( "dualsource", Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, ), ("functions-webgl", Targets::GLSL), ( "interpolate", Targets::SPIRV | Targets::METAL | Targets::HLSL | Targets::WGSL, ), ( "interpolate_compat", Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, ), ( "access", Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL | Targets::IR | Targets::ANALYSIS, ), ( "atomicOps", Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, ), ( "atomicCompareExchange", Targets::SPIRV | Targets::METAL | Targets::WGSL, ), ( "padding", Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, ), ( "atomicOps-int64", Targets::SPIRV | Targets::HLSL | Targets::WGSL, ), ( "atomicOps-int64-min-max", Targets::SPIRV | Targets::METAL | Targets::HLSL | Targets::WGSL, ), ( "atomicCompareExchange-int64", Targets::SPIRV | Targets::WGSL, ), ("pointers", Targets::SPIRV | Targets::WGSL), ( "control-flow", Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, ), ( "standard", Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, ), //TODO: GLSL https://github.com/gfx-rs/naga/issues/874 ( "interface", Targets::SPIRV | Targets::METAL | Targets::HLSL | Targets::WGSL, ), ( "globals", Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, ), ("bounds-check-zero", Targets::SPIRV | Targets::METAL), ("bounds-check-zero-atomic", Targets::METAL), ("bounds-check-restrict", Targets::SPIRV | Targets::METAL), ( "bounds-check-image-restrict", Targets::SPIRV | Targets::METAL | Targets::GLSL, ), ( "bounds-check-image-rzsw", Targets::SPIRV | Targets::METAL | Targets::GLSL, ), ("policy-mix", Targets::SPIRV | Targets::METAL), ( "texture-arg", Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, ), ("cubeArrayShadow", Targets::GLSL), ("sample-cube-array-depth-lod", Targets::GLSL), ( "use-gl-ext-over-grad-workaround-if-instructed", Targets::GLSL, ), ("local-const", Targets::IR | Targets::WGSL), ( "math-functions", Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, ), ( "binding-arrays", Targets::WGSL | Targets::HLSL | Targets::METAL | Targets::SPIRV, ), ( "binding-buffer-arrays", Targets::WGSL | Targets::SPIRV, //TODO: more backends, eventually merge into "binding-arrays" ), ("resource-binding-map", Targets::METAL), ("multiview", Targets::SPIRV | Targets::GLSL | Targets::WGSL), ("multiview_webgl", Targets::GLSL), ( "break-if", Targets::WGSL | Targets::GLSL | Targets::SPIRV | Targets::HLSL | Targets::METAL, ), ("lexical-scopes", Targets::WGSL), ("type-alias", Targets::WGSL), ("module-scope", Targets::WGSL), ( "workgroup-var-init", Targets::WGSL | Targets::GLSL | Targets::SPIRV | Targets::HLSL | Targets::METAL, ), ( "workgroup-uniform-load", Targets::WGSL | Targets::GLSL | Targets::SPIRV | Targets::HLSL | Targets::METAL, ), ("runtime-array-in-unused-struct", Targets::SPIRV), ("sprite", Targets::SPIRV), ("force_point_size_vertex_shader_webgl", Targets::GLSL), ("invariant", Targets::GLSL), ("ray-query", Targets::SPIRV | Targets::METAL), ("hlsl-keyword", Targets::HLSL), ( "constructors", Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, ), ("msl-varyings", Targets::METAL), ( "const-exprs", Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, ), ("const_assert", Targets::WGSL | Targets::IR), ("separate-entry-points", Targets::SPIRV | Targets::GLSL), ( "struct-layout", Targets::WGSL | Targets::GLSL | Targets::SPIRV | Targets::HLSL | Targets::METAL, ), ( "f64", Targets::SPIRV | Targets::GLSL | Targets::HLSL | Targets::WGSL, ), ( "abstract-types-const", Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::WGSL, ), ( "abstract-types-var", Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::WGSL, ), ( "abstract-types-operators", Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::WGSL, ), ( "int64", Targets::SPIRV | Targets::HLSL | Targets::WGSL | Targets::METAL, ), ( "subgroup-operations", Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, ), ( "overrides", Targets::IR | Targets::ANALYSIS | Targets::SPIRV | Targets::METAL | Targets::HLSL | Targets::GLSL, ), ( "overrides-atomicCompareExchangeWeak", Targets::IR | Targets::SPIRV | Targets::METAL, ), ( "overrides-ray-query", Targets::IR | Targets::SPIRV | Targets::METAL, ), ("vertex-pulling-transform", Targets::METAL), ( "cross", Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, ), ( "phony_assignment", Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, ), ("6220-break-from-loop", Targets::SPIRV), ("index-by-value", Targets::SPIRV | Targets::IR), ( "6438-conflicting-idents", Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, ), ("diagnostic-filter", Targets::IR), ]; for &(name, targets) in inputs.iter() { // WGSL shaders lives in root dir as a privileged. 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, None), Err(e) => panic!( "{}", e.emit_to_string_with_path(&source, input.input_path()) ), } } { let inputs = [ ("debug-symbol-simple", Targets::SPIRV), ("debug-symbol-terrain", Targets::SPIRV), ("debug-symbol-large-source", Targets::SPIRV), ]; for &(name, targets) in inputs.iter() { // WGSL shaders lives in root dir as a privileged. let input = Input::new(None, name, "wgsl"); let source = input.read_source(); // crlf will make the large split output different on different platform let source = source.replace('\r', ""); match naga::front::wgsl::parse_str(&source) { Ok(mut module) => check_targets(&input, &mut module, targets, Some(&source), None), Err(e) => panic!( "{}", e.emit_to_string_with_path(&source, input.input_path()) ), } } } } #[cfg(all(feature = "wgsl-in", hlsl_out))] #[test] fn unconsumed_vertex_outputs_hlsl_out() { let load_and_parse = |name| { // WGSL shaders lives in root dir as a privileged. let input = Input::new(None, name, "wgsl"); let source = input.read_source(); let module = match naga::front::wgsl::parse_str(&source) { Ok(module) => module, Err(e) => panic!( "{}", e.emit_to_string_with_path(&source, input.input_path()) ), }; (input, module) }; // Uses separate wgsl files to make sure the tested code doesn't accidentally rely on // the fragment entry point being from the same parsed content (e.g. accidentally using the // wrong `Module` when looking up info). We also don't just create a module from the same file // twice since everything would probably be stored behind the same keys. let (input, mut module) = load_and_parse("unconsumed_vertex_outputs_vert"); let (frag_input, mut frag_module) = load_and_parse("unconsumed_vertex_outputs_frag"); let frag_ep = naga::back::hlsl::FragmentEntryPoint::new(&frag_module, "fs_main") .expect("fs_main not found"); check_targets(&input, &mut module, Targets::HLSL, None, Some(frag_ep)); check_targets(&frag_input, &mut frag_module, Targets::HLSL, None, None); } #[cfg(feature = "spv-in")] fn convert_spv(name: &str, adjust_coordinate_space: bool, targets: Targets) { let _ = env_logger::try_init(); let input = Input::new(Some("spv"), name, "spv"); let mut module = naga::front::spv::parse_u8_slice( &input.read_bytes(), &naga::front::spv::Options { adjust_coordinate_space, strict_capabilities: false, block_ctx_dump_prefix: None, }, ) .unwrap(); check_targets(&input, &mut module, targets, None, None); } #[cfg(feature = "spv-in")] #[test] fn convert_spv_all() { convert_spv( "quad-vert", false, Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, ); convert_spv("shadow", true, Targets::IR | Targets::ANALYSIS); convert_spv( "inv-hyperbolic-trig-functions", true, Targets::HLSL | Targets::WGSL, ); convert_spv( "empty-global-name", true, Targets::HLSL | Targets::WGSL | Targets::METAL, ); convert_spv("degrees", false, Targets::empty()); convert_spv("binding-arrays.dynamic", true, Targets::WGSL); convert_spv("binding-arrays.static", true, Targets::WGSL); convert_spv( "do-while", true, Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, ); convert_spv( "unnamed-gl-per-vertex", true, Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, ); convert_spv("builtin-accessed-outside-entrypoint", true, Targets::WGSL); convert_spv("spec-constants", true, Targets::IR); convert_spv("spec-constants-issue-5598", true, Targets::GLSL); convert_spv( "subgroup-operations-s", false, Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, ); convert_spv("atomic_i_increment", false, Targets::IR); convert_spv( "fetch_depth", false, Targets::IR | Targets::SPIRV | Targets::METAL | Targets::HLSL | Targets::WGSL, ); } #[cfg(feature = "glsl-in")] #[test] fn convert_glsl_variations_check() { 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( &naga::front::glsl::Options { stage: naga::ShaderStage::Fragment, defines: Default::default(), }, &source, ) .unwrap(); check_targets(&input, &mut module, Targets::GLSL, None, None); } #[cfg(feature = "glsl-in")] #[allow(unused_variables)] #[test] fn convert_glsl_folder() { let _ = env_logger::try_init(); 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; } let mut parser = naga::front::glsl::Frontend::default(); let module = parser .parse( &naga::front::glsl::Options { stage: match file_name.extension().and_then(|s| s.to_str()).unwrap() { "vert" => naga::ShaderStage::Vertex, "frag" => naga::ShaderStage::Fragment, "comp" => naga::ShaderStage::Compute, ext => panic!("Unknown extension for glsl file {ext}"), }, defines: Default::default(), }, &input.read_source(), ) .unwrap(); let info = naga::valid::Validator::new( naga::valid::ValidationFlags::all(), naga::valid::Capabilities::all(), ) .validate(&module) .unwrap(); #[cfg(wgsl_out)] { write_output_wgsl(&input, &module, &info, &WgslOutParameters::default()); } } }