minemod/build.rs

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(())
}