1 #![allow(clippy::manual_strip)]
2 #[allow(unused_imports)]
3 use std::fs;
4 use std::{env, error::Error, path::Path};
5 
6 #[derive(Default)]
7 struct Parameters {
8     validation_flags: naga::valid::ValidationFlags,
9     #[cfg(feature = "spv-in")]
10     spv_adjust_coordinate_space: bool,
11     #[cfg(feature = "spv-in")]
12     spv_flow_dump_prefix: Option<String>,
13     #[cfg(feature = "spv-out")]
14     spv: naga::back::spv::Options,
15     #[cfg(feature = "msl-out")]
16     msl: naga::back::msl::Options,
17     #[cfg(feature = "glsl-out")]
18     glsl: naga::back::glsl::Options,
19 }
20 
21 trait PrettyResult {
22     type Target;
unwrap_pretty(self) -> Self::Target23     fn unwrap_pretty(self) -> Self::Target;
24 }
25 
print_err(error: impl Error)26 fn print_err(error: impl Error) {
27     eprintln!("{}:", error);
28     let mut e = error.source();
29     while let Some(source) = e {
30         eprintln!("\t{}", source);
31         e = source.source();
32     }
33 }
34 
35 impl<T, E: Error> PrettyResult for Result<T, E> {
36     type Target = T;
unwrap_pretty(self) -> T37     fn unwrap_pretty(self) -> T {
38         match self {
39             Result::Ok(value) => value,
40             Result::Err(error) => {
41                 print_err(error);
42                 std::process::exit(1);
43             }
44         }
45     }
46 }
47 
main()48 fn main() {
49     //env_logger::init(); // uncomment during development
50 
51     let mut input_path = None;
52     let mut output_paths = Vec::new();
53     //TODO: read the parameters from RON?
54     #[allow(unused_mut)]
55     let mut params = Parameters::default();
56 
57     let mut args = env::args();
58     let _ = args.next().unwrap();
59     #[allow(clippy::while_let_on_iterator)]
60     while let Some(arg) = args.next() {
61         //TODO: use `strip_prefix` when MSRV reaches 1.45.0
62         if arg.starts_with("--") {
63             match &arg[2..] {
64                 "validate" => {
65                     let value = args.next().unwrap().parse().unwrap();
66                     params.validation_flags =
67                         naga::valid::ValidationFlags::from_bits(value).unwrap();
68                 }
69                 #[cfg(feature = "spv-in")]
70                 "flow-dir" => params.spv_flow_dump_prefix = args.next(),
71                 #[cfg(feature = "glsl-out")]
72                 "entry-point" => params.glsl.entry_point = args.next().unwrap(),
73                 #[cfg(feature = "glsl-out")]
74                 "profile" => {
75                     use naga::back::glsl::Version;
76                     let string = args.next().unwrap();
77                     //TODO: use `strip_prefix` in 1.45.0
78                     params.glsl.version = if string.starts_with("core") {
79                         Version::Desktop(string[4..].parse().unwrap_or(330))
80                     } else if string.starts_with("es") {
81                         Version::Embedded(string[2..].parse().unwrap_or(310))
82                     } else {
83                         panic!("Unknown profile: {}", string)
84                     };
85                 }
86                 other => log::warn!("Unknown parameter: {}", other),
87             }
88         } else if input_path.is_none() {
89             input_path = Some(arg);
90         } else {
91             output_paths.push(arg);
92         }
93     }
94 
95     let input_path = match input_path {
96         Some(ref string) => string,
97         None => {
98             println!("Call with <input> <output> [<options>]");
99             return;
100         }
101     };
102     let module = match Path::new(input_path)
103         .extension()
104         .expect("Input has no extension?")
105         .to_str()
106         .unwrap()
107     {
108         #[cfg(feature = "spv-in")]
109         "spv" => {
110             let options = naga::front::spv::Options {
111                 adjust_coordinate_space: params.spv_adjust_coordinate_space,
112                 strict_capabilities: false,
113                 flow_graph_dump_prefix: params.spv_flow_dump_prefix.map(std::path::PathBuf::from),
114             };
115             let input = fs::read(input_path).unwrap();
116             naga::front::spv::parse_u8_slice(&input, &options).unwrap()
117         }
118         #[cfg(feature = "wgsl-in")]
119         "wgsl" => {
120             let input = fs::read_to_string(input_path).unwrap();
121             let result = naga::front::wgsl::parse_str(&input);
122             match result {
123                 Ok(v) => v,
124                 Err(ref e) => {
125                     e.emit_to_stderr(&input);
126                     panic!("unable to parse WGSL");
127                 }
128             }
129         }
130         #[cfg(feature = "glsl-in")]
131         "vert" => {
132             let input = fs::read_to_string(input_path).unwrap();
133             let mut entry_points = naga::FastHashMap::default();
134             entry_points.insert("main".to_string(), naga::ShaderStage::Vertex);
135             naga::front::glsl::parse_str(
136                 &input,
137                 &naga::front::glsl::Options {
138                     entry_points,
139                     defines: Default::default(),
140                 },
141             )
142             .unwrap_pretty()
143         }
144         #[cfg(feature = "glsl-in")]
145         "frag" => {
146             let input = fs::read_to_string(input_path).unwrap();
147             let mut entry_points = naga::FastHashMap::default();
148             entry_points.insert("main".to_string(), naga::ShaderStage::Fragment);
149             naga::front::glsl::parse_str(
150                 &input,
151                 &naga::front::glsl::Options {
152                     entry_points,
153                     defines: Default::default(),
154                 },
155             )
156             .unwrap_pretty()
157         }
158         #[cfg(feature = "glsl-in")]
159         "comp" => {
160             let input = fs::read_to_string(input_path).unwrap();
161             let mut entry_points = naga::FastHashMap::default();
162             entry_points.insert("main".to_string(), naga::ShaderStage::Compute);
163             naga::front::glsl::parse_str(
164                 &input,
165                 &naga::front::glsl::Options {
166                     entry_points,
167                     defines: Default::default(),
168                 },
169             )
170             .unwrap_pretty()
171         }
172         other => {
173             if true {
174                 // prevent "unreachable_code" warnings
175                 panic!("Unknown input extension: {}", other);
176             }
177             naga::Module::default()
178         }
179     };
180 
181     // validate the IR
182     let info = match naga::valid::Validator::new(
183         params.validation_flags,
184         naga::valid::Capabilities::all(),
185     )
186     .validate(&module)
187     {
188         Ok(info) => Some(info),
189         Err(error) => {
190             print_err(error);
191             None
192         }
193     };
194 
195     if output_paths.is_empty() {
196         if info.is_some() {
197             println!("Validation successful");
198             return;
199         } else {
200             std::process::exit(!0);
201         }
202     }
203 
204     for output_path in output_paths {
205         match Path::new(&output_path)
206             .extension()
207             .expect("Output has no extension?")
208             .to_str()
209             .unwrap()
210         {
211             "txt" => {
212                 use std::io::Write;
213 
214                 let mut file = fs::File::create(output_path).unwrap();
215                 writeln!(file, "{:#?}", module).unwrap();
216                 if let Some(ref info) = info {
217                     writeln!(file).unwrap();
218                     writeln!(file, "{:#?}", info).unwrap();
219                 }
220             }
221             #[cfg(feature = "msl-out")]
222             "metal" => {
223                 use naga::back::msl;
224 
225                 let pipeline_options = msl::PipelineOptions::default();
226                 let (msl, _) = msl::write_string(
227                     &module,
228                     info.as_ref().unwrap(),
229                     &params.msl,
230                     &pipeline_options,
231                 )
232                 .unwrap_pretty();
233                 fs::write(output_path, msl).unwrap();
234             }
235             #[cfg(feature = "spv-out")]
236             "spv" => {
237                 use naga::back::spv;
238 
239                 let spv =
240                     spv::write_vec(&module, info.as_ref().unwrap(), &params.spv).unwrap_pretty();
241                 let bytes = spv
242                     .iter()
243                     .fold(Vec::with_capacity(spv.len() * 4), |mut v, w| {
244                         v.extend_from_slice(&w.to_le_bytes());
245                         v
246                     });
247 
248                 fs::write(output_path, bytes.as_slice()).unwrap();
249             }
250             #[cfg(feature = "glsl-out")]
251             stage @ "vert" | stage @ "frag" | stage @ "comp" => {
252                 use naga::back::glsl;
253 
254                 params.glsl.shader_stage = match stage {
255                     "vert" => naga::ShaderStage::Vertex,
256                     "frag" => naga::ShaderStage::Fragment,
257                     "comp" => naga::ShaderStage::Compute,
258                     _ => unreachable!(),
259                 };
260 
261                 let mut buffer = String::new();
262                 let mut writer =
263                     glsl::Writer::new(&mut buffer, &module, info.as_ref().unwrap(), &params.glsl)
264                         .unwrap_pretty();
265                 writer.write().unwrap();
266                 fs::write(output_path, buffer).unwrap();
267             }
268             #[cfg(feature = "dot-out")]
269             "dot" => {
270                 use naga::back::dot;
271 
272                 let output = dot::write(&module, info.as_ref()).unwrap();
273                 fs::write(output_path, output).unwrap();
274             }
275             #[cfg(feature = "hlsl-out")]
276             "hlsl" => {
277                 use naga::back::hlsl;
278 
279                 let hlsl = hlsl::write_string(&module).unwrap_pretty();
280                 fs::write(output_path, hlsl).unwrap();
281             }
282             #[cfg(feature = "wgsl-out")]
283             "wgsl" => {
284                 use naga::back::wgsl;
285 
286                 let wgsl = wgsl::write_string(&module, info.as_ref().unwrap()).unwrap_pretty();
287                 fs::write(output_path, wgsl).unwrap();
288             }
289             other => {
290                 let _ = params;
291                 println!(
292                     "Unknown output extension: {}, forgot to enable a feature?",
293                     other
294                 );
295             }
296         }
297     }
298 }
299