Bug 1816405 - Only print source of single shader when glslopt pass fails. r=gfx-reviewers,lsalzman

If the glslopt pass fails to optimize a shader, we helpfully print the
shader source so that it is easy to find what the problem is. However,
we print this from within the build_parallel call, meaning if multiple
shaders fail to optimize we print all of their sources and they get
interleaved with each other, making it very difficult to find the
problematic line.

This patch makes us delay printing the source until the same place
where we print the error log, after we have stopped processing in
parallel. This means we will only print the source of a single
shader (the first one which fails to optimize), along with the error
log for that shader, which is much easier to debug.

This also includes a small tidy-up to handle both vertex and fragment
shaders in a loop to remove code duplication, and runs rustfmt.

Differential Revision: https://phabricator.services.mozilla.com/D169618
This commit is contained in:
Jamie Nicol 2023-02-13 16:30:26 +00:00
parent 232617570d
commit f03cafb885

View file

@ -34,7 +34,10 @@ fn escape_include_path(path: &Path) -> String {
full_name
}
fn write_unoptimized_shaders(mut glsl_files: Vec<PathBuf>, shader_file: &mut File) -> Result<(), std::io::Error> {
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> = {{"
@ -100,17 +103,23 @@ struct ShaderOptimizationError {
message: String,
}
fn print_shader_source(shader_src: &str) {
/// 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.
println!("0\t|");
let mut out = format!("0\t|");
for (n, line) in shader_src.split('\n').enumerate() {
let line_number = n + 1;
println!("{}\t|{}", line_number, line);
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> {
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> = {{"
@ -135,7 +144,10 @@ fn write_optimized_shaders(shader_dir: &Path, shader_file: &mut File, out_dir: &
flags.remove(ShaderFeatureFlags::GLES);
flags.remove(ShaderFeatureFlags::TEXTURE_EXTERNAL);
}
if !matches!(env::var("CARGO_CFG_TARGET_OS").as_ref().map(|s| &**s), Ok("android")) {
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);
@ -151,7 +163,8 @@ fn write_optimized_shaders(shader_dir: &Path, shader_file: &mut File, out_dir: &
}
}
let outputs = build_parallel::compile_objects(&|shader: &ShaderOptimizationInput| {
let outputs = build_parallel::compile_objects::<_, _, ShaderOptimizationError, _>(
&|shader: &ShaderOptimizationInput| {
println!("Optimizing shader {:?}", shader);
let target = match shader.gl_version {
ShaderVersion::Gl => glslopt::Target::OpenGl,
@ -159,14 +172,18 @@ fn write_optimized_shaders(shader_dir: &Path, shader_file: &mut File, out_dir: &
};
let glslopt_ctx = glslopt::Context::new(target);
let features = shader.config.split(",").filter(|f| !f.is_empty()).collect::<Vec<_>>();
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 (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()
@ -174,39 +191,41 @@ fn write_optimized_shaders(shader_dir: &Path, shader_file: &mut File, out_dir: &
format!("{}_{}", shader.shader_name, shader.config.replace(",", "_"))
};
let vert = glslopt_ctx.optimize(glslopt::ShaderType::Vertex, vert_src.clone());
if !vert.get_status() {
print_shader_source(&vert_src);
return Err(ShaderOptimizationError {
shader: shader.clone(),
message: vert.get_log().to_string(),
});
}
let frag = glslopt_ctx.optimize(glslopt::ShaderType::Fragment, frag_src.clone());
if !frag.get_status() {
print_shader_source(&frag_src);
return Err(ShaderOptimizationError {
shader: shader.clone(),
message: frag.get_log().to_string(),
});
}
let vert_source = vert.get_output().unwrap();
let frag_source = frag.get_output().unwrap();
// 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 = Path::new(out_dir)
.join(format!("{}_{:?}.vert", full_shader_name, shader.gl_version));
write_optimized_shader_file(&vert_file_path, vert_source, &shader.shader_name, &features, &mut hasher);
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 frag_file_path = vert_file_path.with_extension("frag");
write_optimized_shader_file(&frag_file_path, frag_source, &shader.shader_name, &features, &mut hasher);
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 digest: ProgramSourceDigest = hasher.into();
let vert_file_path = vert_file_path?;
let frag_file_path = frag_file_path?;
println!("Finished optimizing shader {:?}", shader);
@ -215,16 +234,19 @@ fn write_optimized_shaders(shader_dir: &Path, shader_file: &mut File, out_dir: &
gl_version: shader.gl_version,
vert_file_path,
frag_file_path,
digest,
digest: hasher.into(),
})
}, &shaders);
},
&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()))
(a.gl_version, a.full_shader_name.clone())
.cmp(&(b.gl_version, b.full_shader_name.clone()))
});
for shader in outputs {
@ -253,7 +275,7 @@ fn write_optimized_shaders(shader_dir: &Path, shader_file: &mut File, out_dir: &
panic!("Error optimizing shader {:?}: {}", err.shader, err.message)
}
_ => panic!("Error optimizing shaders."),
}
},
}
writeln!(shader_file, " shaders")?;
@ -276,10 +298,7 @@ fn write_optimized_shader_file(
// 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
);
let prelude = format!("// {}\n// features: {:?}\n\n", shader_name, features);
file.write_all(prelude.as_bytes()).unwrap();
hasher.write(prelude.as_bytes());
}