mirror of
https://github.com/gfx-rs/wgpu.git
synced 2025-12-08 21:26:17 +00:00
452 lines
14 KiB
Rust
452 lines
14 KiB
Rust
use criterion::*;
|
|
use std::{fs, path::PathBuf, process::Command};
|
|
|
|
struct Input {
|
|
filename: String,
|
|
size: u64,
|
|
data: Vec<u8>,
|
|
string: Option<String>,
|
|
module: Option<naga::Module>,
|
|
module_info: Option<naga::valid::ModuleInfo>,
|
|
}
|
|
|
|
struct Inputs {
|
|
inner: Vec<Input>,
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
Self { inner: inputs }
|
|
}
|
|
|
|
fn bytes(&self) -> u64 {
|
|
self.inner.iter().map(|input| input.size).sum()
|
|
}
|
|
|
|
fn load(&mut self) {
|
|
for input in &mut self.inner {
|
|
if !input.data.is_empty() {
|
|
continue;
|
|
}
|
|
|
|
input.data = fs::read(&input.filename).unwrap_or_default();
|
|
}
|
|
}
|
|
|
|
fn load_utf8(&mut self) {
|
|
self.load();
|
|
|
|
for input in &mut self.inner {
|
|
if input.string.is_some() {
|
|
continue;
|
|
}
|
|
|
|
input.string = Some(std::str::from_utf8(&input.data).unwrap().to_string());
|
|
}
|
|
}
|
|
|
|
fn parse(&mut self) {
|
|
self.load_utf8();
|
|
|
|
let mut parser = naga::front::wgsl::Frontend::new();
|
|
for input in &mut self.inner {
|
|
if input.module.is_some() {
|
|
continue;
|
|
}
|
|
|
|
input.module = Some(parser.parse(input.string.as_ref().unwrap()).unwrap());
|
|
}
|
|
}
|
|
|
|
fn validate(&mut self) {
|
|
self.parse();
|
|
|
|
let mut validator = naga::valid::Validator::new(
|
|
naga::valid::ValidationFlags::all(),
|
|
// Note, this is empty, to let all backends work.
|
|
naga::valid::Capabilities::empty(),
|
|
);
|
|
|
|
for input in &mut self.inner {
|
|
if input.module_info.is_some() {
|
|
continue;
|
|
}
|
|
|
|
input.module_info = validator.validate(input.module.as_ref().unwrap()).ok();
|
|
}
|
|
|
|
self.inner.retain(|input| input.module_info.is_some());
|
|
}
|
|
|
|
fn is_empty(&self) -> bool {
|
|
self.inner.is_empty()
|
|
}
|
|
}
|
|
|
|
fn parse_glsl(stage: naga::ShaderStage, inputs: &Inputs) {
|
|
let mut parser = naga::front::glsl::Frontend::default();
|
|
let options = naga::front::glsl::Options {
|
|
stage,
|
|
defines: Default::default(),
|
|
};
|
|
for input in &inputs.inner {
|
|
parser
|
|
.parse(&options, input.string.as_deref().unwrap())
|
|
.unwrap();
|
|
}
|
|
}
|
|
|
|
fn get_wgsl_inputs() -> Inputs {
|
|
let mut inputs = Inputs::from_dir("../naga/tests/in/wgsl", "wgsl");
|
|
|
|
// remove "large-source" tests, they skew the results
|
|
inputs
|
|
.inner
|
|
.retain(|input| !input.filename.contains("large-source"));
|
|
|
|
assert!(!inputs.is_empty());
|
|
|
|
inputs
|
|
}
|
|
|
|
fn frontends(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("front");
|
|
|
|
let mut inputs_wgsl = get_wgsl_inputs();
|
|
|
|
group.throughput(Throughput::Bytes(inputs_wgsl.bytes()));
|
|
group.bench_function("shader: naga module bincode decode", |b| {
|
|
inputs_wgsl.parse();
|
|
|
|
let inputs_bin = inputs_wgsl
|
|
.inner
|
|
.iter()
|
|
.map(|input| {
|
|
bincode::serde::encode_to_vec(
|
|
input.module.as_ref().unwrap(),
|
|
bincode::config::standard(),
|
|
)
|
|
.unwrap()
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
b.iter(move || {
|
|
for input in inputs_bin.iter() {
|
|
bincode::serde::decode_from_slice::<naga::Module, _>(
|
|
input,
|
|
bincode::config::standard(),
|
|
)
|
|
.unwrap();
|
|
}
|
|
});
|
|
});
|
|
|
|
group.bench_function("shader: wgsl-in", |b| {
|
|
inputs_wgsl.load_utf8();
|
|
|
|
let mut frontend = naga::front::wgsl::Frontend::new();
|
|
b.iter(|| {
|
|
for input in &inputs_wgsl.inner {
|
|
frontend.parse(input.string.as_ref().unwrap()).unwrap();
|
|
}
|
|
});
|
|
});
|
|
|
|
let inputs_spirv = Inputs::from_dir("../naga/tests/in/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("-o")
|
|
.arg("-")
|
|
.output()
|
|
{
|
|
Ok(output) => output,
|
|
Err(e) => {
|
|
eprintln!(
|
|
"Failed to execute spirv-as: {e}\n\
|
|
spvasm benchmarks will be skipped.\n\
|
|
spirv-as can be installed by installing the Vulkan SDK and adding \
|
|
it to your PATH.",
|
|
);
|
|
break 'spirv;
|
|
}
|
|
};
|
|
|
|
if !output.status.success() {
|
|
panic!(
|
|
"spirv-as failed: {}\n{}",
|
|
String::from_utf8_lossy(&output.stdout),
|
|
String::from_utf8_lossy(&output.stderr)
|
|
);
|
|
}
|
|
|
|
assembled_spirv.push(bytemuck::pod_collect_to_vec(&output.stdout));
|
|
}
|
|
|
|
let total_bytes = assembled_spirv.iter().map(|spv| spv.len() as u64).sum();
|
|
|
|
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);
|
|
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");
|
|
assert!(!inputs_vertex.is_empty());
|
|
assert!(!inputs_fragment.is_empty());
|
|
// let mut inputs_compute = Inputs::from_dir("../naga/tests/in/glsl", "comp");
|
|
group.throughput(Throughput::Bytes(
|
|
inputs_vertex.bytes() + inputs_fragment.bytes(), // + inputs_compute.bytes()
|
|
));
|
|
group.bench_function("shader: glsl-in", |b| {
|
|
inputs_vertex.load();
|
|
inputs_vertex.load_utf8();
|
|
inputs_fragment.load_utf8();
|
|
// inputs_compute.load_utf8();
|
|
|
|
b.iter(|| parse_glsl(naga::ShaderStage::Vertex, &inputs_vertex));
|
|
b.iter(|| parse_glsl(naga::ShaderStage::Vertex, &inputs_fragment));
|
|
// TODO: This one hangs for some reason
|
|
// b.iter(move || parse_glsl(naga::ShaderStage::Compute, &inputs_compute));
|
|
});
|
|
}
|
|
|
|
fn validation(c: &mut Criterion) {
|
|
let mut inputs = get_wgsl_inputs();
|
|
|
|
let mut group = c.benchmark_group("validate");
|
|
group.throughput(Throughput::Bytes(inputs.bytes()));
|
|
group.bench_function("shader: validation", |b| {
|
|
inputs.load();
|
|
inputs.load_utf8();
|
|
inputs.parse();
|
|
|
|
let mut validator = naga::valid::Validator::new(
|
|
naga::valid::ValidationFlags::all(),
|
|
naga::valid::Capabilities::all(),
|
|
);
|
|
validator
|
|
.subgroup_stages(naga::valid::ShaderStages::all())
|
|
.subgroup_operations(naga::valid::SubgroupOperationSet::all());
|
|
b.iter(|| {
|
|
for input in &inputs.inner {
|
|
validator.validate(input.module.as_ref().unwrap()).unwrap();
|
|
}
|
|
});
|
|
});
|
|
group.finish();
|
|
}
|
|
|
|
fn compact(c: &mut Criterion) {
|
|
use naga::compact::{compact, KeepUnused};
|
|
|
|
let mut inputs = get_wgsl_inputs();
|
|
|
|
inputs.validate();
|
|
assert!(!inputs.is_empty());
|
|
|
|
let mut group = c.benchmark_group("compact");
|
|
group.throughput(Throughput::Bytes(inputs.bytes()));
|
|
group.bench_function("shader: compact", |b| {
|
|
b.iter(|| {
|
|
for input in &mut inputs.inner {
|
|
compact(input.module.as_mut().unwrap(), KeepUnused::No);
|
|
}
|
|
});
|
|
});
|
|
group.finish();
|
|
}
|
|
|
|
fn backends(c: &mut Criterion) {
|
|
let mut inputs = get_wgsl_inputs();
|
|
|
|
let mut group = c.benchmark_group("back");
|
|
// While normally this would be done inside the bench_function callback, we need to
|
|
// run this to properly know the size of the inputs, as any that fail validation
|
|
// will be removed.
|
|
inputs.validate();
|
|
assert!(!inputs.is_empty());
|
|
|
|
group.throughput(Throughput::Bytes(inputs.bytes()));
|
|
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();
|
|
}
|
|
});
|
|
});
|
|
|
|
group.bench_function("shader: spv-out", |b| {
|
|
b.iter(|| {
|
|
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 _ = writer.write(
|
|
input.module.as_ref().unwrap(),
|
|
input.module_info.as_ref().unwrap(),
|
|
None,
|
|
&None,
|
|
&mut data,
|
|
);
|
|
data.clear();
|
|
}
|
|
});
|
|
});
|
|
group.bench_function("shader: spv-out multiple entrypoints", |b| {
|
|
b.iter(|| {
|
|
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();
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
group.bench_function("shader: msl-out", |b| {
|
|
b.iter(|| {
|
|
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();
|
|
}
|
|
});
|
|
});
|
|
|
|
group.bench_function("shader: hlsl-out", |b| {
|
|
b.iter(|| {
|
|
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();
|
|
}
|
|
});
|
|
});
|
|
|
|
group.bench_function("shader: glsl-out multiple entrypoints", |b| {
|
|
b.iter(|| {
|
|
let mut string = String::new();
|
|
let options = naga::back::glsl::Options {
|
|
version: naga::back::glsl::Version::new_gles(320),
|
|
writer_flags: naga::back::glsl::WriterFlags::empty(),
|
|
binding_map: Default::default(),
|
|
zero_initialize_workgroup_memory: true,
|
|
};
|
|
for input in &inputs.inner {
|
|
let module = input.module.as_ref().unwrap();
|
|
let info = input.module_info.as_ref().unwrap();
|
|
for ep in module.entry_points.iter() {
|
|
let pipeline_options = naga::back::glsl::PipelineOptions {
|
|
shader_stage: ep.stage,
|
|
entry_point: ep.name.clone(),
|
|
multiview: None,
|
|
};
|
|
|
|
// might be `Err` if missing features
|
|
if let Ok(mut writer) = naga::back::glsl::Writer::new(
|
|
&mut string,
|
|
module,
|
|
info,
|
|
&options,
|
|
&pipeline_options,
|
|
naga::proc::BoundsCheckPolicies::default(),
|
|
) {
|
|
let _ = writer.write(); // might be `Err` if unsupported
|
|
}
|
|
|
|
string.clear();
|
|
}
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
criterion_group!(shader, frontends, validation, compact, backends);
|