1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 extern crate webrender_build;
6 
7 use std::borrow::Cow;
8 use std::env;
9 use std::fs::{canonicalize, read_dir, File};
10 use std::io::prelude::*;
11 use std::path::{Path, PathBuf};
12 use std::collections::hash_map::DefaultHasher;
13 use std::hash::Hasher;
14 use webrender_build::shader::*;
15 use webrender_build::shader_features::{ShaderFeatureFlags, get_shader_features};
16 
17 // glsopt is known to leak, but we don't particularly care.
18 #[no_mangle]
__lsan_default_options() -> *const u819 pub extern "C" fn __lsan_default_options() -> *const u8 {
20     b"detect_leaks=0\0".as_ptr()
21 }
22 
23 /// Compute the shader path for insertion into the include_str!() macro.
24 /// This makes for more compact generated code than inserting the literal
25 /// shader source into the generated file.
26 ///
27 /// If someone is building on a network share, I'm sorry.
escape_include_path(path: &Path) -> String28 fn escape_include_path(path: &Path) -> String {
29     let full_path = canonicalize(path).unwrap();
30     let full_name = full_path.as_os_str().to_str().unwrap();
31     let full_name = full_name.replace("\\\\?\\", "");
32     let full_name = full_name.replace("\\", "/");
33 
34     full_name
35 }
36 
37 fn write_unoptimized_shaders(mut glsl_files: Vec<PathBuf>, shader_file: &mut File) -> Result<(), std::io::Error> {
38     writeln!(
39         shader_file,
40         "  pub static ref UNOPTIMIZED_SHADERS: HashMap<&'static str, SourceWithDigest> = {{"
41     )?;
42     writeln!(shader_file, "    let mut shaders = HashMap::new();")?;
43 
44     // Sort the file list so that the shaders.rs file is filled
45     // deterministically.
46     glsl_files.sort_by(|a, b| a.file_name().cmp(&b.file_name()));
47 
48     for glsl in glsl_files {
49         // Compute the shader name.
50         assert!(glsl.is_file());
51         let shader_name = glsl.file_name().unwrap().to_str().unwrap();
52         let shader_name = shader_name.replace(".glsl", "");
53 
54         // Compute a digest of the #include-expanded shader source. We store
55         // this as a literal alongside the source string so that we don't need
56         // to hash large strings at runtime.
57         let mut hasher = DefaultHasher::new();
58         let base = glsl.parent().unwrap();
59         assert!(base.is_dir());
60         ShaderSourceParser::new().parse(
61             Cow::Owned(shader_source_from_file(&glsl)),
62             &|f| Cow::Owned(shader_source_from_file(&base.join(&format!("{}.glsl", f)))),
63             &mut |s| hasher.write(s.as_bytes()),
64         );
65         let digest: ProgramSourceDigest = hasher.into();
66 
67         writeln!(
68             shader_file,
69             "    shaders.insert(\"{}\", SourceWithDigest {{ source: include_str!(\"{}\"), digest: \"{}\"}});",
70             shader_name,
71             escape_include_path(&glsl),
72             digest,
73         )?;
74     }
75     writeln!(shader_file, "    shaders")?;
76     writeln!(shader_file, "  }};")?;
77 
78     Ok(())
79 }
80 
81 #[derive(Clone, Debug)]
82 struct ShaderOptimizationInput {
83     shader_name: &'static str,
84     config: String,
85     gl_version: ShaderVersion,
86 }
87 
88 #[derive(Debug)]
89 struct ShaderOptimizationOutput {
90     full_shader_name: String,
91     gl_version: ShaderVersion,
92     vert_file_path: PathBuf,
93     frag_file_path: PathBuf,
94     digest: ProgramSourceDigest,
95 }
96 
97 #[derive(Debug)]
98 struct ShaderOptimizationError {
99     shader: ShaderOptimizationInput,
100     message: String,
101 }
102 
print_shader_source(shader_src: &str)103 fn print_shader_source(shader_src: &str) {
104     // For some reason the glsl-opt errors are offset by 1 compared
105     // to the provided shader source string.
106     println!("0\t|");
107     for (n, line) in shader_src.split('\n').enumerate() {
108         let line_number = n + 1;
109         println!("{}\t|{}", line_number, line);
110     }
111 }
112 
write_optimized_shaders(shader_dir: &Path, shader_file: &mut File, out_dir: &str) -> Result<(), std::io::Error>113 fn write_optimized_shaders(shader_dir: &Path, shader_file: &mut File, out_dir: &str) -> Result<(), std::io::Error> {
114     writeln!(
115         shader_file,
116         "  pub static ref OPTIMIZED_SHADERS: HashMap<(ShaderVersion, &'static str), OptimizedSourceWithDigest> = {{"
117     )?;
118     writeln!(shader_file, "    let mut shaders = HashMap::new();")?;
119 
120     // The full set of optimized shaders can be quite large, so only optimize
121     // for the GL version we expect to be used on the target platform. If a different GL
122     // version is used we will simply fall back to the unoptimized shaders.
123     let shader_versions = match env::var("CARGO_CFG_TARGET_OS").as_ref().map(|s| &**s) {
124         Ok("android") | Ok("windows") => [ShaderVersion::Gles],
125         _ => [ShaderVersion::Gl],
126     };
127 
128     let mut shaders = Vec::default();
129     for &gl_version in &shader_versions {
130         let mut flags = ShaderFeatureFlags::all();
131         if gl_version != ShaderVersion::Gl {
132             flags.remove(ShaderFeatureFlags::GL);
133         }
134         if gl_version != ShaderVersion::Gles {
135             flags.remove(ShaderFeatureFlags::GLES);
136             flags.remove(ShaderFeatureFlags::TEXTURE_EXTERNAL);
137         }
138         if !matches!(env::var("CARGO_CFG_TARGET_OS").as_ref().map(|s| &**s), Ok("android")) {
139             flags.remove(ShaderFeatureFlags::TEXTURE_EXTERNAL_ESSL1);
140         }
141         flags.remove(ShaderFeatureFlags::DITHERING);
142 
143         for (shader_name, configs) in get_shader_features(flags) {
144             for config in configs {
145                 shaders.push(ShaderOptimizationInput {
146                     shader_name,
147                     config,
148                     gl_version,
149                 });
150             }
151         }
152     }
153 
154     let outputs = build_parallel::compile_objects(&|shader: &ShaderOptimizationInput| {
155         println!("Optimizing shader {:?}", shader);
156         let target = match shader.gl_version {
157             ShaderVersion::Gl => glslopt::Target::OpenGl,
158             ShaderVersion::Gles => glslopt::Target::OpenGles30,
159         };
160         let glslopt_ctx = glslopt::Context::new(target);
161 
162         let features = shader.config.split(",").filter(|f| !f.is_empty()).collect::<Vec<_>>();
163 
164         let (vert_src, frag_src) = build_shader_strings(
165             shader.gl_version,
166             &features,
167             shader.shader_name,
168             &|f| Cow::Owned(shader_source_from_file(&shader_dir.join(&format!("{}.glsl", f)))),
169         );
170 
171         let full_shader_name = if shader.config.is_empty() {
172             shader.shader_name.to_string()
173         } else {
174             format!("{}_{}", shader.shader_name, shader.config.replace(",", "_"))
175         };
176 
177         let vert = glslopt_ctx.optimize(glslopt::ShaderType::Vertex, vert_src.clone());
178         if !vert.get_status() {
179             print_shader_source(&vert_src);
180             return Err(ShaderOptimizationError {
181                 shader: shader.clone(),
182                 message: vert.get_log().to_string(),
183             });
184         }
185         let frag = glslopt_ctx.optimize(glslopt::ShaderType::Fragment, frag_src.clone());
186         if !frag.get_status() {
187             print_shader_source(&frag_src);
188             return Err(ShaderOptimizationError {
189                 shader: shader.clone(),
190                 message: frag.get_log().to_string(),
191             });
192         }
193 
194         let vert_source = vert.get_output().unwrap();
195         let frag_source = frag.get_output().unwrap();
196 
197         // Compute a digest of the optimized shader sources. We store this
198         // as a literal alongside the source string so that we don't need
199         // to hash large strings at runtime.
200         let mut hasher = DefaultHasher::new();
201 
202         let vert_file_path = Path::new(out_dir)
203             .join(format!("{}_{:?}.vert", full_shader_name, shader.gl_version));
204         write_optimized_shader_file(&vert_file_path, vert_source, &shader.shader_name, &features, &mut hasher);
205 
206         let frag_file_path = vert_file_path.with_extension("frag");
207         write_optimized_shader_file(&frag_file_path, frag_source, &shader.shader_name, &features, &mut hasher);
208 
209         let digest: ProgramSourceDigest = hasher.into();
210 
211         println!("Finished optimizing shader {:?}", shader);
212 
213         Ok(ShaderOptimizationOutput {
214             full_shader_name,
215             gl_version: shader.gl_version,
216             vert_file_path,
217             frag_file_path,
218             digest,
219         })
220     }, &shaders);
221 
222     match outputs {
223         Ok(mut outputs) => {
224             // Sort the shader list so that the shaders.rs file is filled
225             // deterministically.
226             outputs.sort_by(|a, b| {
227                 (a.gl_version, a.full_shader_name.clone()).cmp(&(b.gl_version, b.full_shader_name.clone()))
228             });
229 
230             for shader in outputs {
231                 writeln!(
232                     shader_file,
233                     "    shaders.insert(({}, \"{}\"), OptimizedSourceWithDigest {{",
234                     shader.gl_version.variant_name(),
235                     shader.full_shader_name,
236                 )?;
237                 writeln!(
238                     shader_file,
239                     "        vert_source: include_str!(\"{}\"),",
240                     escape_include_path(&shader.vert_file_path),
241                 )?;
242                 writeln!(
243                     shader_file,
244                     "        frag_source: include_str!(\"{}\"),",
245                     escape_include_path(&shader.frag_file_path),
246                 )?;
247                 writeln!(shader_file, "        digest: \"{}\",", shader.digest)?;
248                 writeln!(shader_file, "    }});")?;
249             }
250         }
251         Err(err) => match err {
252             build_parallel::Error::BuildError(err) => {
253                 panic!("Error optimizing shader {:?}: {}", err.shader, err.message)
254             }
255             _ => panic!("Error optimizing shaders."),
256         }
257     }
258 
259     writeln!(shader_file, "    shaders")?;
260     writeln!(shader_file, "  }};")?;
261 
262     Ok(())
263 }
264 
write_optimized_shader_file( path: &Path, source: &str, shader_name: &str, features: &[&str], hasher: &mut DefaultHasher, )265 fn write_optimized_shader_file(
266     path: &Path,
267     source: &str,
268     shader_name: &str,
269     features: &[&str],
270     hasher: &mut DefaultHasher,
271 ) {
272     let mut file = File::create(&path).unwrap();
273     for (line_number, line) in source.lines().enumerate() {
274         // We embed the shader name and features as a comment in the
275         // source to make debugging easier.
276         // The #version directive must be on the first line so we insert
277         // the extra information on the next line.
278         if line_number == 1 {
279             let prelude = format!(
280                 "// {}\n// features: {:?}\n\n",
281                 shader_name, features
282             );
283             file.write_all(prelude.as_bytes()).unwrap();
284             hasher.write(prelude.as_bytes());
285         }
286         file.write_all(line.as_bytes()).unwrap();
287         file.write_all("\n".as_bytes()).unwrap();
288         hasher.write(line.as_bytes());
289         hasher.write("\n".as_bytes());
290     }
291 }
292 
main() -> Result<(), std::io::Error>293 fn main() -> Result<(), std::io::Error> {
294     let out_dir = env::var("OUT_DIR").unwrap_or("out".to_owned());
295 
296     let shaders_file_path = Path::new(&out_dir).join("shaders.rs");
297     let mut glsl_files = vec![];
298 
299     println!("cargo:rerun-if-changed=res");
300     let res_dir = Path::new("res");
301     for entry in read_dir(res_dir)? {
302         let entry = entry?;
303         let path = entry.path();
304 
305         if entry.file_name().to_str().unwrap().ends_with(".glsl") {
306             println!("cargo:rerun-if-changed={}", path.display());
307             glsl_files.push(path.to_owned());
308         }
309     }
310 
311     let mut shader_file = File::create(shaders_file_path)?;
312 
313     writeln!(shader_file, "/// AUTO GENERATED BY build.rs\n")?;
314     writeln!(shader_file, "use std::collections::HashMap;\n")?;
315     writeln!(shader_file, "use webrender_build::shader::ShaderVersion;\n")?;
316     writeln!(shader_file, "pub struct SourceWithDigest {{")?;
317     writeln!(shader_file, "    pub source: &'static str,")?;
318     writeln!(shader_file, "    pub digest: &'static str,")?;
319     writeln!(shader_file, "}}\n")?;
320     writeln!(shader_file, "pub struct OptimizedSourceWithDigest {{")?;
321     writeln!(shader_file, "    pub vert_source: &'static str,")?;
322     writeln!(shader_file, "    pub frag_source: &'static str,")?;
323     writeln!(shader_file, "    pub digest: &'static str,")?;
324     writeln!(shader_file, "}}\n")?;
325     writeln!(shader_file, "lazy_static! {{")?;
326 
327     write_unoptimized_shaders(glsl_files, &mut shader_file)?;
328     writeln!(shader_file, "")?;
329     write_optimized_shaders(&res_dir, &mut shader_file, &out_dir)?;
330     writeln!(shader_file, "}}")?;
331 
332     Ok(())
333 }
334