Update shader bencher to share some logic with snapshots (#8108)

Co-authored-by: Connor Fitzgerald <connorwadefitzgerald@gmail.com>
This commit is contained in:
Magnus 2025-09-10 19:55:42 -05:00 committed by GitHub
parent d31d944ed5
commit 54ce9f7f98
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 788 additions and 707 deletions

19
Cargo.lock generated
View File

@ -2531,6 +2531,7 @@ dependencies = [
"itertools 0.14.0",
"libm",
"log",
"naga-test",
"num-traits",
"once_cell",
"petgraph 0.8.2",
@ -2542,7 +2543,6 @@ dependencies = [
"spirv",
"strum 0.27.2",
"thiserror 2.0.16",
"toml 0.9.5",
"unicode-ident",
"walkdir",
]
@ -2570,6 +2570,22 @@ dependencies = [
"naga",
]
[[package]]
name = "naga-test"
version = "26.0.0"
dependencies = [
"bitflags 2.9.3",
"env_logger",
"naga",
"ron",
"rspirv",
"serde",
"serde_json",
"spirv",
"toml 0.9.5",
"walkdir",
]
[[package]]
name = "naga-xtask"
version = "0.1.0"
@ -4946,6 +4962,7 @@ dependencies = [
"bytemuck",
"criterion",
"naga",
"naga-test",
"nanorand 0.8.0",
"pollster",
"profiling",

View File

@ -10,6 +10,7 @@ members = [
"examples/standalone/*",
"lock-analyzer",
"naga-cli",
"naga-test",
"naga",
"naga/fuzz",
"naga/hlsl-snapshots",
@ -32,6 +33,7 @@ default-members = [
"examples/standalone/*",
"lock-analyzer",
"naga-cli",
"naga-test",
"naga",
"naga/fuzz",
"naga/hlsl-snapshots",
@ -64,6 +66,7 @@ authors = ["gfx-rs developers"]
[workspace.dependencies]
naga = { version = "26.0.0", path = "./naga" }
naga-test = { version = "26.0.0", path = "./naga-test" }
wgpu = { version = "26.0.0", path = "./wgpu", default-features = false, features = [
"serde",
"wgsl",

View File

@ -41,6 +41,7 @@ naga = { workspace = true, features = [
"glsl-out",
"wgsl-out",
] }
naga-test = { workspace = true, features = [] }
nanorand.workspace = true
pollster.workspace = true
profiling.workspace = true

View File

@ -1,57 +1,56 @@
use criterion::*;
use std::{fs, path::PathBuf, process::Command};
use std::{fs, process::Command};
struct Input {
filename: String,
size: u64,
const DIR_IN: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../naga/tests/in");
use naga_test::*;
struct InputWithInfo {
inner: Input,
data: Vec<u8>,
string: Option<String>,
options: Parameters,
module: Option<naga::Module>,
module_info: Option<naga::valid::ModuleInfo>,
}
impl From<Input> for InputWithInfo {
fn from(value: Input) -> Self {
let mut options = value.read_parameters(DIR_IN);
options.targets = Some(options.targets.unwrap_or(Targets::all()));
Self {
options,
inner: value,
data: Vec::new(),
string: None,
module: None,
module_info: None,
}
}
}
impl InputWithInfo {
fn filename(&self) -> &str {
self.inner.file_name.file_name().unwrap().to_str().unwrap()
}
}
struct Inputs {
inner: Vec<Input>,
inner: Vec<InputWithInfo>,
}
impl Inputs {
#[track_caller]
fn from_dir(folder: &str, extension: &str) -> Self {
let mut inputs = Vec::new();
let read_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join(folder)
.read_dir()
.unwrap();
for file_entry in read_dir {
match file_entry {
Ok(entry) => match entry.path().extension() {
Some(ostr) if ostr == extension => {
let path = entry.path();
inputs.push(Input {
filename: path.to_string_lossy().into_owned(),
size: entry.metadata().unwrap().len(),
string: None,
data: vec![],
module: None,
module_info: None,
});
}
_ => continue,
},
Err(e) => {
eprintln!("Skipping file: {e:?}");
continue;
}
}
}
let inputs: Vec<InputWithInfo> = Input::files_in_dir(folder, &[extension], DIR_IN)
.map(|a| a.into())
.collect();
Self { inner: inputs }
}
fn bytes(&self) -> u64 {
self.inner.iter().map(|input| input.size).sum()
self.inner
.iter()
.map(|input| input.inner.bytes(DIR_IN))
.sum()
}
fn load(&mut self) {
@ -60,7 +59,7 @@ impl Inputs {
continue;
}
input.data = fs::read(&input.filename).unwrap_or_default();
input.data = fs::read(input.inner.input_path(DIR_IN)).unwrap_or_default();
}
}
@ -85,6 +84,8 @@ impl Inputs {
continue;
}
parser.set_options((&input.options.wgsl_in).into());
input.module = Some(parser.parse(input.string.as_ref().unwrap()).unwrap());
}
}
@ -122,22 +123,22 @@ fn parse_glsl(stage: naga::ShaderStage, inputs: &Inputs) {
};
for input in &inputs.inner {
parser
.parse(&options, input.string.as_deref().unwrap())
.parse(&options, &input.inner.read_source(DIR_IN, false))
.unwrap();
}
}
fn get_wgsl_inputs() -> Inputs {
let mut inputs = Inputs::from_dir("../naga/tests/in/wgsl", "wgsl");
let mut inputs: Vec<InputWithInfo> = Input::files_in_dir("wgsl", &["wgsl"], DIR_IN)
.map(|a| a.into())
.collect();
// remove "large-source" tests, they skew the results
inputs
.inner
.retain(|input| !input.filename.contains("large-source"));
inputs.retain(|input| !input.filename().contains("large-source"));
assert!(!inputs.is_empty());
inputs
Inputs { inner: inputs }
}
fn frontends(c: &mut Criterion) {
@ -178,19 +179,20 @@ fn frontends(c: &mut Criterion) {
let mut frontend = naga::front::wgsl::Frontend::new();
b.iter(|| {
for input in &inputs_wgsl.inner {
frontend.set_options((&input.options.wgsl_in).into());
frontend.parse(input.string.as_ref().unwrap()).unwrap();
}
});
});
let inputs_spirv = Inputs::from_dir("../naga/tests/in/spv", "spvasm");
let inputs_spirv = Inputs::from_dir("spv", "spvasm");
assert!(!inputs_spirv.is_empty());
// Assemble all the SPIR-V assembly.
let mut assembled_spirv = Vec::<Vec<u32>>::new();
'spirv: for input in &inputs_spirv.inner {
let output = match Command::new("spirv-as")
.arg(&input.filename)
.arg(input.inner.input_path(DIR_IN))
.arg("-o")
.arg("-")
.output()
@ -220,19 +222,32 @@ fn frontends(c: &mut Criterion) {
let total_bytes = assembled_spirv.iter().map(|spv| spv.len() as u64).sum();
assert!(assembled_spirv.len() == inputs_spirv.inner.len() || assembled_spirv.is_empty());
group.throughput(Throughput::Bytes(total_bytes));
group.bench_function("shader: spv-in", |b| {
b.iter(|| {
let options = naga::front::spv::Options::default();
for input in &assembled_spirv {
let parser = naga::front::spv::Frontend::new(input.iter().cloned(), &options);
for (i, input) in assembled_spirv.iter().enumerate() {
let params = &inputs_spirv.inner[i].options;
let SpirvInParameters {
adjust_coordinate_space,
} = params.spv_in;
let parser = naga::front::spv::Frontend::new(
input.iter().cloned(),
&naga::front::spv::Options {
adjust_coordinate_space,
strict_capabilities: true,
..Default::default()
},
);
parser.parse().unwrap();
}
});
});
let mut inputs_vertex = Inputs::from_dir("../naga/tests/in/glsl", "vert");
let mut inputs_fragment = Inputs::from_dir("../naga/tests/in/glsl", "frag");
let mut inputs_vertex = Inputs::from_dir("glsl", "vert");
let mut inputs_fragment = Inputs::from_dir("glsl", "frag");
assert!(!inputs_vertex.is_empty());
assert!(!inputs_fragment.is_empty());
// let mut inputs_compute = Inputs::from_dir("../naga/tests/in/glsl", "comp");
@ -312,14 +327,16 @@ fn backends(c: &mut Criterion) {
group.bench_function("shader: wgsl-out", |b| {
b.iter(|| {
let mut string = String::new();
let flags = naga::back::wgsl::WriterFlags::empty();
for input in &inputs.inner {
let mut writer = naga::back::wgsl::Writer::new(&mut string, flags);
let _ = writer.write(
input.module.as_ref().unwrap(),
input.module_info.as_ref().unwrap(),
);
string.clear();
if input.options.targets.unwrap().contains(Targets::WGSL) {
let mut writer =
naga::back::wgsl::Writer::new(&mut string, (&input.options.wgsl).into());
let _ = writer.write(
input.module.as_ref().unwrap(),
input.module_info.as_ref().unwrap(),
);
string.clear();
}
}
});
});
@ -327,21 +344,28 @@ fn backends(c: &mut Criterion) {
group.bench_function("shader: spv-out", |b| {
b.iter(|| {
let mut data = Vec::new();
let options = naga::back::spv::Options::default();
let mut writer = naga::back::spv::Writer::new(&Default::default()).unwrap();
for input in &inputs.inner {
if input.filename.contains("pointer-function-arg") {
// These fail due to https://github.com/gfx-rs/wgpu/issues/7315
continue;
if input.options.targets.unwrap().contains(Targets::SPIRV) {
if input.filename().contains("pointer-function-arg") {
// These fail due to https://github.com/gfx-rs/wgpu/issues/7315
continue;
}
let opt = input
.options
.spv
.to_options(input.options.bounds_check_policies, None);
if writer.set_options(&opt).is_ok() {
let _ = writer.write(
input.module.as_ref().unwrap(),
input.module_info.as_ref().unwrap(),
None,
&None,
&mut data,
);
data.clear();
}
}
let mut writer = naga::back::spv::Writer::new(&options).unwrap();
let _ = writer.write(
input.module.as_ref().unwrap(),
input.module_info.as_ref().unwrap(),
None,
&None,
&mut data,
);
data.clear();
}
});
});
@ -350,25 +374,27 @@ fn backends(c: &mut Criterion) {
let mut data = Vec::new();
let options = naga::back::spv::Options::default();
for input in &inputs.inner {
if input.filename.contains("pointer-function-arg") {
// These fail due to https://github.com/gfx-rs/wgpu/issues/7315
continue;
}
let mut writer = naga::back::spv::Writer::new(&options).unwrap();
let module = input.module.as_ref().unwrap();
for ep in module.entry_points.iter() {
let pipeline_options = naga::back::spv::PipelineOptions {
shader_stage: ep.stage,
entry_point: ep.name.clone(),
};
let _ = writer.write(
input.module.as_ref().unwrap(),
input.module_info.as_ref().unwrap(),
Some(&pipeline_options),
&None,
&mut data,
);
data.clear();
if input.options.targets.unwrap().contains(Targets::SPIRV) {
if input.filename().contains("pointer-function-arg") {
// These fail due to https://github.com/gfx-rs/wgpu/issues/7315
continue;
}
let mut writer = naga::back::spv::Writer::new(&options).unwrap();
let module = input.module.as_ref().unwrap();
for ep in module.entry_points.iter() {
let pipeline_options = naga::back::spv::PipelineOptions {
shader_stage: ep.stage,
entry_point: ep.name.clone(),
};
let _ = writer.write(
input.module.as_ref().unwrap(),
input.module_info.as_ref().unwrap(),
Some(&pipeline_options),
&None,
&mut data,
);
data.clear();
}
}
}
});
@ -379,15 +405,17 @@ fn backends(c: &mut Criterion) {
let mut string = String::new();
let options = naga::back::msl::Options::default();
for input in &inputs.inner {
let pipeline_options = naga::back::msl::PipelineOptions::default();
let mut writer = naga::back::msl::Writer::new(&mut string);
let _ = writer.write(
input.module.as_ref().unwrap(),
input.module_info.as_ref().unwrap(),
&options,
&pipeline_options,
);
string.clear();
if input.options.targets.unwrap().contains(Targets::METAL) {
let pipeline_options = naga::back::msl::PipelineOptions::default();
let mut writer = naga::back::msl::Writer::new(&mut string);
let _ = writer.write(
input.module.as_ref().unwrap(),
input.module_info.as_ref().unwrap(),
&options,
&pipeline_options,
);
string.clear();
}
}
});
});
@ -397,15 +425,17 @@ fn backends(c: &mut Criterion) {
let options = naga::back::hlsl::Options::default();
let mut string = String::new();
for input in &inputs.inner {
let pipeline_options = Default::default();
let mut writer =
naga::back::hlsl::Writer::new(&mut string, &options, &pipeline_options);
let _ = writer.write(
input.module.as_ref().unwrap(),
input.module_info.as_ref().unwrap(),
None,
); // may fail on unimplemented things
string.clear();
if input.options.targets.unwrap().contains(Targets::HLSL) {
let pipeline_options = Default::default();
let mut writer =
naga::back::hlsl::Writer::new(&mut string, &options, &pipeline_options);
let _ = writer.write(
input.module.as_ref().unwrap(),
input.module_info.as_ref().unwrap(),
None,
); // may fail on unimplemented things
string.clear();
}
}
});
});
@ -420,6 +450,9 @@ fn backends(c: &mut Criterion) {
zero_initialize_workgroup_memory: true,
};
for input in &inputs.inner {
if !input.options.targets.unwrap().contains(Targets::GLSL) {
continue;
}
let module = input.module.as_ref().unwrap();
let info = input.module_info.as_ref().unwrap();
for ep in module.entry_points.iter() {

39
naga-test/Cargo.toml Normal file
View File

@ -0,0 +1,39 @@
[package]
name = "naga-test"
version.workspace = true
authors.workspace = true
edition.workspace = true
description = "common code for naga tests"
homepage.workspace = true
repository.workspace = true
keywords.workspace = true
license.workspace = true
rust-version.workspace = true
publish = false
[features]
[dependencies]
naga = { workspace = true, features = [
"serialize",
"deserialize",
"glsl-in",
"glsl-out",
"spv-in",
"spv-out",
"wgsl-in",
"wgsl-out",
"msl-out",
"dot-out",
"hlsl-out",
] }
spirv = { workspace = true, features = ["deserialize"] }
rspirv.workspace = true
ron.workspace = true
toml.workspace = true
bitflags.workspace = true
serde_json.workspace = true
serde.workspace = true
walkdir.workspace = true
env_logger.workspace = true

452
naga-test/src/lib.rs Normal file
View File

@ -0,0 +1,452 @@
// A lot of the code can be unused based on configuration flags,
// the corresponding warnings aren't helpful.
#![allow(dead_code, unused_imports)]
use core::fmt::Write;
use std::{
fs,
path::{Path, PathBuf},
};
use naga::compact::KeepUnused;
use ron::de;
bitflags::bitflags! {
#[derive(Clone, Copy, serde::Deserialize)]
#[serde(transparent)]
#[derive(Debug, Eq, PartialEq)]
pub 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;
}
}
impl Targets {
/// Defaults for `spv` and `glsl` snapshots.
pub fn non_wgsl_default() -> Self {
Targets::WGSL
}
/// Defaults for `wgsl` snapshots.
pub fn wgsl_default() -> Self {
Targets::HLSL | Targets::SPIRV | Targets::GLSL | Targets::METAL | Targets::WGSL
}
}
#[derive(serde::Deserialize)]
pub struct SpvOutVersion(pub u8, pub u8);
impl Default for SpvOutVersion {
fn default() -> Self {
SpvOutVersion(1, 1)
}
}
#[derive(serde::Deserialize)]
pub struct BindingMapSerialization {
pub resource_binding: naga::ResourceBinding,
pub bind_target: naga::back::spv::BindingInfo,
}
pub fn deserialize_binding_map<'de, D>(
deserializer: D,
) -> Result<naga::back::spv::BindingMap, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::Deserialize;
let vec = Vec::<BindingMapSerialization>::deserialize(deserializer)?;
let mut map = naga::back::spv::BindingMap::default();
for item in vec {
map.insert(item.resource_binding, item.bind_target);
}
Ok(map)
}
#[derive(Default, serde::Deserialize)]
#[serde(default)]
pub struct WgslInParameters {
pub parse_doc_comments: bool,
}
impl From<&WgslInParameters> for naga::front::wgsl::Options {
fn from(value: &WgslInParameters) -> Self {
Self {
parse_doc_comments: value.parse_doc_comments,
}
}
}
#[derive(Default, serde::Deserialize)]
#[serde(default)]
pub struct SpirvInParameters {
pub adjust_coordinate_space: bool,
}
impl From<&SpirvInParameters> for naga::front::spv::Options {
fn from(value: &SpirvInParameters) -> Self {
Self {
adjust_coordinate_space: value.adjust_coordinate_space,
..Default::default()
}
}
}
#[derive(serde::Deserialize)]
#[serde(default)]
pub struct SpirvOutParameters {
pub version: SpvOutVersion,
pub capabilities: naga::FastHashSet<spirv::Capability>,
pub debug: bool,
pub adjust_coordinate_space: bool,
pub force_point_size: bool,
pub clamp_frag_depth: bool,
pub separate_entry_points: bool,
#[serde(deserialize_with = "deserialize_binding_map")]
pub binding_map: naga::back::spv::BindingMap,
pub use_storage_input_output_16: bool,
}
impl Default for SpirvOutParameters {
fn default() -> Self {
Self {
version: SpvOutVersion::default(),
capabilities: naga::FastHashSet::default(),
debug: false,
adjust_coordinate_space: false,
force_point_size: false,
clamp_frag_depth: false,
separate_entry_points: false,
use_storage_input_output_16: true,
binding_map: naga::back::spv::BindingMap::default(),
}
}
}
impl SpirvOutParameters {
pub fn to_options<'a>(
&'a self,
bounds_check_policies: naga::proc::BoundsCheckPolicies,
debug_info: Option<naga::back::spv::DebugInfo<'a>>,
) -> naga::back::spv::Options<'a> {
use naga::back::spv;
let mut flags = spv::WriterFlags::LABEL_VARYINGS;
flags.set(spv::WriterFlags::DEBUG, self.debug);
flags.set(
spv::WriterFlags::ADJUST_COORDINATE_SPACE,
self.adjust_coordinate_space,
);
flags.set(spv::WriterFlags::FORCE_POINT_SIZE, self.force_point_size);
flags.set(spv::WriterFlags::CLAMP_FRAG_DEPTH, self.clamp_frag_depth);
naga::back::spv::Options {
lang_version: (self.version.0, self.version.1),
flags,
capabilities: if self.capabilities.is_empty() {
None
} else {
Some(self.capabilities.clone())
},
bounds_check_policies,
binding_map: self.binding_map.clone(),
zero_initialize_workgroup_memory: spv::ZeroInitializeWorkgroupMemoryMode::Polyfill,
force_loop_bounding: true,
debug_info,
use_storage_input_output_16: self.use_storage_input_output_16,
}
}
}
#[derive(Default, serde::Deserialize)]
#[serde(default)]
pub struct WgslOutParameters {
pub explicit_types: bool,
}
impl From<&WgslOutParameters> for naga::back::wgsl::WriterFlags {
fn from(value: &WgslOutParameters) -> Self {
let mut flags = Self::empty();
flags.set(Self::EXPLICIT_TYPES, value.explicit_types);
flags
}
}
#[derive(Default, serde::Deserialize)]
pub struct FragmentModule {
pub path: String,
pub entry_point: String,
}
#[derive(Default, serde::Deserialize)]
#[serde(default)]
pub struct Parameters {
// -- GOD MODE --
pub god_mode: bool,
// -- wgsl-in options --
#[serde(rename = "wgsl-in")]
pub wgsl_in: WgslInParameters,
// -- spirv-in options --
#[serde(rename = "spv-in")]
pub spv_in: SpirvInParameters,
// -- SPIR-V options --
pub spv: SpirvOutParameters,
/// Defaults to [`Targets::non_wgsl_default()`] for `spv` and `glsl` snapshots,
/// and [`Targets::wgsl_default()`] for `wgsl` snapshots.
pub targets: Option<Targets>,
// -- MSL options --
pub msl: naga::back::msl::Options,
#[serde(default)]
pub msl_pipeline: naga::back::msl::PipelineOptions,
// -- GLSL options --
pub glsl: naga::back::glsl::Options,
pub glsl_exclude_list: naga::FastHashSet<String>,
pub glsl_multiview: Option<core::num::NonZeroU32>,
// -- HLSL options --
pub hlsl: naga::back::hlsl::Options,
// -- WGSL options --
pub wgsl: WgslOutParameters,
// -- General options --
// Allow backends to be aware of the fragment module.
// Is the name of a WGSL file in the same directory as the test file.
pub fragment_module: Option<FragmentModule>,
pub bounds_check_policies: naga::proc::BoundsCheckPolicies,
pub pipeline_constants: naga::back::PipelineConstants,
}
/// Information about a shader input file.
#[derive(Debug)]
pub 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".
pub subdirectory: PathBuf,
/// The input filename name, without a directory.
pub 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_snapshots_glsl`, 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`.
pub 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.
pub fn new(subdirectory: &str, name: &str, extension: &str) -> Input {
Input {
subdirectory: PathBuf::from(subdirectory),
// 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`.
pub fn files_in_dir<'a>(
subdirectory: &'a str,
file_extensions: &'a [&'a str],
dir_in: &str,
) -> impl Iterator<Item = Input> + 'a {
let input_directory = Path::new(dir_in).join(subdirectory);
let entries = match std::fs::read_dir(&input_directory) {
Ok(entries) => entries,
Err(err) => panic!(
"Error opening directory '{}': {}",
input_directory.display(),
err
),
};
entries.filter_map(move |result| {
let entry = result.expect("error reading directory");
if !entry.file_type().unwrap().is_file() {
return None;
}
let file_name = PathBuf::from(entry.file_name());
let extension = file_name
.extension()
.expect("all files in snapshot input directory should have extensions");
if !file_extensions.contains(&extension.to_str().unwrap()) {
return None;
}
if let Ok(pat) = std::env::var("NAGA_SNAPSHOT") {
if !file_name.to_string_lossy().contains(&pat) {
return None;
}
}
let input = Input::new(
subdirectory,
file_name.file_stem().unwrap().to_str().unwrap(),
extension.to_str().unwrap(),
);
Some(input)
})
}
/// Return the path to the input directory.
pub fn input_directory(&self, dir_in: &str) -> PathBuf {
Path::new(dir_in).join(&self.subdirectory)
}
/// Return the path to the output directory.
pub fn output_directory(subdirectory: &str, dir_out: &str) -> PathBuf {
Path::new(dir_out).join(subdirectory)
}
/// Return the path to the input file.
pub fn input_path(&self, dir_in: &str) -> PathBuf {
let mut input = self.input_directory(dir_in);
input.push(&self.file_name);
input
}
pub fn output_path(&self, subdirectory: &str, extension: &str, dir_out: &str) -> PathBuf {
let mut output = Self::output_directory(subdirectory, dir_out);
if self.keep_input_extension {
let file_name = format!(
"{}-{}.{}",
self.subdirectory.display(),
self.file_name.display(),
extension
);
output.push(&file_name);
} else {
let file_name = format!(
"{}-{}",
self.subdirectory.display(),
self.file_name.display()
);
output.push(&file_name);
output.set_extension(extension);
}
output
}
/// Return the contents of the input file as a string.
pub fn read_source(&self, dir_in: &str, print: bool) -> String {
if print {
println!("Processing '{}'", self.file_name.display());
}
let input_path = self.input_path(dir_in);
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.
pub fn read_bytes(&self, dir_in: &str, print: bool) -> Vec<u8> {
if print {
println!("Processing '{}'", self.file_name.display());
}
let input_path = self.input_path(dir_in);
match fs::read(&input_path) {
Ok(bytes) => bytes,
Err(err) => {
panic!(
"Couldn't read shader input file `{}`: {}",
input_path.display(),
err
);
}
}
}
pub fn bytes(&self, dir_in: &str) -> u64 {
let input_path = self.input_path(dir_in);
std::fs::metadata(input_path).unwrap().len()
}
/// Return this input's parameter file, parsed.
pub fn read_parameters(&self, dir_in: &str) -> Parameters {
let mut param_path = self.input_path(dir_in);
param_path.set_extension("toml");
let mut params = match fs::read_to_string(&param_path) {
Ok(string) => match toml::de::from_str(&string) {
Ok(params) => params,
Err(e) => panic!(
"Couldn't parse param file: {} due to: {e}",
param_path.display()
),
},
Err(_) => Parameters::default(),
};
if params.targets.is_none() {
match self
.input_path(dir_in)
.extension()
.unwrap()
.to_str()
.unwrap()
{
"wgsl" => params.targets = Some(Targets::wgsl_default()),
"spvasm" => params.targets = Some(Targets::non_wgsl_default()),
"vert" | "frag" | "comp" => params.targets = Some(Targets::non_wgsl_default()),
e => {
panic!("Unknown extension: {e}");
}
}
}
params
}
/// Write `data` to a file corresponding to this input file in
/// `subdirectory`, with `extension`.
pub fn write_output_file(
&self,
subdirectory: &str,
extension: &str,
data: impl AsRef<[u8]>,
dir_out: &str,
) {
let output_path = self.output_path(subdirectory, extension, dir_out);
fs::create_dir_all(output_path.parent().unwrap()).unwrap();
if let Err(err) = fs::write(&output_path, data) {
panic!("Error writing {}: {}", output_path.display(), err);
}
}
}

View File

@ -113,12 +113,17 @@ env_logger.workspace = true
hashbrown = { workspace = true, features = ["serde"] }
hlsl-snapshots.workspace = true
itertools.workspace = true
naga-test.workspace = true
ron.workspace = true
rspirv.workspace = true
# So we don't actually need this, however if we remove this, it
# brakes calling `--features spirv` at the workspace level. I think
# this is because there is a `dep:spirv` in the regular feature set,
# so cargo tries to match the feature against that, fails as it's a optional dep,
# and then refuses to build instead of ignoring it.
spirv.workspace = true
serde = { workspace = true, features = ["default", "derive"] }
spirv = { workspace = true, features = ["deserialize"] }
strum = { workspace = true }
toml.workspace = true
walkdir.workspace = true
[lints.clippy]

View File

@ -99,6 +99,24 @@ impl Writer {
})
}
pub fn set_options(&mut self, options: &Options) -> Result<(), Error> {
let (major, minor) = options.lang_version;
if major != 1 {
return Err(Error::UnsupportedVersion(major, minor));
}
self.physical_layout = PhysicalLayout::new(major, minor);
self.capabilities_available = options.capabilities.clone();
self.flags = options.flags;
self.bounds_check_policies = options.bounds_check_policies;
self.zero_initialize_workgroup_memory = options.zero_initialize_workgroup_memory;
self.force_loop_bounding = options.force_loop_bounding;
self.use_storage_input_output_16 = options.use_storage_input_output_16;
self.binding_map = options.binding_map.clone();
self.io_f16_polyfills =
super::f16_polyfill::F16IoPolyfill::new(options.use_storage_input_output_16);
Ok(())
}
/// Returns `(major, minor)` of the SPIR-V language version.
pub const fn lang_version(&self) -> (u8, u8) {
self.physical_layout.lang_version()

View File

@ -50,6 +50,9 @@ impl Frontend {
options,
}
}
pub fn set_options(&mut self, options: Options) {
self.options = options;
}
pub fn parse(&mut self, source: &str) -> core::result::Result<crate::Module, ParseError> {
self.inner(source).map_err(|x| x.as_parse_error(source))

View File

@ -1,407 +1,12 @@
// A lot of the code can be unused based on configuration flags,
// the corresponding warnings aren't helpful.
#![allow(dead_code, unused_imports)]
use core::fmt::Write;
use std::{
fs,
path::{Path, PathBuf},
};
use naga::compact::KeepUnused;
use ron::de;
use naga_test::*;
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, serde::Deserialize)]
#[serde(transparent)]
#[derive(Debug, Eq, PartialEq)]
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;
}
}
impl Targets {
/// Defaults for `spv` and `glsl` snapshots.
fn non_wgsl_default() -> Self {
Targets::WGSL
}
/// Defaults for `wgsl` snapshots.
fn wgsl_default() -> Self {
Targets::HLSL | Targets::SPIRV | Targets::GLSL | Targets::METAL | Targets::WGSL
}
}
#[derive(serde::Deserialize)]
struct SpvOutVersion(u8, u8);
impl Default for SpvOutVersion {
fn default() -> Self {
SpvOutVersion(1, 1)
}
}
#[cfg(all(feature = "deserialize", spv_out))]
#[derive(serde::Deserialize)]
struct BindingMapSerialization {
resource_binding: naga::ResourceBinding,
bind_target: naga::back::spv::BindingInfo,
}
#[cfg(all(feature = "deserialize", spv_out))]
fn deserialize_binding_map<'de, D>(deserializer: D) -> Result<naga::back::spv::BindingMap, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::Deserialize;
let vec = Vec::<BindingMapSerialization>::deserialize(deserializer)?;
let mut map = naga::back::spv::BindingMap::default();
for item in vec {
map.insert(item.resource_binding, item.bind_target);
}
Ok(map)
}
#[derive(Default, serde::Deserialize)]
#[serde(default)]
struct WgslInParameters {
parse_doc_comments: bool,
}
#[derive(Default, serde::Deserialize)]
#[serde(default)]
struct SpirvInParameters {
adjust_coordinate_space: bool,
}
#[derive(serde::Deserialize)]
#[serde(default)]
struct SpirvOutParameters {
version: SpvOutVersion,
capabilities: naga::FastHashSet<spirv::Capability>,
debug: bool,
adjust_coordinate_space: bool,
force_point_size: bool,
clamp_frag_depth: bool,
separate_entry_points: bool,
use_storage_input_output_16: bool,
#[cfg(all(feature = "deserialize", spv_out))]
#[serde(deserialize_with = "deserialize_binding_map")]
binding_map: naga::back::spv::BindingMap,
}
impl Default for SpirvOutParameters {
fn default() -> Self {
Self {
version: SpvOutVersion::default(),
capabilities: naga::FastHashSet::default(),
debug: false,
adjust_coordinate_space: false,
force_point_size: false,
clamp_frag_depth: false,
separate_entry_points: false,
use_storage_input_output_16: true,
#[cfg(all(feature = "deserialize", spv_out))]
binding_map: naga::back::spv::BindingMap::default(),
}
}
}
#[derive(Default, serde::Deserialize)]
#[serde(default)]
struct WgslOutParameters {
explicit_types: bool,
}
#[derive(Default, serde::Deserialize)]
struct FragmentModule {
path: String,
entry_point: String,
}
#[derive(Default, serde::Deserialize)]
#[serde(default)]
struct Parameters {
// -- GOD MODE --
god_mode: bool,
// -- wgsl-in options --
#[serde(rename = "wgsl-in")]
wgsl_in: WgslInParameters,
// -- spirv-in options --
#[serde(rename = "spv-in")]
spv_in: SpirvInParameters,
// -- SPIR-V options --
spv: SpirvOutParameters,
/// Defaults to [`Targets::non_wgsl_default()`] for `spv` and `glsl` snapshots,
/// and [`Targets::wgsl_default()`] for `wgsl` snapshots.
targets: Option<Targets>,
// -- MSL options --
#[cfg(all(feature = "deserialize", msl_out))]
msl: naga::back::msl::Options,
#[cfg(all(feature = "deserialize", msl_out))]
#[serde(default)]
msl_pipeline: naga::back::msl::PipelineOptions,
// -- GLSL options --
#[cfg(all(feature = "deserialize", glsl_out))]
glsl: naga::back::glsl::Options,
glsl_exclude_list: naga::FastHashSet<String>,
#[cfg(all(feature = "deserialize", glsl_out))]
glsl_multiview: Option<core::num::NonZeroU32>,
// -- HLSL options --
#[cfg(all(feature = "deserialize", hlsl_out))]
hlsl: naga::back::hlsl::Options,
// -- WGSL options --
wgsl: WgslOutParameters,
// -- General options --
// Allow backends to be aware of the fragment module.
// Is the name of a WGSL file in the same directory as the test file.
fragment_module: Option<FragmentModule>,
#[cfg(feature = "deserialize")]
bounds_check_policies: naga::proc::BoundsCheckPolicies,
#[cfg(all(feature = "deserialize", any(hlsl_out, msl_out, spv_out, glsl_out)))]
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: PathBuf,
/// 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_snapshots_glsl`, 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: &str, name: &str, extension: &str) -> Input {
Input {
subdirectory: PathBuf::from(subdirectory),
// 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: &'static str,
file_extensions: &'static [&'static str],
) -> impl Iterator<Item = Input> + 'static {
let input_directory = Path::new(CRATE_ROOT).join(BASE_DIR_IN).join(subdirectory);
let entries = match std::fs::read_dir(&input_directory) {
Ok(entries) => entries,
Err(err) => panic!(
"Error opening directory '{}': {}",
input_directory.display(),
err
),
};
entries.filter_map(move |result| {
let entry = result.expect("error reading directory");
if !entry.file_type().unwrap().is_file() {
return None;
}
let file_name = PathBuf::from(entry.file_name());
let extension = file_name
.extension()
.expect("all files in snapshot input directory should have extensions");
if !file_extensions.contains(&extension.to_str().unwrap()) {
return None;
}
if let Ok(pat) = std::env::var("NAGA_SNAPSHOT") {
if !file_name.to_string_lossy().contains(&pat) {
return None;
}
}
let input = Input::new(
subdirectory,
file_name.file_stem().unwrap().to_str().unwrap(),
extension.to_str().unwrap(),
);
Some(input)
})
}
/// Return the path to the input directory.
fn input_directory(&self) -> PathBuf {
let mut dir = Path::new(CRATE_ROOT).join(BASE_DIR_IN);
dir.push(&self.subdirectory);
dir
}
/// Return the path to the output directory.
fn output_directory(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 file_name = format!(
"{}-{}.{}",
self.subdirectory.display(),
self.file_name.display(),
extension
);
output.push(&file_name);
} else {
let file_name = format!(
"{}-{}",
self.subdirectory.display(),
self.file_name.display()
);
output.push(&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<u8> {
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("toml");
let mut params = match fs::read_to_string(&param_path) {
Ok(string) => match toml::de::from_str(&string) {
Ok(params) => params,
Err(e) => panic!(
"Couldn't parse param file: {} due to: {e}",
param_path.display()
),
},
Err(_) => Parameters::default(),
};
if params.targets.is_none() {
match self.input_path().extension().unwrap().to_str().unwrap() {
"wgsl" => params.targets = Some(Targets::wgsl_default()),
"spvasm" => params.targets = Some(Targets::non_wgsl_default()),
"vert" | "frag" | "comp" => params.targets = Some(Targets::non_wgsl_default()),
e => {
panic!("Unknown extension: {e}");
}
}
}
params
}
/// 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);
fs::create_dir_all(output_path.parent().unwrap()).unwrap();
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> = ();
const DIR_IN: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/in");
const DIR_OUT: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/out");
#[allow(unused_variables)]
fn check_targets(input: &Input, module: &mut naga::Module, source_code: Option<&str>) {
let params = input.read_parameters();
let params = input.read_parameters(DIR_IN);
let name = &input.file_name;
let targets = params.targets.unwrap();
@ -420,12 +25,11 @@ fn check_targets(input: &Input, module: &mut naga::Module, source_code: Option<&
)
};
#[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);
input.write_output_file("ir", "ron", string, DIR_OUT);
}
}
@ -456,12 +60,11 @@ fn check_targets(input: &Input, module: &mut naga::Module, source_code: Option<&
// snapshots makes the output independent of unused arena entries.
naga::compact::compact(module, KeepUnused::No);
#[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);
input.write_output_file("ir", "compact.ron", string, DIR_OUT);
}
}
@ -478,130 +81,112 @@ fn check_targets(input: &Input, module: &mut naga::Module, source_code: Option<&
})
};
#[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);
input.write_output_file("analysis", "info.ron", string, DIR_OUT);
}
}
#[cfg(all(feature = "deserialize", spv_out))]
{
if targets.contains(Targets::SPIRV) {
let mut debug_info = None;
if let Some(source_code) = source_code {
debug_info = Some(naga::back::spv::DebugInfo {
source_code,
file_name: name.as_path().into(),
// 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) {
let mut debug_info = None;
if let Some(source_code) = source_code {
debug_info = Some(naga::back::spv::DebugInfo {
source_code,
file_name: name.as_path().into(),
// 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,
})
}
write_output_spv(
write_output_spv(
input,
module,
&info,
debug_info,
&params.spv,
params.bounds_check_policies,
&params.pipeline_constants,
);
}
if targets.contains(Targets::METAL) {
write_output_msl(
input,
module,
&info,
&params.msl,
&params.msl_pipeline,
params.bounds_check_policies,
&params.pipeline_constants,
);
}
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,
debug_info,
&params.spv,
ep.stage,
&ep.name,
&params.glsl,
params.bounds_check_policies,
params.glsl_multiview,
&params.pipeline_constants,
);
}
}
#[cfg(all(feature = "deserialize", msl_out))]
{
if targets.contains(Targets::METAL) {
write_output_msl(
input,
module,
&info,
&params.msl,
&params.msl_pipeline,
params.bounds_check_policies,
&params.pipeline_constants,
if targets.contains(Targets::DOT) {
let string = naga::back::dot::write(module, Some(&info), Default::default()).unwrap();
input.write_output_file("dot", "dot", string, DIR_OUT);
}
if targets.contains(Targets::HLSL) {
let frag_module;
let mut frag_ep = None;
if let Some(ref module_spec) = params.fragment_module {
let full_path = input.input_directory(DIR_IN).join(&module_spec.path);
assert_eq!(
full_path.extension().unwrap().to_string_lossy(),
"wgsl",
"Currently all fragment modules must be in WGSL"
);
}
}
#[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,
&params.glsl,
params.bounds_check_policies,
params.glsl_multiview,
&params.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) {
let frag_module;
let mut frag_ep = None;
if let Some(ref module_spec) = params.fragment_module {
let full_path = input.input_directory().join(&module_spec.path);
assert_eq!(
full_path.extension().unwrap().to_string_lossy(),
"wgsl",
"Currently all fragment modules must be in WGSL"
);
let frag_src = std::fs::read_to_string(full_path).unwrap();
let frag_src = fs::read_to_string(full_path).unwrap();
frag_module =
naga::front::wgsl::parse_str(&frag_src).expect("Failed to parse fragment module");
frag_module = naga::front::wgsl::parse_str(&frag_src)
.expect("Failed to parse fragment module");
frag_ep = Some(
naga::back::hlsl::FragmentEntryPoint::new(
&frag_module,
&module_spec.entry_point,
)
frag_ep = Some(
naga::back::hlsl::FragmentEntryPoint::new(&frag_module, &module_spec.entry_point)
.expect("Could not find fragment entry point"),
);
}
write_output_hlsl(
input,
module,
&info,
&params.hlsl,
&params.pipeline_constants,
frag_ep,
);
}
write_output_hlsl(
input,
module,
&info,
&params.hlsl,
&params.pipeline_constants,
frag_ep,
);
}
#[cfg(all(feature = "deserialize", wgsl_out))]
{
if targets.contains(Targets::WGSL) {
write_output_wgsl(input, module, &info, &params.wgsl);
}
if targets.contains(Targets::WGSL) {
write_output_wgsl(input, module, &info, &params.wgsl);
}
}
#[cfg(spv_out)]
fn write_output_spv(
input: &Input,
module: &naga::Module,
@ -612,32 +197,8 @@ fn write_output_spv(
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,
force_loop_bounding: true,
use_storage_input_output_16: params.use_storage_input_output_16,
debug_info,
};
let options = params.to_options(bounds_check_policies, debug_info);
let (module, info) =
naga::back::pipeline_constants::process_overrides(module, info, None, pipeline_constants)
@ -663,7 +224,6 @@ fn write_output_spv(
}
}
#[cfg(spv_out)]
fn write_output_spv_inner(
input: &Input,
module: &naga::Module,
@ -686,10 +246,9 @@ fn write_output_spv_inner(
} else {
dis
};
input.write_output_file("spv", extension, dis);
input.write_output_file("spv", extension, dis, DIR_OUT);
}
#[cfg(msl_out)]
fn write_output_msl(
input: &Input,
module: &naga::Module,
@ -718,10 +277,9 @@ fn write_output_msl(
}
}
input.write_output_file("msl", "msl", string);
input.write_output_file("msl", "msl", string, DIR_OUT);
}
#[cfg(glsl_out)]
#[allow(clippy::too_many_arguments)]
fn write_output_glsl(
input: &Input,
@ -760,10 +318,9 @@ fn write_output_glsl(
writer.write().expect("GLSL write failed");
let extension = format!("{ep_name}.{stage:?}.glsl");
input.write_output_file("glsl", &extension, buffer);
input.write_output_file("glsl", &extension, buffer, DIR_OUT);
}
#[cfg(hlsl_out)]
fn write_output_hlsl(
input: &Input,
module: &naga::Module,
@ -772,7 +329,6 @@ fn write_output_hlsl(
pipeline_constants: &naga::back::PipelineConstants,
frag_ep: Option<naga::back::hlsl::FragmentEntryPoint>,
) {
use core::fmt::Write as _;
use naga::back::hlsl;
println!("generating HLSL");
@ -788,7 +344,7 @@ fn write_output_hlsl(
.write(&module, &info, frag_ep.as_ref())
.expect("HLSL write failed");
input.write_output_file("hlsl", "hlsl", buffer);
input.write_output_file("hlsl", "hlsl", buffer, DIR_OUT);
// We need a config file for validation script
// This file contains an info about profiles (shader stages) contains inside generated shader
@ -815,10 +371,11 @@ fn write_output_hlsl(
});
}
config.to_file(input.output_path("hlsl", "ron")).unwrap();
config
.to_file(input.output_path("hlsl", "ron", DIR_OUT))
.unwrap();
}
#[cfg(wgsl_out)]
fn write_output_wgsl(
input: &Input,
module: &naga::Module,
@ -829,44 +386,37 @@ fn write_output_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, params.into()).expect("WGSL write failed");
let string = wgsl::write_string(module, info, flags).expect("WGSL write failed");
input.write_output_file("wgsl", "wgsl", string);
input.write_output_file("wgsl", "wgsl", string, DIR_OUT);
}
// While we _can_ run this test under miri, it is extremely slow (>5 minutes),
// and naga isn't the primary target for miri testing, so we disable it.
#[cfg(feature = "wgsl-in")]
#[cfg_attr(miri, ignore)]
#[test]
fn convert_snapshots_wgsl() {
let _ = env_logger::try_init();
for input in Input::files_in_dir("wgsl", &["wgsl"]) {
let source = input.read_source();
for input in Input::files_in_dir("wgsl", &["wgsl"], DIR_IN) {
let source = input.read_source(DIR_IN, true);
// crlf will make the large split output different on different platform
let source = source.replace('\r', "");
let params = input.read_parameters();
let WgslInParameters { parse_doc_comments } = params.wgsl_in;
let params = input.read_parameters(DIR_IN);
let options = naga::front::wgsl::Options { parse_doc_comments };
let mut frontend = naga::front::wgsl::Frontend::new_with_options(options);
let mut frontend = naga::front::wgsl::Frontend::new_with_options((&params.wgsl_in).into());
match frontend.parse(&source) {
Ok(mut module) => check_targets(&input, &mut module, Some(&source)),
Err(e) => panic!(
"{}",
e.emit_to_string_with_path(&source, input.input_path())
e.emit_to_string_with_path(&source, input.input_path(DIR_IN))
),
}
}
}
// miri doesn't allow us to shell out to `spirv-as`
#[cfg(feature = "spv-in")]
#[cfg_attr(miri, ignore)]
#[test]
fn convert_snapshots_spv() {
@ -874,11 +424,11 @@ fn convert_snapshots_spv() {
let _ = env_logger::try_init();
for input in Input::files_in_dir("spv", &["spvasm"]) {
for input in Input::files_in_dir("spv", &["spvasm"], DIR_IN) {
println!("Assembling '{}'", input.file_name.display());
let command = Command::new("spirv-as")
.arg(input.input_path())
.arg(input.input_path(DIR_IN))
.arg("-o")
.arg("-")
.output()
@ -897,20 +447,10 @@ fn convert_snapshots_spv() {
);
}
let params = input.read_parameters();
let SpirvInParameters {
adjust_coordinate_space,
} = params.spv_in;
let params = input.read_parameters(DIR_IN);
let mut module = naga::front::spv::parse_u8_slice(
&command.stdout,
&naga::front::spv::Options {
adjust_coordinate_space,
strict_capabilities: true,
..Default::default()
},
)
.unwrap();
let mut module =
naga::front::spv::parse_u8_slice(&command.stdout, &(&params.spv_in).into()).unwrap();
check_targets(&input, &mut module, None);
}
@ -918,14 +458,13 @@ fn convert_snapshots_spv() {
// While we _can_ run this test under miri, it is extremely slow (>5 minutes),
// and naga isn't the primary target for miri testing, so we disable it.
#[cfg(feature = "glsl-in")]
#[cfg_attr(miri, ignore)]
#[allow(unused_variables)]
#[test]
fn convert_snapshots_glsl() {
let _ = env_logger::try_init();
for input in Input::files_in_dir("glsl", &["vert", "frag", "comp"]) {
for input in Input::files_in_dir("glsl", &["vert", "frag", "comp"], DIR_IN) {
let input = Input {
keep_input_extension: true,
..input
@ -946,7 +485,7 @@ fn convert_snapshots_glsl() {
stage,
defines: Default::default(),
},
&input.read_source(),
&input.read_source(DIR_IN, true),
)
.unwrap();

View File

@ -6,7 +6,6 @@ Test SPIR-V backend capability checks.
use spirv::Capability as Ca;
#[cfg(spv_out)]
use rspirv::binary::Disassemble;
fn capabilities_used(source: &str) -> naga::FastIndexSet<Ca> {
@ -276,7 +275,6 @@ fn f16_io_capabilities() {
assert!(caps_polyfill.contains(&Ca::Float16));
}
#[cfg(spv_out)]
#[test]
fn f16_io_polyfill_codegen() {
let source = r#"

View File

@ -361,7 +361,6 @@ fn builtin_cross_product_args() {
assert!(variant(VectorSize::Quad, 2).is_err());
}
#[cfg(feature = "wgsl-in")]
#[test]
fn incompatible_interpolation_and_sampling_types() {
use dummy_interpolation_shader::DummyInterpolationShader;
@ -443,7 +442,6 @@ fn incompatible_interpolation_and_sampling_types() {
}
}
#[cfg(all(feature = "wgsl-in", feature = "glsl-out"))]
#[test]
fn no_flat_first_in_glsl() {
use dummy_interpolation_shader::DummyInterpolationShader;
@ -484,13 +482,11 @@ fn no_flat_first_in_glsl() {
));
}
#[cfg(all(test, feature = "wgsl-in"))]
mod dummy_interpolation_shader {
pub struct DummyInterpolationShader {
pub source: String,
pub module: naga::Module,
pub interpolate_attr: String,
#[cfg_attr(not(feature = "glsl-out"), expect(dead_code))]
pub entry_point: &'static str,
}
@ -656,7 +652,6 @@ fn binding_arrays_cannot_hold_scalars() {
assert!(t.validator.validate(&t.module).is_err());
}
#[cfg(feature = "wgsl-in")]
#[test]
fn validation_error_messages() {
let cases = [(
@ -693,7 +688,6 @@ error: Function [1] 'main' is invalid
}
}
#[cfg(feature = "wgsl-in")]
#[test]
fn bad_texture_dimensions_level() {
fn validate(level: &str) -> Result<ModuleInfo, naga::valid::ValidationError> {
@ -790,7 +784,6 @@ fn arity_check() {
assert!(validate(Mf::Pow, &[3]).is_err());
}
#[cfg(feature = "wgsl-in")]
#[test]
fn global_use_scalar() {
let source = "
@ -815,7 +808,6 @@ fn main() {
);
}
#[cfg(feature = "wgsl-in")]
#[test]
fn global_use_array() {
let source = "
@ -840,7 +832,6 @@ fn main() {
);
}
#[cfg(feature = "wgsl-in")]
#[test]
fn global_use_array_index() {
let source = "
@ -865,7 +856,6 @@ fn main() {
);
}
#[cfg(feature = "wgsl-in")]
#[test]
fn global_use_phony() {
let source = "
@ -890,7 +880,6 @@ fn main() {
);
}
#[cfg(feature = "wgsl-in")]
#[test]
fn global_use_unreachable() {
// We should allow statements after `return`, and such statements should
@ -924,7 +913,6 @@ fn main() {
/// Parse and validate the module defined in `source`.
///
/// Panics if unsuccessful.
#[cfg(feature = "wgsl-in")]
fn parse_validate(source: &str) -> (Module, ModuleInfo) {
let module = naga::front::wgsl::parse_str(source).expect("module should parse");
let info = valid::Validator::new(Default::default(), valid::Capabilities::all())
@ -948,7 +936,6 @@ fn parse_validate(source: &str) -> (Module, ModuleInfo) {
///
/// The optional `unused_body` can introduce additional objects to the module,
/// to verify that they are adjusted correctly by compaction.
#[cfg(feature = "wgsl-in")]
fn override_test(test_case: &str, unused_body: Option<&str>) {
use hashbrown::HashMap;
use naga::back::pipeline_constants::PipelineConstantError;
@ -1004,7 +991,6 @@ fn unused() {
.unwrap();
}
#[cfg(feature = "wgsl-in")]
#[test]
fn override_in_workgroup_size() {
override_test(
@ -1017,7 +1003,6 @@ fn used() {
);
}
#[cfg(feature = "wgsl-in")]
#[test]
fn override_in_workgroup_size_nested() {
// Initializer for override used in workgroup size refers to another
@ -1034,7 +1019,6 @@ fn used() {
);
}
#[cfg(feature = "wgsl-in")]
#[test]
fn override_in_function() {
override_test(
@ -1052,7 +1036,6 @@ fn used() {
);
}
#[cfg(feature = "wgsl-in")]
#[test]
fn override_in_entrypoint() {
override_test(
@ -1070,7 +1053,6 @@ fn used() {
);
}
#[cfg(feature = "wgsl-in")]
#[test]
fn override_in_array_size() {
override_test(
@ -1086,7 +1068,6 @@ fn used() {
);
}
#[cfg(feature = "wgsl-in")]
#[test]
fn override_in_global_init() {
override_test(
@ -1102,7 +1083,6 @@ fn used() {
);
}
#[cfg(feature = "wgsl-in")]
#[test]
fn override_with_multiple_globals() {
// Test that when compaction of the `unused` entrypoint removes `arr1`, the

View File

@ -4098,7 +4098,6 @@ fn invalid_clip_distances() {
}
}
#[cfg(feature = "wgsl-in")]
#[test]
fn max_type_size_large_array() {
// The total size of an array is not resolved until validation. Type aliases
@ -4114,7 +4113,6 @@ fn max_type_size_large_array() {
}
}
#[cfg(feature = "wgsl-in")]
#[test]
fn max_type_size_array_of_arrays() {
// If the size of the base type of an array is oversize, the error is raised
@ -4129,7 +4127,6 @@ fn max_type_size_array_of_arrays() {
);
}
#[cfg(feature = "wgsl-in")]
#[test]
fn max_type_size_override_array() {
// The validation that occurs after override processing should reject any
@ -4166,7 +4163,6 @@ fn max_type_size_override_array() {
));
}
#[cfg(feature = "wgsl-in")]
#[test]
fn max_type_size_array_in_struct() {
// If a struct member is oversize, the error is raised during lowering.
@ -4189,7 +4185,6 @@ fn max_type_size_array_in_struct() {
);
}
#[cfg(feature = "wgsl-in")]
#[test]
fn max_type_size_two_arrays_in_struct() {
// The total size of a struct is checked during lowering. For a struct,
@ -4215,7 +4210,6 @@ fn max_type_size_two_arrays_in_struct() {
);
}
#[cfg(feature = "wgsl-in")]
#[test]
fn max_type_size_array_of_structs() {
// The total size of an array is not resolved until validation. Type aliases
@ -4236,7 +4230,6 @@ fn max_type_size_array_of_structs() {
}
}
#[cfg(feature = "wgsl-in")]
#[test]
fn source_with_control_char() {
check(