build.rs (9803B)
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 cc; 6 extern crate glsl_to_cxx; 7 extern crate webrender_build; 8 9 use std::collections::HashSet; 10 use std::fmt::Write; 11 use webrender_build::shader::{get_shader_features, ShaderFeatureFlags}; 12 13 // Shader key is in "name feature,feature" format. 14 // File name needs to be formatted as "name_feature_feature". 15 fn shader_file(shader_key: &str) -> String { 16 shader_key.replace([' ', ','], "_") 17 } 18 19 fn write_load_shader(shader_keys: &[String]) { 20 let mut load_shader = String::new(); 21 for s in shader_keys { 22 let _ = writeln!(load_shader, "#include \"{}.h\"", shader_file(s)); 23 } 24 load_shader.push_str("ProgramLoader load_shader(const char* name) {\n"); 25 for s in shader_keys { 26 let _ = writeln!( 27 load_shader, 28 " if (!strcmp(name, \"{}\")) {{ return {}_program::loader; }}", 29 s, 30 shader_file(s) 31 ); 32 } 33 load_shader.push_str(" return nullptr;\n}\n"); 34 std::fs::write( 35 std::env::var("OUT_DIR").unwrap() + "/load_shader.h", 36 load_shader, 37 ) 38 .unwrap(); 39 } 40 41 fn process_imports( 42 shader_dir: &str, 43 shader: &str, 44 included: &mut HashSet<String>, 45 output: &mut String, 46 ) { 47 if !included.insert(shader.into()) { 48 return; 49 } 50 println!("cargo:rerun-if-changed={shader_dir}/{shader}.glsl"); 51 let source = std::fs::read_to_string(format!("{shader_dir}/{shader}.glsl")).unwrap(); 52 for line in source.lines() { 53 if let Some(imports) = line.strip_prefix("#include ") { 54 let imports = imports.split(','); 55 for import in imports { 56 process_imports(shader_dir, import, included, output); 57 } 58 } else if line.starts_with("#version ") || line.starts_with("#extension ") { 59 // ignore 60 } else { 61 output.push_str(line); 62 output.push('\n'); 63 } 64 } 65 } 66 67 fn translate_shader( 68 shader_key: &str, 69 shader_dir: &str, 70 suppressed_env_vars: &mut Option<Vec<EnvVarGuard>>, 71 ) { 72 let mut imported = String::from("#define SWGL 1\n#define __VERSION__ 150\n"); 73 let _ = writeln!( 74 imported, 75 "#define WR_MAX_VERTEX_TEXTURE_WIDTH {}U", 76 webrender_build::MAX_VERTEX_TEXTURE_WIDTH 77 ); 78 79 let (basename, features) = 80 shader_key.split_at(shader_key.find(' ').unwrap_or(shader_key.len())); 81 if !features.is_empty() { 82 for feature in features.trim().split(',') { 83 let _ = writeln!(imported, "#define WR_FEATURE_{feature}"); 84 } 85 } 86 87 process_imports(shader_dir, basename, &mut HashSet::new(), &mut imported); 88 89 let shader = shader_file(shader_key); 90 91 let out_dir = std::env::var("OUT_DIR").unwrap(); 92 let imp_name = format!("{out_dir}/{shader}.c"); 93 std::fs::write(&imp_name, imported).unwrap(); 94 95 // We need to ensure that the C preprocessor does not pull compiler flags from the host or 96 // target environment. Set all `CFLAGS` or `CXXFLAGS` env. vars. to empty to work around this. 97 let _ = suppressed_env_vars.get_or_insert_with(|| { 98 cflags_env_vars() 99 .map(|(key, value)| { 100 std::env::set_var(&key, ""); 101 EnvVarGuard { 102 key, 103 old_value: Some(value), 104 } 105 }) 106 .collect::<Vec<_>>() 107 }); 108 109 let mut build = cc::Build::new(); 110 build.no_default_flags(true); 111 if let Ok(tool) = build.try_get_compiler() { 112 if tool.is_like_msvc() { 113 build.flag("/EP"); 114 if tool.path().to_str().is_some_and(|p| p.contains("clang")) { 115 build.flag("/clang:-undef"); 116 } else { 117 build.flag("/u"); 118 } 119 } else { 120 build.flag("-xc").flag("-P").flag("-undef"); 121 } 122 } 123 build.file(&imp_name); 124 let vs = build.clone().define("WR_VERTEX_SHADER", Some("1")).expand(); 125 let fs = build 126 .clone() 127 .define("WR_FRAGMENT_SHADER", Some("1")) 128 .expand(); 129 let vs_name = format!("{out_dir}/{shader}.vert"); 130 let fs_name = format!("{out_dir}/{shader}.frag"); 131 std::fs::write(&vs_name, vs).unwrap(); 132 std::fs::write(&fs_name, fs).unwrap(); 133 134 let args = vec!["glsl_to_cxx".to_string(), vs_name, fs_name]; 135 let result = glsl_to_cxx::translate(&mut args.into_iter()); 136 std::fs::write(format!("{out_dir}/{shader}.h"), result).unwrap(); 137 } 138 139 fn main() { 140 let shader_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap() + "/../webrender/res"; 141 142 let shader_flags = ShaderFeatureFlags::GL 143 | ShaderFeatureFlags::DUAL_SOURCE_BLENDING 144 | ShaderFeatureFlags::ADVANCED_BLEND_EQUATION 145 | ShaderFeatureFlags::DEBUG; 146 let mut shaders: Vec<String> = Vec::new(); 147 for (name, features) in get_shader_features(shader_flags) { 148 shaders.extend(features.iter().map(|f| { 149 if f.is_empty() { 150 name.to_owned() 151 } else { 152 format!("{name} {f}") 153 } 154 })); 155 } 156 157 shaders.sort(); 158 159 let mut suppressed_env_vars = None; 160 for shader in &shaders { 161 translate_shader(shader, &shader_dir, &mut suppressed_env_vars); 162 } 163 drop(suppressed_env_vars); // Restore env. vars. for further compilation. 164 165 write_load_shader(&shaders); 166 167 println!("cargo:rerun-if-changed=src/blend.h"); 168 println!("cargo:rerun-if-changed=src/composite.h"); 169 println!("cargo:rerun-if-changed=src/gl_defs.h"); 170 println!("cargo:rerun-if-changed=src/glsl.h"); 171 println!("cargo:rerun-if-changed=src/program.h"); 172 println!("cargo:rerun-if-changed=src/rasterize.h"); 173 println!("cargo:rerun-if-changed=src/swgl_ext.h"); 174 println!("cargo:rerun-if-changed=src/texture.h"); 175 println!("cargo:rerun-if-changed=src/vector_type.h"); 176 println!("cargo:rerun-if-changed=src/gl.cc"); 177 let mut build = cc::Build::new(); 178 build.cpp(true); 179 180 let _suppressed_cflags_env_vars = cflags_env_vars() 181 .filter_map(|(key, value)| { 182 // NOTE: Cancels out [the `moz-check` plugin added in 183 // `build/moz.configure/clang_plugin.configure`'s `clang_plugin_flags` 184 // assignment][crossref]. 185 // 186 // [crossref]: https://searchfox.org/mozilla-central/rev/f008b9aa2adf2dca6bdd49855b314cb3195f6f27/build/moz.configure/clang_plugin.configure#77-80 187 const MOZ_CHECK_PLUGIN_LOAD_ARGS: &str = "-Xclang -add-plugin -Xclang moz-check"; 188 189 let replaced = value 190 .to_str() 191 .filter(|v| v.contains(MOZ_CHECK_PLUGIN_LOAD_ARGS))? 192 .replace(MOZ_CHECK_PLUGIN_LOAD_ARGS, ""); 193 194 std::env::set_var(&key, replaced); 195 Some(EnvVarGuard { 196 key, 197 old_value: Some(value), 198 }) 199 }) 200 .collect::<Vec<_>>(); 201 202 if let Ok(tool) = build.try_get_compiler() { 203 if tool.is_like_msvc() { 204 build 205 .flag("/std:c++20") 206 .flag("/EHs-") 207 .flag("/GR-"); 208 } else { 209 build 210 .flag("-std=c++20") 211 .flag("-fno-exceptions") 212 .flag("-fno-rtti") 213 .flag("-fno-math-errno"); 214 } 215 // SWGL relies heavily on inlining for performance so override -Oz with -O2 216 if tool.args().contains(&"-Oz".into()) { 217 build.flag("-O2"); 218 } 219 220 // Most GLSL compilers assume something like fast-math so we turn it on. 221 // However, reciprocal division makes it so 1/1 = 0.999994 which can produce a lot of fuzz 222 // in reftests and the use of reciprocal instructions usually involves a refinement step 223 // which bloats our already bloated code. Further, our shader code is sufficiently parallel 224 // that we're more likely to be throughput bound vs latency bound. Having fewer 225 // instructions makes things easier on the processor and in places where it matters we can 226 // probably explicitly use reciprocal instructions and avoid the refinement step. 227 // Also, allow checks for non-finite values which fast-math may disable. 228 if tool.is_like_msvc() { 229 build 230 .flag("/fp:fast") 231 .flag("-Xclang") 232 .flag("-mrecip=none") 233 .flag("/clang:-fno-finite-math-only"); 234 } else if tool.is_like_clang() { 235 // gcc only supports -mrecip=none on some targets so to keep 236 // things simple we don't use -ffast-math with gcc at all 237 build 238 .flag("-ffast-math") 239 .flag("-mrecip=none") 240 .flag("-fno-finite-math-only"); 241 } 242 } 243 244 build 245 .file("src/gl.cc") 246 .define("_GLIBCXX_USE_CXX11_ABI", Some("0")) 247 .include(shader_dir) 248 .include("src") 249 .include(std::env::var("OUT_DIR").unwrap()) 250 .compile("gl_cc"); 251 } 252 253 struct EnvVarGuard { 254 key: std::ffi::OsString, 255 old_value: Option<std::ffi::OsString>, 256 } 257 258 impl Drop for EnvVarGuard { 259 fn drop(&mut self) { 260 let Self { key, old_value } = &*self; 261 if let Some(old_value) = old_value.as_ref() { 262 std::env::set_var(key, old_value); 263 } else { 264 std::env::remove_var(key); 265 } 266 } 267 } 268 269 fn cflags_env_vars() -> impl Iterator<Item = (std::ffi::OsString, std::ffi::OsString)> { 270 std::env::vars_os().filter(|(key, _value)| { 271 key.to_str().is_some_and(|key_utf8| { 272 ["CFLAGS", "CXXFLAGS"] 273 .iter() 274 .any(|opt_name| key_utf8.contains(opt_name)) 275 }) 276 }) 277 }