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 rust_min_stack(mut self, stack: u32) -> Self {
46 self.rust_min_stack = stack;
47 self
48 }
49
50 pub fn shader_src(mut self, src: impl AsRef<Path>) -> Self {
52 self.shader_src = Some(src.as_ref().to_owned());
53 self
54 }
55
56 pub fn feature(mut self, feature: impl ToString) -> Self {
58 let feature = feature.to_string();
59 if !self.features.contains(&feature) {
60 self.features.push(feature);
61 }
62 self
63 }
64
65 pub fn build(self, output_dir: impl AsRef<Path>) {
67 let output_dir = output_dir.as_ref();
68
69 self.setup_change_detection();
70
71 if self.build_spirv {
72 self.build_spirv(output_dir);
73 }
74
75 #[cfg(feature = "cuda")]
76 if self.build_cuda {
77 self.build_ptx(output_dir);
78 }
79 }
80
81 fn append_builtin_features(mut self) -> Self {
82 if cfg!(feature = "unsafe_remove_boundchecks") {
83 self = self.feature("unsafe-remove-boundchecks");
84 }
85
86 self
87 }
88
89 fn setup_change_detection(&self) {
90 println!(
91 "cargo:rerun-if-changed={}",
92 self.shader_crate.to_string_lossy()
93 );
94 let shader_src = self
95 .shader_src
96 .clone()
97 .unwrap_or_else(|| self.shader_crate.join("src"));
98 for entry in walkdir::WalkDir::new(shader_src)
99 .into_iter()
100 .filter_map(|e| e.ok())
101 {
102 println!("cargo:rerun-if-changed={}", entry.path().display());
103 }
104
105 println!("cargo:rerun-if-env-changed=CARGO_FEATURE_PUSH_CONSTANTS"); println!("cargo:rerun-if-env-changed=CARGO_FEATURE_CUDA");
107 }
108
109 fn build_spirv(&self, output_dir: impl AsRef<Path>) {
110 let output_dir = output_dir.as_ref();
111 let mut args = vec![
112 "gpu",
113 "build",
114 "--shader-crate",
115 self.shader_crate
116 .to_str()
117 .expect("Invalid shader crate path"),
118 "--output-dir",
119 output_dir.to_str().expect("Invalid output directory path"),
120 "--multimodule",
121 ];
122
123 let features_str = self.features.join(",");
124 if !features_str.is_empty() {
125 args.push("--features");
126 args.push(&features_str);
127 }
128
129 let status = Command::new("cargo")
130 .args(args)
131 .env("RUST_MIN_STACK", self.rust_min_stack.to_string())
132 .status()
133 .expect("failed to run cargo gpu");
134
135 if !status.success() {
136 panic!("cargo gpu build failed");
137 }
138 }
139
140 #[cfg(feature = "cuda")]
142 fn build_ptx(&self, output_dir: impl AsRef<Path>) {
143 let output_dir = output_dir.as_ref();
144 let features_str = self.features.join(",");
145
146 let mut args = vec![
147 "cuda",
148 "build",
149 "--shader-crate",
150 self.shader_crate
151 .to_str()
152 .expect("Invalid shader crate path"),
153 "--output-dir",
154 output_dir.to_str().expect("Invalid output directory path"),
155 ];
156
157 if !features_str.is_empty() {
158 args.push("--features");
159 args.push(&features_str);
160 }
161
162 let status = Command::new("cargo")
163 .args(args)
164 .env("RUST_MIN_STACK", self.rust_min_stack.to_string())
165 .status()
166 .expect("failed to run cargo cuda");
167
168 if !status.success() {
169 panic!("cargo cuda build failed");
170 }
171 }
172}