tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }