summaryrefslogtreecommitdiffstats
path: root/gfx/wr/webrender/build.rs
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/wr/webrender/build.rs')
-rw-r--r--gfx/wr/webrender/build.rs352
1 files changed, 352 insertions, 0 deletions
diff --git a/gfx/wr/webrender/build.rs b/gfx/wr/webrender/build.rs
new file mode 100644
index 0000000000..783f8cd5ca
--- /dev/null
+++ b/gfx/wr/webrender/build.rs
@@ -0,0 +1,352 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+extern crate webrender_build;
+
+use std::borrow::Cow;
+use std::env;
+use std::fs::{canonicalize, read_dir, File};
+use std::io::prelude::*;
+use std::path::{Path, PathBuf};
+use std::collections::hash_map::DefaultHasher;
+use std::hash::Hasher;
+use webrender_build::shader::*;
+use webrender_build::shader_features::{ShaderFeatureFlags, get_shader_features};
+
+// glsopt is known to leak, but we don't particularly care.
+#[no_mangle]
+pub extern "C" fn __lsan_default_options() -> *const u8 {
+ b"detect_leaks=0\0".as_ptr()
+}
+
+/// Compute the shader path for insertion into the include_str!() macro.
+/// This makes for more compact generated code than inserting the literal
+/// shader source into the generated file.
+///
+/// If someone is building on a network share, I'm sorry.
+fn escape_include_path(path: &Path) -> String {
+ let full_path = canonicalize(path).unwrap();
+ let full_name = full_path.as_os_str().to_str().unwrap();
+ let full_name = full_name.replace("\\\\?\\", "");
+ let full_name = full_name.replace("\\", "/");
+
+ full_name
+}
+
+fn write_unoptimized_shaders(
+ mut glsl_files: Vec<PathBuf>,
+ shader_file: &mut File,
+) -> Result<(), std::io::Error> {
+ writeln!(
+ shader_file,
+ " pub static ref UNOPTIMIZED_SHADERS: HashMap<&'static str, SourceWithDigest> = {{"
+ )?;
+ writeln!(shader_file, " let mut shaders = HashMap::new();")?;
+
+ // Sort the file list so that the shaders.rs file is filled
+ // deterministically.
+ glsl_files.sort_by(|a, b| a.file_name().cmp(&b.file_name()));
+
+ for glsl in glsl_files {
+ // Compute the shader name.
+ assert!(glsl.is_file());
+ let shader_name = glsl.file_name().unwrap().to_str().unwrap();
+ let shader_name = shader_name.replace(".glsl", "");
+
+ // Compute a digest of the #include-expanded shader source. We store
+ // this as a literal alongside the source string so that we don't need
+ // to hash large strings at runtime.
+ let mut hasher = DefaultHasher::new();
+ let base = glsl.parent().unwrap();
+ assert!(base.is_dir());
+ ShaderSourceParser::new().parse(
+ Cow::Owned(shader_source_from_file(&glsl)),
+ &|f| Cow::Owned(shader_source_from_file(&base.join(&format!("{}.glsl", f)))),
+ &mut |s| hasher.write(s.as_bytes()),
+ );
+ let digest: ProgramSourceDigest = hasher.into();
+
+ writeln!(
+ shader_file,
+ " shaders.insert(\"{}\", SourceWithDigest {{ source: include_str!(\"{}\"), digest: \"{}\"}});",
+ shader_name,
+ escape_include_path(&glsl),
+ digest,
+ )?;
+ }
+ writeln!(shader_file, " shaders")?;
+ writeln!(shader_file, " }};")?;
+
+ Ok(())
+}
+
+#[derive(Clone, Debug)]
+struct ShaderOptimizationInput {
+ shader_name: &'static str,
+ config: String,
+ gl_version: ShaderVersion,
+}
+
+#[derive(Debug)]
+struct ShaderOptimizationOutput {
+ full_shader_name: String,
+ gl_version: ShaderVersion,
+ vert_file_path: PathBuf,
+ frag_file_path: PathBuf,
+ digest: ProgramSourceDigest,
+}
+
+#[derive(Debug)]
+struct ShaderOptimizationError {
+ shader: ShaderOptimizationInput,
+ message: String,
+}
+
+/// Prepends the line number to each line of a shader source.
+fn enumerate_shader_source_lines(shader_src: &str) -> String {
+ // For some reason the glsl-opt errors are offset by 1 compared
+ // to the provided shader source string.
+ let mut out = format!("0\t|");
+ for (n, line) in shader_src.split('\n').enumerate() {
+ let line_number = n + 1;
+ out.push_str(&format!("{}\t|{}\n", line_number, line));
+ }
+ out
+}
+
+fn write_optimized_shaders(
+ shader_dir: &Path,
+ shader_file: &mut File,
+ out_dir: &str,
+) -> Result<(), std::io::Error> {
+ writeln!(
+ shader_file,
+ " pub static ref OPTIMIZED_SHADERS: HashMap<(ShaderVersion, &'static str), OptimizedSourceWithDigest> = {{"
+ )?;
+ writeln!(shader_file, " let mut shaders = HashMap::new();")?;
+
+ // The full set of optimized shaders can be quite large, so only optimize
+ // for the GL version we expect to be used on the target platform. If a different GL
+ // version is used we will simply fall back to the unoptimized shaders.
+ let shader_versions = match env::var("CARGO_CFG_TARGET_OS").as_ref().map(|s| &**s) {
+ Ok("android") | Ok("windows") => [ShaderVersion::Gles],
+ _ => [ShaderVersion::Gl],
+ };
+
+ let mut shaders = Vec::default();
+ for &gl_version in &shader_versions {
+ let mut flags = ShaderFeatureFlags::all();
+ if gl_version != ShaderVersion::Gl {
+ flags.remove(ShaderFeatureFlags::GL);
+ }
+ if gl_version != ShaderVersion::Gles {
+ flags.remove(ShaderFeatureFlags::GLES);
+ flags.remove(ShaderFeatureFlags::TEXTURE_EXTERNAL);
+ }
+ if !matches!(
+ env::var("CARGO_CFG_TARGET_OS").as_ref().map(|s| &**s),
+ Ok("android")
+ ) {
+ flags.remove(ShaderFeatureFlags::TEXTURE_EXTERNAL_ESSL1);
+ }
+ flags.remove(ShaderFeatureFlags::DITHERING);
+
+ for (shader_name, configs) in get_shader_features(flags) {
+ for config in configs {
+ shaders.push(ShaderOptimizationInput {
+ shader_name,
+ config,
+ gl_version,
+ });
+ }
+ }
+ }
+
+ let outputs = build_parallel::compile_objects::<_, _, ShaderOptimizationError, _>(
+ &|shader: &ShaderOptimizationInput| {
+ println!("Optimizing shader {:?}", shader);
+ let target = match shader.gl_version {
+ ShaderVersion::Gl => glslopt::Target::OpenGl,
+ ShaderVersion::Gles => glslopt::Target::OpenGles30,
+ };
+ let glslopt_ctx = glslopt::Context::new(target);
+
+ let features = shader
+ .config
+ .split(",")
+ .filter(|f| !f.is_empty())
+ .collect::<Vec<_>>();
+
+ let (vert_src, frag_src) =
+ build_shader_strings(shader.gl_version, &features, shader.shader_name, &|f| {
+ Cow::Owned(shader_source_from_file(
+ &shader_dir.join(&format!("{}.glsl", f)),
+ ))
+ });
+
+ let full_shader_name = if shader.config.is_empty() {
+ shader.shader_name.to_string()
+ } else {
+ format!("{}_{}", shader.shader_name, shader.config.replace(",", "_"))
+ };
+
+ // Compute a digest of the optimized shader sources. We store this
+ // as a literal alongside the source string so that we don't need
+ // to hash large strings at runtime.
+ let mut hasher = DefaultHasher::new();
+
+ let [vert_file_path, frag_file_path] = [
+ (glslopt::ShaderType::Vertex, vert_src, "vert"),
+ (glslopt::ShaderType::Fragment, frag_src, "frag"),
+ ]
+ .map(|(shader_type, shader_src, extension)| {
+ let output = glslopt_ctx.optimize(shader_type, shader_src.clone());
+ if !output.get_status() {
+ let source = enumerate_shader_source_lines(&shader_src);
+ return Err(ShaderOptimizationError {
+ shader: shader.clone(),
+ message: format!("{}\n{}", source, output.get_log()),
+ });
+ }
+
+ let shader_path = Path::new(out_dir).join(format!(
+ "{}_{:?}.{}",
+ full_shader_name, shader.gl_version, extension
+ ));
+ write_optimized_shader_file(
+ &shader_path,
+ output.get_output().unwrap(),
+ &shader.shader_name,
+ &features,
+ &mut hasher,
+ );
+ Ok(shader_path)
+ });
+
+ let vert_file_path = vert_file_path?;
+ let frag_file_path = frag_file_path?;
+
+ println!("Finished optimizing shader {:?}", shader);
+
+ Ok(ShaderOptimizationOutput {
+ full_shader_name,
+ gl_version: shader.gl_version,
+ vert_file_path,
+ frag_file_path,
+ digest: hasher.into(),
+ })
+ },
+ &shaders,
+ );
+
+ match outputs {
+ Ok(mut outputs) => {
+ // Sort the shader list so that the shaders.rs file is filled
+ // deterministically.
+ outputs.sort_by(|a, b| {
+ (a.gl_version, a.full_shader_name.clone())
+ .cmp(&(b.gl_version, b.full_shader_name.clone()))
+ });
+
+ for shader in outputs {
+ writeln!(
+ shader_file,
+ " shaders.insert(({}, \"{}\"), OptimizedSourceWithDigest {{",
+ shader.gl_version.variant_name(),
+ shader.full_shader_name,
+ )?;
+ writeln!(
+ shader_file,
+ " vert_source: include_str!(\"{}\"),",
+ escape_include_path(&shader.vert_file_path),
+ )?;
+ writeln!(
+ shader_file,
+ " frag_source: include_str!(\"{}\"),",
+ escape_include_path(&shader.frag_file_path),
+ )?;
+ writeln!(shader_file, " digest: \"{}\",", shader.digest)?;
+ writeln!(shader_file, " }});")?;
+ }
+ }
+ Err(err) => match err {
+ build_parallel::Error::BuildError(err) => {
+ panic!("Error optimizing shader {:?}: {}", err.shader, err.message)
+ }
+ _ => panic!("Error optimizing shaders."),
+ },
+ }
+
+ writeln!(shader_file, " shaders")?;
+ writeln!(shader_file, " }};")?;
+
+ Ok(())
+}
+
+fn write_optimized_shader_file(
+ path: &Path,
+ source: &str,
+ shader_name: &str,
+ features: &[&str],
+ hasher: &mut DefaultHasher,
+) {
+ let mut file = File::create(&path).unwrap();
+ for (line_number, line) in source.lines().enumerate() {
+ // We embed the shader name and features as a comment in the
+ // source to make debugging easier.
+ // The #version directive must be on the first line so we insert
+ // the extra information on the next line.
+ if line_number == 1 {
+ let prelude = format!("// {}\n// features: {:?}\n\n", shader_name, features);
+ file.write_all(prelude.as_bytes()).unwrap();
+ hasher.write(prelude.as_bytes());
+ }
+ file.write_all(line.as_bytes()).unwrap();
+ file.write_all("\n".as_bytes()).unwrap();
+ hasher.write(line.as_bytes());
+ hasher.write("\n".as_bytes());
+ }
+}
+
+fn main() -> Result<(), std::io::Error> {
+ let out_dir = env::var("OUT_DIR").unwrap_or("out".to_owned());
+
+ let shaders_file_path = Path::new(&out_dir).join("shaders.rs");
+ let mut glsl_files = vec![];
+
+ println!("cargo:rerun-if-changed=res");
+ let res_dir = Path::new("res");
+ for entry in read_dir(res_dir)? {
+ let entry = entry?;
+ let path = entry.path();
+
+ if entry.file_name().to_str().unwrap().ends_with(".glsl") {
+ println!("cargo:rerun-if-changed={}", path.display());
+ glsl_files.push(path.to_owned());
+ }
+ }
+
+ let mut shader_file = File::create(shaders_file_path)?;
+
+ writeln!(shader_file, "/// AUTO GENERATED BY build.rs\n")?;
+ writeln!(shader_file, "use std::collections::HashMap;\n")?;
+ writeln!(shader_file, "use webrender_build::shader::ShaderVersion;\n")?;
+ writeln!(shader_file, "pub struct SourceWithDigest {{")?;
+ writeln!(shader_file, " pub source: &'static str,")?;
+ writeln!(shader_file, " pub digest: &'static str,")?;
+ writeln!(shader_file, "}}\n")?;
+ writeln!(shader_file, "pub struct OptimizedSourceWithDigest {{")?;
+ writeln!(shader_file, " pub vert_source: &'static str,")?;
+ writeln!(shader_file, " pub frag_source: &'static str,")?;
+ writeln!(shader_file, " pub digest: &'static str,")?;
+ writeln!(shader_file, "}}\n")?;
+ writeln!(shader_file, "lazy_static! {{")?;
+
+ write_unoptimized_shaders(glsl_files, &mut shader_file)?;
+ writeln!(shader_file, "")?;
+ write_optimized_shaders(&res_dir, &mut shader_file, &out_dir)?;
+ writeln!(shader_file, "}}")?;
+
+ Ok(())
+}