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