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