use std::path::{Path, PathBuf};
use std::process::Command;
pub struct KhalBuilder {
shader_crate: PathBuf,
shader_src: Option<PathBuf>,
features: Vec<String>,
rust_min_stack: u32,
#[allow(dead_code)]
build_cuda: bool,
build_spirv: bool,
}
impl KhalBuilder {
pub fn new(shader_crate: impl AsRef<Path>, enable_builtin_features: bool) -> Self {
let mut builder = Self {
shader_crate: shader_crate.as_ref().to_owned(),
shader_src: None,
features: Vec::new(),
build_cuda: true,
build_spirv: true,
rust_min_stack: 1024 * 1024 * 32,
};
if enable_builtin_features {
builder = builder.append_builtin_features();
}
builder
}
pub fn from_dependency(links_name: &str, enable_builtin_features: bool) -> Self {
let env_key = format!(
"DEP_{}_MANIFEST_DIR",
links_name.to_ascii_uppercase().replace('-', "_")
);
let manifest_dir = std::env::var(&env_key).unwrap_or_else(|_| {
panic!(
"environment variable `{env_key}` is not set; ensure `{links_name}` is declared \
as a `[build-dependencies]` entry of the host crate and that its `build.rs` emits \
`cargo::metadata=manifest_dir=$CARGO_MANIFEST_DIR`"
)
});
Self::new(manifest_dir, enable_builtin_features)
}
pub fn rust_min_stack(mut self, stack: u32) -> Self {
self.rust_min_stack = stack;
self
}
pub fn shader_src(mut self, src: impl AsRef<Path>) -> Self {
self.shader_src = Some(src.as_ref().to_owned());
self
}
pub fn feature(mut self, feature: impl ToString) -> Self {
let feature = feature.to_string();
if !self.features.contains(&feature) {
self.features.push(feature);
}
self
}
pub fn build(self, output_dir: impl AsRef<Path>) {
let output_dir = output_dir.as_ref();
self.setup_change_detection();
if self.build_spirv {
self.build_spirv(output_dir);
}
#[cfg(feature = "cuda")]
if self.build_cuda {
self.build_ptx(output_dir);
}
}
fn append_builtin_features(mut self) -> Self {
if cfg!(feature = "unsafe_remove_boundchecks") {
self = self.feature("unsafe-remove-boundchecks");
}
self
}
fn setup_change_detection(&self) {
println!(
"cargo:rerun-if-changed={}",
self.shader_crate.to_string_lossy()
);
let shader_src = self
.shader_src
.clone()
.unwrap_or_else(|| self.shader_crate.join("src"));
for entry in walkdir::WalkDir::new(shader_src)
.into_iter()
.filter_map(|e| e.ok())
{
println!("cargo:rerun-if-changed={}", entry.path().display());
}
println!("cargo:rerun-if-env-changed=CARGO_FEATURE_PUSH_CONSTANTS"); println!("cargo:rerun-if-env-changed=CARGO_FEATURE_CUDA");
}
fn build_spirv(&self, output_dir: impl AsRef<Path>) {
let output_dir = output_dir.as_ref();
let mut args = vec![
"gpu",
"build",
"--shader-crate",
self.shader_crate
.to_str()
.expect("Invalid shader crate path"),
"--output-dir",
output_dir.to_str().expect("Invalid output directory path"),
"--multimodule",
];
let features_str = self.features.join(",");
if !features_str.is_empty() {
args.push("--features");
args.push(&features_str);
}
let status = Command::new("cargo")
.args(args)
.env("RUST_MIN_STACK", self.rust_min_stack.to_string())
.status()
.expect("failed to run cargo gpu");
if !status.success() {
panic!("cargo gpu build failed");
}
}
#[cfg(feature = "cuda")]
fn build_ptx(&self, output_dir: impl AsRef<Path>) {
let output_dir = output_dir.as_ref();
let features_str = self.features.join(",");
let mut args = vec![
"cuda",
"build",
"--shader-crate",
self.shader_crate
.to_str()
.expect("Invalid shader crate path"),
"--output-dir",
output_dir.to_str().expect("Invalid output directory path"),
];
if !features_str.is_empty() {
args.push("--features");
args.push(&features_str);
}
let status = Command::new("cargo")
.args(args)
.env("RUST_MIN_STACK", self.rust_min_stack.to_string())
.status()
.expect("failed to run cargo cuda");
if !status.success() {
panic!("cargo cuda build failed");
}
}
}