renderling/
linkage.rs

1//! Provides convenient wrappers around renderling shader linkage.
2//!
3//! For internal use.
4// # Warning!
5// Please don't put anything in `crates/renderling/src/linkage/*`.
6// The files there are all auto-generated by the shader compilation machinery.
7// It is common to delete everything in that directory and regenerate.
8use std::sync::Arc;
9
10pub mod atlas_blit_fragment;
11pub mod atlas_blit_vertex;
12pub mod bloom_downsample_fragment;
13pub mod bloom_mix_fragment;
14pub mod bloom_upsample_fragment;
15pub mod bloom_vertex;
16pub mod brdf_lut_convolution_fragment;
17pub mod brdf_lut_convolution_vertex;
18pub mod compute_copy_depth_to_pyramid;
19pub mod compute_copy_depth_to_pyramid_multisampled;
20pub mod compute_culling;
21pub mod compute_downsample_depth_pyramid;
22pub mod cubemap_sampling_test_fragment;
23pub mod cubemap_sampling_test_vertex;
24pub mod debug_overlay_fragment;
25pub mod debug_overlay_vertex;
26pub mod di_convolution_fragment;
27pub mod generate_mipmap_fragment;
28pub mod generate_mipmap_vertex;
29pub mod light_tiling_bin_lights;
30pub mod light_tiling_clear_tiles;
31pub mod light_tiling_compute_tile_min_and_max_depth;
32pub mod light_tiling_compute_tile_min_and_max_depth_multisampled;
33pub mod light_tiling_depth_pre_pass;
34pub mod prefilter_environment_cubemap_fragment;
35pub mod prefilter_environment_cubemap_vertex;
36pub mod primitive_fragment;
37pub mod primitive_vertex;
38pub mod shadow_mapping_fragment;
39pub mod shadow_mapping_vertex;
40pub mod skybox_cubemap_fragment;
41pub mod skybox_cubemap_vertex;
42pub mod skybox_equirectangular_fragment;
43pub mod skybox_vertex;
44pub mod tonemapping_fragment;
45pub mod tonemapping_vertex;
46
47// Tutorial shaders
48pub mod implicit_isosceles_vertex;
49pub mod passthru_fragment;
50pub mod slabbed_renderlet;
51pub mod slabbed_vertices;
52pub mod slabbed_vertices_no_instance;
53
54pub struct ShaderLinkage {
55    pub module: Arc<wgpu::ShaderModule>,
56    pub entry_point: &'static str,
57}
58
59#[cfg(test)]
60mod test {
61    use naga::valid::ValidationFlags;
62
63    #[test]
64    // Ensure that the shaders can be converted to WGSL.
65    // This is necessary for WASM using WebGPU, because WebGPU only accepts
66    // WGSL as a shading language.
67    fn validate_shaders() {
68        fn validate_src(path: &std::path::PathBuf) {
69            log::info!("validating source");
70            log::info!("  reading '{}'", path.display());
71            let bytes = std::fs::read(path).unwrap();
72            log::info!("  {:0.2}k bytes read", bytes.len() as f32 / 1000.0);
73            let opts = naga::front::spv::Options::default();
74            let module = match naga::front::spv::parse_u8_slice(&bytes, &opts) {
75                Ok(m) => m,
76                Err(e) => {
77                    log::error!("{e}");
78                    panic!("SPIR-V parse error");
79                }
80            };
81            log::info!("  SPIR-V parsed");
82            let mut validator =
83                naga::valid::Validator::new(Default::default(), naga::valid::Capabilities::empty());
84            let is_valid;
85            let info = match validator.validate(&module) {
86                Ok(i) => {
87                    is_valid = true;
88                    log::info!("  SPIR-V validated");
89                    i
90                }
91                Err(e) => {
92                    log::error!("{}", e.emit_to_string(""));
93                    is_valid = false;
94                    let mut validator = naga::valid::Validator::new(
95                        ValidationFlags::empty(),
96                        naga::valid::Capabilities::empty(),
97                    );
98                    validator.validate(&module).unwrap()
99                }
100            };
101            let wgsl = naga::back::wgsl::write_string(
102                &module,
103                &info,
104                naga::back::wgsl::WriterFlags::empty(),
105            )
106            .unwrap();
107            log::info!("  output WGSL generated");
108
109            let print_var_name = path
110                .file_stem()
111                .unwrap()
112                .to_str()
113                .unwrap()
114                .replace('-', "_");
115            let maybe_output_path = if std::env::var("print_wgsl").is_ok() || !is_valid {
116                let dir = std::path::PathBuf::from("../../test_output");
117                std::fs::create_dir_all(&dir).unwrap();
118                let output_path = dir.join(print_var_name).with_extension("wgsl");
119                log::info!("writing WGSL to '{}'", output_path.display());
120                Some(output_path)
121            } else {
122                log::info!("  to save the generated WGSL, use an env var 'print_wgsl=1'");
123                None
124            };
125
126            if let Some(output_path) = maybe_output_path {
127                std::fs::write(&output_path, &wgsl).unwrap();
128                log::info!("  wrote generated WGSL to {}", output_path.display());
129            }
130
131            if !is_valid {
132                panic!("SPIR-V validation error");
133            }
134
135            let module = match naga::front::wgsl::parse_str(&wgsl) {
136                Ok(m) => m,
137                Err(e) => {
138                    log::error!("{}", e.emit_to_string(&wgsl));
139                    panic!("wgsl parse error");
140                }
141            };
142            log::info!("  output WGSL parsed");
143            let mut validator =
144                naga::valid::Validator::new(Default::default(), naga::valid::Capabilities::empty());
145            let _info = match validator.validate(&module) {
146                Ok(i) => i,
147                Err(e) => {
148                    log::error!("{}", e.emit_to_string(&wgsl));
149                    panic!("wgsl validation error");
150                }
151            };
152            log::info!("  wgsl output validated");
153        }
154
155        let may_entries = std::fs::read_dir("src/linkage").unwrap();
156        for may_entry in may_entries {
157            let entry = may_entry.unwrap();
158            let path = entry.path();
159            let ext = path.extension().unwrap().to_str().unwrap();
160            if let Ok(filename) = std::env::var("only_shader") {
161                let stem = path.file_stem().unwrap().to_str().unwrap();
162                if filename != stem {
163                    log::info!(
164                        "  '{}' doesn't match 'only_shader' env '{}', skipping",
165                        filename,
166                        stem
167                    );
168                    continue;
169                }
170            }
171            if ext == "spv" {
172                validate_src(&path);
173            }
174        }
175    }
176}