354 lines
13 KiB
Rust
354 lines
13 KiB
Rust
/* 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);
|
|
}
|
|
// The optimizer cannot handle the required EXT_YUV_target extension
|
|
flags.remove(ShaderFeatureFlags::TEXTURE_EXTERNAL_BT709);
|
|
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(())
|
|
}
|