shader_sense/validator/
mod.rs

1#[cfg(not(target_os = "wasi"))]
2pub mod dxc;
3pub mod glslang;
4pub mod naga;
5pub mod validator;
6
7#[cfg(test)]
8mod tests {
9    use std::{collections::HashMap, path::Path};
10
11    use crate::shader::{
12        ShaderCompilationParams, ShaderContextParams, ShaderParams, ShaderStage, ShadingLanguage,
13    };
14
15    use super::validator::*;
16    use super::*;
17
18    fn create_test_validator(shading_language: ShadingLanguage) -> Box<dyn ValidatorImpl> {
19        // Do not use Validator::from_shading_language to enforce dxc on PC.
20        match shading_language {
21            ShadingLanguage::Wgsl => Box::new(naga::Naga::new()),
22            #[cfg(not(target_os = "wasi"))]
23            ShadingLanguage::Hlsl => Box::new(dxc::Dxc::new().unwrap()),
24            #[cfg(target_os = "wasi")]
25            ShadingLanguage::Hlsl => Box::new(glslang::Glslang::hlsl()),
26            ShadingLanguage::Glsl => Box::new(glslang::Glslang::glsl()),
27        }
28    }
29
30    #[test]
31    fn glsl_ok() {
32        let validator = create_test_validator(ShadingLanguage::Glsl);
33        let file_path = Path::new("./test/glsl/ok.frag.glsl");
34        let shader_content = std::fs::read_to_string(file_path).unwrap();
35        match validator.validate_shader(
36            &shader_content,
37            file_path,
38            &ShaderParams::default(),
39            &mut default_include_callback,
40        ) {
41            Ok(result) => {
42                println!("Diagnostic should be empty: {:#?}", result);
43                assert!(result.is_empty())
44            }
45            Err(err) => panic!("{}", err),
46        }
47    }
48
49    #[test]
50    fn glsl_include_config() {
51        let validator = create_test_validator(ShadingLanguage::Glsl);
52        let file_path = Path::new("./test/glsl/include-config.frag.glsl");
53        let shader_content = std::fs::read_to_string(file_path).unwrap();
54        match validator.validate_shader(
55            &shader_content,
56            file_path,
57            &ShaderParams {
58                context: ShaderContextParams {
59                    includes: vec!["./test/glsl/inc0/".into()],
60                    ..Default::default()
61                },
62                ..Default::default()
63            },
64            &mut default_include_callback,
65        ) {
66            Ok(result) => {
67                println!("Diagnostic should be empty: {:#?}", result);
68                assert!(result.is_empty())
69            }
70            Err(err) => panic!("{}", err),
71        };
72    }
73
74    #[test]
75    fn glsl_include_level() {
76        let validator = create_test_validator(ShadingLanguage::Glsl);
77        let file_path = Path::new("./test/glsl/include-level.comp.glsl");
78        let shader_content = std::fs::read_to_string(file_path).unwrap();
79        match validator.validate_shader(
80            &shader_content,
81            file_path,
82            &ShaderParams {
83                context: ShaderContextParams {
84                    includes: vec!["./test/glsl/inc0/".into()],
85                    ..Default::default()
86                },
87                ..Default::default()
88            },
89            &mut default_include_callback,
90        ) {
91            Ok(result) => {
92                println!("Diagnostic should be empty: {:#?}", result);
93                assert!(result.is_empty())
94            }
95            Err(err) => panic!("{}", err),
96        };
97    }
98
99    #[test]
100    fn glsl_no_stage() {
101        let validator = create_test_validator(ShadingLanguage::Glsl);
102        let file_path = Path::new("./test/glsl/nostage.glsl");
103        let shader_content = std::fs::read_to_string(file_path).unwrap();
104        match validator.validate_shader(
105            &shader_content,
106            file_path,
107            &ShaderParams {
108                context: ShaderContextParams {
109                    includes: vec!["./test/glsl/inc0/".into()],
110                    ..Default::default()
111                },
112                ..Default::default()
113            },
114            &mut default_include_callback,
115        ) {
116            Ok(result) => {
117                println!("Diagnostic should be empty: {:#?}", result);
118                assert!(result.is_empty())
119            }
120            Err(err) => panic!("{}", err),
121        };
122    }
123
124    #[test]
125    fn glsl_macro() {
126        let validator = create_test_validator(ShadingLanguage::Glsl);
127        let file_path = Path::new("./test/glsl/macro.frag.glsl");
128        let shader_content = std::fs::read_to_string(file_path).unwrap();
129        match validator.validate_shader(
130            &shader_content,
131            file_path,
132            &ShaderParams {
133                context: ShaderContextParams {
134                    defines: HashMap::from([("CUSTOM_MACRO".into(), "42".into())]),
135                    ..Default::default()
136                },
137                ..Default::default()
138            },
139            &mut default_include_callback,
140        ) {
141            Ok(result) => {
142                println!("Diagnostic should be empty: {:#?}", result);
143                assert!(result.is_empty())
144            }
145            Err(err) => panic!("{}", err),
146        };
147    }
148
149    #[test]
150    fn glsl_error_parsing() {
151        let validator = create_test_validator(ShadingLanguage::Glsl);
152        let file_path = Path::new("./test/glsl/error-parsing.frag.glsl");
153        let shader_content = std::fs::read_to_string(file_path).unwrap();
154        match validator.validate_shader(
155            &shader_content,
156            file_path,
157            &ShaderParams::default(),
158            &mut default_include_callback,
159        ) {
160            Ok(result) => {
161                let diags = result.diagnostics;
162                println!("Diagnostic should not be empty: {:#?}", diags);
163                assert!(diags[0].range.file_path.exists());
164                assert_eq!(diags[0].error, String::from(" '#include' : Could not process include directive for header name: ./level1.glsl\n"));
165            }
166            Err(err) => panic!("{}", err),
167        };
168    }
169
170    #[test]
171    fn hlsl_ok() {
172        let validator = create_test_validator(ShadingLanguage::Hlsl);
173        let file_path = Path::new("./test/hlsl/ok.hlsl");
174        let shader_content = std::fs::read_to_string(file_path).unwrap();
175        match validator.validate_shader(
176            &shader_content,
177            file_path,
178            &ShaderParams::default(),
179            &mut default_include_callback,
180        ) {
181            Ok(result) => {
182                println!("Diagnostic should be empty: {:#?}", result);
183                assert!(result.is_empty())
184            }
185            Err(err) => panic!("{}", err),
186        };
187    }
188
189    #[test]
190    fn hlsl_include_config() {
191        let validator = create_test_validator(ShadingLanguage::Hlsl);
192        let file_path = Path::new("./test/hlsl/include-config.hlsl");
193        let shader_content = std::fs::read_to_string(file_path).unwrap();
194        match validator.validate_shader(
195            &shader_content,
196            file_path,
197            &ShaderParams {
198                context: ShaderContextParams {
199                    includes: vec!["./test/hlsl/inc0/".into()],
200                    ..Default::default()
201                },
202                ..Default::default()
203            },
204            &mut default_include_callback,
205        ) {
206            Ok(result) => {
207                println!("Diagnostic should be empty: {:#?}", result);
208                assert!(result.is_empty())
209            }
210            Err(err) => panic!("{}", err),
211        };
212    }
213
214    #[test]
215    fn hlsl_include_parent_folder() {
216        let validator = create_test_validator(ShadingLanguage::Hlsl);
217        let file_path = Path::new("./test/hlsl/folder/folder-file.hlsl");
218        let shader_content = std::fs::read_to_string(file_path).unwrap();
219        match validator.validate_shader(
220            &shader_content,
221            file_path,
222            &ShaderParams {
223                context: ShaderContextParams {
224                    includes: vec!["./test/hlsl/".into()],
225                    ..Default::default()
226                },
227                ..Default::default()
228            },
229            &mut default_include_callback,
230        ) {
231            Ok(result) => {
232                println!("Diagnostic should be empty: {:#?}", result);
233                assert!(result.is_empty())
234            }
235            Err(err) => panic!("{}", err),
236        };
237    }
238
239    #[test]
240    fn hlsl_include_level() {
241        let validator = create_test_validator(ShadingLanguage::Hlsl);
242        let file_path = Path::new("./test/hlsl/include-level.hlsl");
243        let shader_content = std::fs::read_to_string(file_path).unwrap();
244        match validator.validate_shader(
245            &shader_content,
246            file_path,
247            &ShaderParams {
248                context: ShaderContextParams {
249                    includes: vec!["./test/hlsl/inc0/".into()],
250                    ..Default::default()
251                },
252                compilation: ShaderCompilationParams {
253                    entry_point: Some("compute".into()),
254                    shader_stage: Some(ShaderStage::Compute),
255                    ..Default::default()
256                },
257                ..Default::default()
258            },
259            &mut default_include_callback,
260        ) {
261            Ok(result) => {
262                println!("Diagnostic should be empty: {:#?}", result);
263                assert!(result.is_empty())
264            }
265            Err(err) => panic!("{}", err),
266        };
267    }
268
269    #[test]
270    fn hlsl_macro() {
271        let validator = create_test_validator(ShadingLanguage::Hlsl);
272        let file_path = Path::new("./test/hlsl/macro.hlsl");
273        let shader_content = std::fs::read_to_string(file_path).unwrap();
274        match validator.validate_shader(
275            &shader_content,
276            file_path,
277            &ShaderParams {
278                context: ShaderContextParams {
279                    defines: HashMap::from([("CUSTOM_MACRO".into(), "42".into())]),
280                    ..Default::default()
281                },
282                ..Default::default()
283            },
284            &mut default_include_callback,
285        ) {
286            Ok(result) => {
287                println!("Diagnostic should be empty: {:#?}", result);
288                assert!(result.is_empty())
289            }
290            Err(err) => panic!("{}", err),
291        };
292    }
293
294    #[test]
295    #[cfg(not(target_os = "wasi"))] // Somehow glslang fail to enable 16bit types... Disabled for now.
296    fn hlsl_16bits_types_ok() {
297        use crate::shader::HlslCompilationParams;
298
299        let validator = create_test_validator(ShadingLanguage::Hlsl);
300        let file_path = Path::new("./test/hlsl/16bit-types.hlsl");
301        let shader_content = std::fs::read_to_string(file_path).unwrap();
302        match validator.validate_shader(
303            &shader_content,
304            file_path,
305            &ShaderParams {
306                compilation: ShaderCompilationParams {
307                    hlsl: HlslCompilationParams {
308                        enable16bit_types: true,
309                        ..Default::default()
310                    },
311                    ..Default::default()
312                },
313                ..Default::default()
314            },
315            &mut default_include_callback,
316        ) {
317            Ok(result) => {
318                println!("Diagnostic should be empty: {:#?}", result);
319                assert!(result.is_empty())
320            }
321            Err(err) => panic!("{}", err),
322        };
323    }
324
325    #[test]
326    #[cfg(not(target_os = "wasi"))] // Default behaviour of glslang, so ignore
327    fn hlsl_spirv_ok() {
328        use crate::shader::HlslCompilationParams;
329
330        let validator = create_test_validator(ShadingLanguage::Hlsl);
331        let file_path = Path::new("./test/hlsl/spirv-shader.hlsl");
332        let shader_content = std::fs::read_to_string(file_path).unwrap();
333        // Check warning
334        match validator.validate_shader(
335            &shader_content,
336            file_path,
337            &ShaderParams {
338                compilation: ShaderCompilationParams {
339                    hlsl: HlslCompilationParams {
340                        spirv: false,
341                        ..Default::default()
342                    },
343                    ..Default::default()
344                },
345                ..Default::default()
346            },
347            &mut default_include_callback,
348        ) {
349            Ok(result) => {
350                println!("Diagnostic should not be empty: {:#?}", result);
351                assert!(!result.is_empty())
352            }
353            Err(err) => panic!("{}", err),
354        };
355        // Check no warning
356        match validator.validate_shader(
357            &shader_content,
358            file_path,
359            &ShaderParams {
360                compilation: ShaderCompilationParams {
361                    hlsl: HlslCompilationParams {
362                        spirv: true,
363                        ..Default::default()
364                    },
365                    ..Default::default()
366                },
367                ..Default::default()
368            },
369            &mut default_include_callback,
370        ) {
371            Ok(result) => {
372                println!("Diagnostic should be empty: {:#?}", result);
373                assert!(result.is_empty())
374            }
375            Err(err) => panic!("{}", err),
376        };
377    }
378
379    #[test]
380    fn glsl_stages() {
381        #[rustfmt::skip] // Keep them inline
382        let stages = vec![
383            ("graphics.vert.glsl", "VSMain", ShaderStage::Vertex),
384            ("graphics.frag.glsl", "PSMain", ShaderStage::Fragment),
385            ("graphics.geom.glsl", "GSMain", ShaderStage::Geometry),
386            ("graphics.tesc.glsl", "TCSMain", ShaderStage::TesselationControl),
387            ("graphics.tese.glsl", "TESMain", ShaderStage::TesselationEvaluation),
388            ("compute.comp.glsl", "CSMain", ShaderStage::Compute),
389            ("mesh.task.glsl", "TSMain", ShaderStage::Task),
390            ("mesh.mesh.glsl", "MSMain", ShaderStage::Mesh),
391            ("raytracing.rgen.glsl", "RayGenMain", ShaderStage::RayGeneration,),
392            ("raytracing.rint.glsl", "IntersectionMain", ShaderStage::Intersect),
393            ("raytracing.rmiss.glsl", "MissMain", ShaderStage::Miss),
394            ("raytracing.rahit.glsl", "AnyHitMain", ShaderStage::AnyHit),
395            ("raytracing.rchit.glsl", "ClosestHitMain", ShaderStage::ClosestHit),
396            ("raytracing.rcall.glsl", "CallableMain", ShaderStage::Callable),
397        ];
398        let validator = create_test_validator(ShadingLanguage::Glsl);
399        for (file_name, entry_point, shader_stage) in stages {
400            let file_path = Path::new("./test/glsl/stages/").join(file_name);
401            let shader_content = std::fs::read_to_string(&file_path).unwrap();
402            match validator.validate_shader(
403                &shader_content,
404                &file_path,
405                &ShaderParams {
406                    compilation: ShaderCompilationParams {
407                        entry_point: Some(entry_point.into()),
408                        shader_stage: Some(shader_stage),
409                        ..Default::default()
410                    },
411                    ..Default::default()
412                },
413                &mut default_include_callback,
414            ) {
415                Ok(result) => {
416                    println!(
417                        "Diagnostic should be empty for stage {:?}: {:#?}",
418                        shader_stage, result
419                    );
420                    assert!(result.is_empty())
421                }
422                Err(err) => panic!("{}", err),
423            };
424        }
425    }
426
427    #[test]
428    fn hlsl_stages() {
429        #[rustfmt::skip] // Keep them inline
430        let stages = vec![
431            ("graphics.hlsl", "VSMain", ShaderStage::Vertex),
432            ("graphics.hlsl", "PSMain", ShaderStage::Fragment),
433            #[cfg(not(target_os= "wasi"))] // TODO: Find why its failing on WASI.
434            ("graphics.hlsl", "GSMain", ShaderStage::Geometry),
435            ("graphics.hlsl", "HSMain", ShaderStage::TesselationControl),
436            ("graphics.hlsl", "DSMain", ShaderStage::TesselationEvaluation),
437            ("compute.hlsl", "CSMain", ShaderStage::Compute),
438            ("mesh.hlsl", "ASMain", ShaderStage::Task),
439            ("mesh.hlsl", "MSMain", ShaderStage::Mesh),
440            ("raytracing.hlsl", "RayGenMain", ShaderStage::RayGeneration),
441            ("raytracing.hlsl", "IntersectionMain", ShaderStage::Intersect),
442            ("raytracing.hlsl", "MissMain", ShaderStage::Miss),
443            ("raytracing.hlsl", "AnyHitMain", ShaderStage::AnyHit),
444            ("raytracing.hlsl", "ClosestHitMain", ShaderStage::ClosestHit),
445            ("raytracing.hlsl", "CallableMain", ShaderStage::Callable),
446        ];
447        let validator = create_test_validator(ShadingLanguage::Hlsl);
448        for (file_name, entry_point, shader_stage) in stages {
449            let file_path = Path::new("./test/hlsl/stages/").join(file_name);
450            let shader_content = std::fs::read_to_string(&file_path).unwrap();
451            // Check for WASI test.
452            if validator.support(shader_stage) {
453                match validator.validate_shader(
454                    &shader_content,
455                    &file_path,
456                    &ShaderParams {
457                        compilation: ShaderCompilationParams {
458                            entry_point: Some(entry_point.into()),
459                            shader_stage: Some(shader_stage),
460                            ..Default::default()
461                        },
462                        ..Default::default()
463                    },
464                    &mut default_include_callback,
465                ) {
466                    Ok(result) => {
467                        println!(
468                            "Diagnostic should be empty for stage {:?}: {:#?}",
469                            shader_stage, result
470                        );
471                        assert!(result.is_empty())
472                    }
473                    Err(err) => panic!("{}", err),
474                };
475            }
476        }
477    }
478
479    #[test]
480    fn wgsl_stages() {
481        // Wgsl only support three main stages.
482        // Mesh shader stage: https://github.com/gfx-rs/wgpu/issues/7197
483        // Raytracing shader stage: https://github.com/gfx-rs/wgpu/issues/6762
484        // Geometry shader deprecated
485        // Tesselation shader deprecated ?
486        #[rustfmt::skip] // Keep them inline
487        let stages = vec![
488            ("graphics.wgsl", "VSMain", ShaderStage::Vertex),
489            ("graphics.wgsl", "PSMain", ShaderStage::Fragment),
490            ("compute.wgsl", "CSMain", ShaderStage::Compute),
491        ];
492        let validator = create_test_validator(ShadingLanguage::Wgsl);
493        for (file_name, entry_point, shader_stage) in stages {
494            let file_path = Path::new("./test/wgsl/stages/").join(file_name);
495            let shader_content = std::fs::read_to_string(&file_path).unwrap();
496            match validator.validate_shader(
497                &shader_content,
498                &file_path,
499                &ShaderParams {
500                    compilation: ShaderCompilationParams {
501                        entry_point: Some(entry_point.into()),
502                        shader_stage: Some(shader_stage),
503                        ..Default::default()
504                    },
505                    ..Default::default()
506                },
507                &mut default_include_callback,
508            ) {
509                Ok(result) => {
510                    println!(
511                        "Diagnostic should be empty for stage {:?}: {:#?}",
512                        shader_stage, result
513                    );
514                    assert!(result.is_empty())
515                }
516                Err(err) => panic!("{}", err),
517            };
518        }
519    }
520
521    #[test]
522    fn wgsl_ok() {
523        let validator = create_test_validator(ShadingLanguage::Wgsl);
524        let file_path = Path::new("./test/wgsl/ok.wgsl");
525        let shader_content = std::fs::read_to_string(file_path).unwrap();
526        match validator.validate_shader(
527            &shader_content,
528            file_path,
529            &ShaderParams::default(),
530            &mut default_include_callback,
531        ) {
532            Ok(result) => {
533                println!("Diagnostic should be empty: {:#?}", result);
534                assert!(result.is_empty())
535            }
536            Err(err) => panic!("{}", err),
537        };
538    }
539}