shader_sense/validator/
mod.rs

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