diff --git a/CHANGELOG.md b/CHANGELOG.md index a54b33d29..e01cac176 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,8 @@ Bottom level categories: ### New features +- Added `--shader-stage` and `--input-kind` options to naga-cli for specifying vertex/fragment/compute shaders, and frontend. by @ratmice in [#5411](https://github.com/gfx-rs/wgpu/pull/5411) + #### General - Implemented the `Unorm10_10_10_2` VertexFormat. diff --git a/Cargo.lock b/Cargo.lock index 464acdbf4..fe630da27 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2161,6 +2161,7 @@ dependencies = [ name = "naga-cli" version = "0.19.0" dependencies = [ + "anyhow", "argh", "bincode", "codespan-reporting", diff --git a/Cargo.toml b/Cargo.toml index 2d8e3d435..4134a463d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,7 +68,7 @@ path = "./naga" version = "0.19.2" [workspace.dependencies] -anyhow = "1.0" +anyhow = "1.0.23" arrayvec = "0.7" bit-vec = "0.6" bitflags = "2" diff --git a/naga-cli/Cargo.toml b/naga-cli/Cargo.toml index 1f3549958..98c353619 100644 --- a/naga-cli/Cargo.toml +++ b/naga-cli/Cargo.toml @@ -23,6 +23,7 @@ log = "0.4" codespan-reporting = "0.11" env_logger = "0.11" argh = "0.1.5" +anyhow.workspace = true [dependencies.naga] version = "0.19" diff --git a/naga-cli/src/bin/naga.rs b/naga-cli/src/bin/naga.rs index faeae47e9..7ff086d3f 100644 --- a/naga-cli/src/bin/naga.rs +++ b/naga-cli/src/bin/naga.rs @@ -1,4 +1,5 @@ #![allow(clippy::manual_strip)] +use anyhow::{anyhow, Context as _}; #[allow(unused_imports)] use std::fs; use std::{error::Error, fmt, io::Read, path::Path, str::FromStr}; @@ -62,6 +63,16 @@ struct Args { #[argh(option)] shader_model: Option, + /// the shader stage, for example 'frag', 'vert', or 'compute'. + /// if the shader stage is unspecified it will be derived from + /// the file extension. + #[argh(option)] + shader_stage: Option, + + /// the kind of input, e.g. 'glsl', 'wgsl', 'spv', or 'bin'. + #[argh(option)] + input_kind: Option, + /// the metal version to use, for example, 1.0, 1.1, 1.2, etc. #[argh(option)] metal_version: Option, @@ -170,6 +181,46 @@ impl FromStr for ShaderModelArg { } } +/// Newtype so we can implement [`FromStr`] for `ShaderSource`. +#[derive(Debug, Clone, Copy)] +struct ShaderStage(naga::ShaderStage); + +impl FromStr for ShaderStage { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + use naga::ShaderStage; + Ok(Self(match s.to_lowercase().as_str() { + "frag" | "fragment" => ShaderStage::Fragment, + "comp" | "compute" => ShaderStage::Compute, + "vert" | "vertex" => ShaderStage::Vertex, + _ => return Err(anyhow!("Invalid shader stage: {s}")), + })) + } +} + +/// Input kind/file extension mapping +#[derive(Debug, Clone, Copy)] +enum InputKind { + Bincode, + Glsl, + SpirV, + Wgsl, +} +impl FromStr for InputKind { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + Ok(match s.to_lowercase().as_str() { + "bin" => InputKind::Bincode, + "glsl" => InputKind::Glsl, + "spv" => InputKind::SpirV, + "wgsl" => InputKind::Wgsl, + _ => return Err(anyhow!("Invalid value for --input-kind: {s}")), + }) + } +} + /// Newtype so we can implement [`FromStr`] for [`naga::back::glsl::Version`]. #[derive(Clone, Debug)] struct GlslProfileArg(naga::back::glsl::Version); @@ -247,6 +298,8 @@ struct Parameters<'a> { msl: naga::back::msl::Options, glsl: naga::back::glsl::Options, hlsl: naga::back::hlsl::Options, + input_kind: Option, + shader_stage: Option, } trait PrettyResult { @@ -300,7 +353,7 @@ impl fmt::Display for CliError { } impl std::error::Error for CliError {} -fn run() -> Result<(), Box> { +fn run() -> anyhow::Result<()> { env_logger::init(); // Parse commandline arguments @@ -381,6 +434,9 @@ fn run() -> Result<(), Box> { return Err(CliError("Input file path is not specified").into()); }; + params.input_kind = args.input_kind; + params.shader_stage = args.shader_stage; + let Parsed { mut module, input_text, @@ -500,67 +556,70 @@ struct Parsed { input_text: Option, } -fn parse_input( - input_path: &Path, - input: Vec, - params: &Parameters, -) -> Result> { - let (module, input_text) = match Path::new(&input_path) - .extension() - .ok_or(CliError("Input filename has no extension"))? - .to_str() - .ok_or(CliError("Input filename not valid unicode"))? - { - "bin" => (bincode::deserialize(&input)?, None), - "spv" => naga::front::spv::parse_u8_slice(&input, ¶ms.spv_in).map(|m| (m, None))?, - "wgsl" => { +fn parse_input(input_path: &Path, input: Vec, params: &Parameters) -> anyhow::Result { + let input_kind = match params.input_kind { + Some(kind) => kind, + None => input_path + .extension() + .context("Input filename has no extension")? + .to_str() + .context("Input filename not valid unicode")? + .parse() + .context("Unable to determine --input-kind from filename")?, + }; + + let (module, input_text) = match input_kind { + InputKind::Bincode => (bincode::deserialize(&input)?, None), + InputKind::SpirV => { + naga::front::spv::parse_u8_slice(&input, ¶ms.spv_in).map(|m| (m, None))? + } + InputKind::Wgsl => { let input = String::from_utf8(input)?; let result = naga::front::wgsl::parse_str(&input); match result { Ok(v) => (v, Some(input)), Err(ref e) => { - let message = format!( + let message = anyhow!( "Could not parse WGSL:\n{}", e.emit_to_string_with_path(&input, input_path) ); - return Err(message.into()); + return Err(message); } } } - ext @ ("vert" | "frag" | "comp" | "glsl") => { + InputKind::Glsl => { + let shader_stage = match params.shader_stage { + Some(shader_stage) => shader_stage, + None => { + // filename.shader_stage.glsl -> filename.shader_stage + let file_stem = input_path + .file_stem() + .context("Unable to determine file stem from input filename.")?; + // filename.shader_stage -> shader_stage + let inner_ext = Path::new(file_stem) + .extension() + .context("Unable to determine inner extension from input filename.")? + .to_str() + .context("Input filename not valid unicode")?; + inner_ext.parse().context("from input filename")? + } + }; let input = String::from_utf8(input)?; let mut parser = naga::front::glsl::Frontend::default(); - ( parser .parse( &naga::front::glsl::Options { - stage: match ext { - "vert" => naga::ShaderStage::Vertex, - "frag" => naga::ShaderStage::Fragment, - "comp" => naga::ShaderStage::Compute, - "glsl" => { - let internal_name = input_path.to_string_lossy(); - match Path::new(&internal_name[..internal_name.len()-5]) - .extension() - .ok_or(CliError("Input filename ending with .glsl has no internal extension"))? - .to_str() - .ok_or(CliError("Input filename not valid unicode"))? - { - "vert" => naga::ShaderStage::Vertex, - "frag" => naga::ShaderStage::Fragment, - "comp" => naga::ShaderStage::Compute, - _ => unreachable!(), - } - }, - _ => unreachable!(), - }, + stage: shader_stage.0, defines: Default::default(), }, &input, ) .unwrap_or_else(|error| { - let filename = input_path.file_name().and_then(std::ffi::OsStr::to_str).unwrap_or("glsl"); + let filename = input_path + .file_name() + .and_then(std::ffi::OsStr::to_str) + .unwrap_or("glsl"); let mut writer = StandardStream::stderr(ColorChoice::Auto); error.emit_to_writer_with_path(&mut writer, &input, filename); std::process::exit(1); @@ -568,7 +627,6 @@ fn parse_input( Some(input), ) } - _ => return Err(CliError("Unknown input file extension").into()), }; Ok(Parsed { module, input_text }) @@ -579,7 +637,7 @@ fn write_output( info: &Option, params: &Parameters, output_path: &str, -) -> Result<(), Box> { +) -> anyhow::Result<()> { match Path::new(&output_path) .extension() .ok_or(CliError("Output filename has no extension"))? @@ -744,7 +802,7 @@ fn write_output( Ok(()) } -fn bulk_validate(args: Args, params: &Parameters) -> Result<(), Box> { +fn bulk_validate(args: Args, params: &Parameters) -> anyhow::Result<()> { let mut invalid = vec![]; for input_path in args.files { let path = Path::new(&input_path); @@ -787,7 +845,7 @@ fn bulk_validate(args: Args, params: &Parameters) -> Result<(), Box