199 lines
6.3 KiB
Rust
199 lines
6.3 KiB
Rust
// Requires crates: shaderc, num_cpus, jobserver, gl_generator
|
|
|
|
// This file is licensed to anyone who obtains a copy by lever1209 under the CC BY 4.0 license available here: https://creativecommons.org/licenses/by/4.0/
|
|
|
|
const SHADER_SRC_ROOT:&'static str = "shaders/";
|
|
const FRAGMENT_EXTENSIONS:[&'static str; 1] = ["frag"];
|
|
const VERTEX_EXTENSIONS:[&'static str; 1] = ["vert"];
|
|
const GLSL_PREFIXES:[&'static str; 1] = ["glsl"];
|
|
const HLSL_PREFIXES:[&'static str; 2] = ["cg", "hlsl"];
|
|
static MAX_THREAD_COUNT:std::sync::LazyLock<usize> = std::sync::LazyLock::new(|| num_cpus::get());
|
|
|
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
println!("cargo:rerun-if-changed=build.rs");
|
|
|
|
generate_opengl_bindings()?;
|
|
|
|
compile_shaders()?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_opengl_bindings() -> Result<(), Box<dyn std::error::Error>> {
|
|
let path = std::path::PathBuf::from(&std::env::var("OUT_DIR")?).join("gl_bindings.rs");
|
|
|
|
// Skip generation if the file is already there
|
|
if !path.exists() {
|
|
let mut file = std::fs::File::create(path)?;
|
|
gl_generator::Registry::new(gl_generator::Api::Gles2, (3, 0), gl_generator::Profile::Core, gl_generator::Fallbacks::All, [])
|
|
.write_bindings(gl_generator::StructGenerator, &mut file)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn compile_shaders() -> Result<(), Box<dyn std::error::Error>> {
|
|
println!("cargo:rerun-if-changed={SHADER_SRC_ROOT}");
|
|
|
|
let client = unsafe { jobserver::Client::from_env() }.unwrap_or({
|
|
println!(
|
|
"cargo:warning=Could not connect to the gnu jobserver. Using defined max thread count: {}.",
|
|
*MAX_THREAD_COUNT
|
|
);
|
|
jobserver::Client::new(*MAX_THREAD_COUNT)?
|
|
});
|
|
|
|
let out_dir = std::env::var("OUT_DIR").map_err(|err| format!("Could not read environment variable OUT_DIR: `{err}`"))?;
|
|
let shader_out_dir = std::path::Path::new(&out_dir).join(SHADER_SRC_ROOT);
|
|
std::fs::create_dir_all(&shader_out_dir)?;
|
|
|
|
let mut running_tasks = vec![];
|
|
|
|
let mut shader_source_files = vec![];
|
|
if let Err(err) = walk_dir(SHADER_SRC_ROOT.into(), &mut shader_source_files) {
|
|
return Err(format!("There was an error walking the shader source directory: `{err}`").into());
|
|
};
|
|
|
|
for input_path in shader_source_files {
|
|
let aquired = client
|
|
.acquire()
|
|
.map_err(|err| format!("There was an error while aquiring a jobserver token: `{err}`"))?;
|
|
|
|
let shader_out_dir = shader_out_dir.clone();
|
|
|
|
running_tasks.push((
|
|
// Store a copy here in case we panic and need to know which file caused the panic (realistically should never happen but whatever)
|
|
input_path.clone(),
|
|
std::thread::spawn(move || -> Result<(), Box<dyn std::error::Error+Sync+Send>> {
|
|
let _aquired = aquired;
|
|
|
|
let output_path = shader_out_dir.join(&input_path.file_name().unwrap()); // Guaranteed to be a file because of walk_dir
|
|
|
|
if let Err(error) = compile_shader(&input_path, &output_path) {
|
|
return Err(format!("Compilation process for input file: {input_path:?} was not successful: `{error}`")
|
|
.replace('\n', "\t")
|
|
.into());
|
|
}
|
|
Ok(())
|
|
}),
|
|
));
|
|
}
|
|
|
|
let mut has_error = false;
|
|
|
|
for (input_file, task) in running_tasks {
|
|
match task.join() {
|
|
Ok(Ok(())) => (),
|
|
Ok(Err(err)) => {
|
|
println!("cargo:warning=There was an error running a task: `{err}`");
|
|
has_error = true;
|
|
},
|
|
Err(_) => {
|
|
println!("cargo:warning=A thread panicked while processing this file!: {input_file:?}",);
|
|
has_error = true;
|
|
},
|
|
}
|
|
}
|
|
|
|
if has_error {
|
|
Err("Cannot proceed with build process. One or more shaders failed to compile.".into())
|
|
} else {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn get_stem_and_ext(path:&std::path::PathBuf) -> Result<(String, String), Box<dyn std::error::Error>> {
|
|
Ok((
|
|
match path.file_stem().and_then(|f| <&str>::try_from(f).ok()) {
|
|
Some(stem) => stem.to_owned(),
|
|
None => return Err(format!("Could not read file stem of file: {path:?}").into()),
|
|
},
|
|
match path.extension().and_then(|f| <&str>::try_from(f).ok()) {
|
|
Some(stem) => stem.to_owned(),
|
|
None => return Err(format!("Could not read extension of file: {path:?}").into()),
|
|
},
|
|
))
|
|
}
|
|
|
|
fn walk_dir(path:std::path::PathBuf, file_paths_buf:&mut Vec<std::path::PathBuf>) -> Result<(), Box<dyn std::error::Error>> {
|
|
if std::path::PathBuf::from(&path).is_file() {
|
|
file_paths_buf.push(path);
|
|
return Ok(());
|
|
} else {
|
|
let dir = std::fs::read_dir(path)?;
|
|
for f in dir {
|
|
walk_dir(f?.path(), file_paths_buf)?;
|
|
}
|
|
return Ok(());
|
|
}
|
|
}
|
|
|
|
fn compile_shader(input_path:&std::path::PathBuf, output_path:&std::path::PathBuf) -> Result<(), Box<dyn std::error::Error>> {
|
|
let compiler = match shaderc::Compiler::new() {
|
|
Some(compiler) => compiler,
|
|
None => return Err(format!("Could not initialize shaderc compiler.").into()),
|
|
};
|
|
|
|
let (stem, ext) = get_stem_and_ext(&input_path)?;
|
|
|
|
let mut target = None;
|
|
for glsl_prefix in GLSL_PREFIXES {
|
|
if stem.starts_with(glsl_prefix) {
|
|
target = Some(shaderc::SourceLanguage::GLSL);
|
|
break;
|
|
}
|
|
}
|
|
for hlsl_prefix in HLSL_PREFIXES {
|
|
if stem.starts_with(hlsl_prefix) {
|
|
target = Some(shaderc::SourceLanguage::HLSL);
|
|
break;
|
|
}
|
|
}
|
|
let target = if let Some(target) = target {
|
|
target
|
|
} else {
|
|
return Err(format!("This file is neither HLSL or GLSL!").into());
|
|
};
|
|
|
|
let mut shader_kind = None;
|
|
for vertex_extension in VERTEX_EXTENSIONS {
|
|
if ext == vertex_extension {
|
|
shader_kind = Some(shaderc::ShaderKind::Vertex)
|
|
}
|
|
}
|
|
for fragment_extension in FRAGMENT_EXTENSIONS {
|
|
if ext == fragment_extension {
|
|
shader_kind = Some(shaderc::ShaderKind::Fragment)
|
|
}
|
|
}
|
|
let shader_kind = if let Some(shader_kind) = shader_kind {
|
|
shader_kind
|
|
} else {
|
|
return Err(format!("This file is neither a fragment shader or vertex shader!").into());
|
|
};
|
|
|
|
let options = match shaderc::CompileOptions::new() {
|
|
Some(mut options) => {
|
|
options.set_optimization_level(shaderc::OptimizationLevel::Performance);
|
|
options.set_source_language(target);
|
|
options
|
|
},
|
|
None => return Err(format!("Could not initialize shaderc compiler options.").into()),
|
|
};
|
|
|
|
let source = std::fs::read_to_string(input_path)?;
|
|
let file_name = match input_path.file_name() {
|
|
Some(file_name) => file_name,
|
|
None => return Err("This is a directory!".into()),
|
|
};
|
|
let file_name = match file_name.to_str() {
|
|
Some(file_name) => file_name,
|
|
None => return Err("Could not load this file name.".into()),
|
|
};
|
|
|
|
let binary_result = compiler.compile_into_spirv(&source, shader_kind, file_name, "main", Some(&options))?;
|
|
|
|
std::fs::write(output_path, binary_result.as_binary_u8())?;
|
|
|
|
Ok(())
|
|
}
|