1use std::path::{Path, PathBuf};
4use std::process::Command;
5
6pub struct KhalBuilder {
10 shader_crate: PathBuf,
11 shader_src: Option<PathBuf>,
13 features: Vec<String>,
15 rust_min_stack: u32,
17 #[allow(dead_code)]
20 build_cuda: bool,
21 build_spirv: bool,
24}
25
26impl KhalBuilder {
27 pub fn new(shader_crate: impl AsRef<Path>, enable_builtin_features: bool) -> Self {
30 let mut builder = Self {
31 shader_crate: shader_crate.as_ref().to_owned(),
32 shader_src: None,
33 features: Vec::new(),
34 build_cuda: true,
35 build_spirv: true,
36 rust_min_stack: 1024 * 1024 * 32,
37 };
38 if enable_builtin_features {
39 builder = builder.append_builtin_features();
40 }
41 builder
42 }
43
44 pub fn from_dependency(links_name: &str, enable_builtin_features: bool) -> Self {
55 let env_key = format!(
56 "DEP_{}_MANIFEST_DIR",
57 links_name.to_ascii_uppercase().replace('-', "_")
58 );
59 let manifest_dir = std::env::var(&env_key).unwrap_or_else(|_| {
60 panic!(
61 "environment variable `{env_key}` is not set; ensure `{links_name}` is declared \
62 as a `[build-dependencies]` entry of the host crate and that its `build.rs` emits \
63 `cargo::metadata=manifest_dir=$CARGO_MANIFEST_DIR`"
64 )
65 });
66 Self::new(manifest_dir, enable_builtin_features)
67 }
68
69 pub fn rust_min_stack(mut self, stack: u32) -> Self {
71 self.rust_min_stack = stack;
72 self
73 }
74
75 pub fn shader_src(mut self, src: impl AsRef<Path>) -> Self {
77 self.shader_src = Some(src.as_ref().to_owned());
78 self
79 }
80
81 pub fn feature(mut self, feature: impl ToString) -> Self {
83 let feature = feature.to_string();
84 if !self.features.contains(&feature) {
85 self.features.push(feature);
86 }
87 self
88 }
89
90 pub fn build(self, output_dir: impl AsRef<Path>) {
92 let output_dir = output_dir.as_ref();
93
94 self.setup_change_detection();
95
96 if self.build_spirv {
97 self.build_spirv(output_dir);
98 }
99
100 #[cfg(feature = "cuda")]
101 if self.build_cuda {
102 self.build_ptx(output_dir);
103 }
104 }
105
106 fn append_builtin_features(mut self) -> Self {
107 if cfg!(feature = "unsafe_remove_boundchecks") {
108 self = self.feature("unsafe-remove-boundchecks");
109 }
110
111 self
112 }
113
114 fn setup_change_detection(&self) {
115 println!(
116 "cargo:rerun-if-changed={}",
117 self.shader_crate.to_string_lossy()
118 );
119 let shader_src = self
120 .shader_src
121 .clone()
122 .unwrap_or_else(|| self.shader_crate.join("src"));
123 for entry in walkdir::WalkDir::new(shader_src)
124 .into_iter()
125 .filter_map(|e| e.ok())
126 {
127 println!("cargo:rerun-if-changed={}", entry.path().display());
128 }
129
130 println!("cargo:rerun-if-env-changed=CARGO_FEATURE_PUSH_CONSTANTS"); println!("cargo:rerun-if-env-changed=CARGO_FEATURE_CUDA");
132 }
133
134 fn build_spirv(&self, output_dir: impl AsRef<Path>) {
135 let output_dir = output_dir.as_ref();
136 let mut args = vec![
137 "gpu",
138 "build",
139 "--shader-crate",
140 self.shader_crate
141 .to_str()
142 .expect("Invalid shader crate path"),
143 "--output-dir",
144 output_dir.to_str().expect("Invalid output directory path"),
145 "--multimodule",
146 ];
147
148 let features_str = self.features.join(",");
149 if !features_str.is_empty() {
150 args.push("--features");
151 args.push(&features_str);
152 }
153
154 let status = Command::new("cargo")
155 .args(args)
156 .env("RUST_MIN_STACK", self.rust_min_stack.to_string())
157 .status()
158 .expect("failed to run cargo gpu");
159
160 if !status.success() {
161 panic!("cargo gpu build failed");
162 }
163 }
164
165 #[cfg(feature = "cuda")]
167 fn build_ptx(&self, output_dir: impl AsRef<Path>) {
168 let output_dir = output_dir.as_ref();
169 let features_str = self.features.join(",");
170
171 let mut args = vec![
172 "cuda",
173 "build",
174 "--shader-crate",
175 self.shader_crate
176 .to_str()
177 .expect("Invalid shader crate path"),
178 "--output-dir",
179 output_dir.to_str().expect("Invalid output directory path"),
180 ];
181
182 if !features_str.is_empty() {
183 args.push("--features");
184 args.push(&features_str);
185 }
186
187 let status = Command::new("cargo")
188 .args(args)
189 .env("RUST_MIN_STACK", self.rust_min_stack.to_string())
190 .status()
191 .expect("failed to run cargo cuda");
192
193 if !status.success() {
194 panic!("cargo cuda build failed");
195 }
196 }
197}