1#![cfg_attr(any(not(target_os = "macos"), feature = "macos-blade"), allow(unused))]
2
3//TODO: consider generating shader code for WGSL
4//TODO: deprecate "runtime-shaders" and "macos-blade"
5
6use std::env;
7
8fn main() {
9 let target = env::var("CARGO_CFG_TARGET_OS");
10 println!("cargo::rustc-check-cfg=cfg(gles)");
11
12 #[cfg(any(
13 not(any(target_os = "macos", target_os = "windows")),
14 feature = "macos-blade"
15 ))]
16 check_wgsl_shaders();
17
18 match target.as_deref() {
19 Ok("macos") => {
20 #[cfg(target_os = "macos")]
21 macos::build();
22 }
23 Ok("windows") => {
24 #[cfg(target_os = "windows")]
25 windows::build();
26 }
27 _ => (),
28 };
29}
30
31#[allow(dead_code)]
32#[cfg(not(target_os = "windows"))]
33fn check_wgsl_shaders() {
34 use std::path::PathBuf;
35 use std::process;
36 use std::str::FromStr;
37
38 let shader_source_path = "./src/platform/blade/shaders.wgsl";
39 let shader_path = PathBuf::from_str(shader_source_path).unwrap();
40 println!("cargo:rerun-if-changed={}", &shader_path.display());
41
42 let shader_source = std::fs::read_to_string(&shader_path).unwrap();
43
44 match naga::front::wgsl::parse_str(&shader_source) {
45 Ok(_) => {
46 // All clear
47 }
48 Err(e) => {
49 eprintln!("WGSL shader compilation failed:\n{}", e);
50 process::exit(1);
51 }
52 }
53}
54#[cfg(target_os = "macos")]
55mod macos {
56 use std::{
57 env,
58 path::{Path, PathBuf},
59 };
60
61 use cbindgen::Config;
62
63 pub(super) fn build() {
64 generate_dispatch_bindings();
65 #[cfg(not(feature = "macos-blade"))]
66 {
67 let header_path = generate_shader_bindings();
68
69 #[cfg(feature = "runtime_shaders")]
70 emit_stitched_shaders(&header_path);
71 #[cfg(not(feature = "runtime_shaders"))]
72 compile_metal_shaders(&header_path);
73 }
74 }
75
76 fn generate_dispatch_bindings() {
77 println!("cargo:rustc-link-lib=framework=System");
78
79 let bindings = bindgen::Builder::default()
80 .header("src/platform/mac/dispatch.h")
81 .allowlist_var("_dispatch_main_q")
82 .allowlist_var("_dispatch_source_type_data_add")
83 .allowlist_var("DISPATCH_QUEUE_PRIORITY_HIGH")
84 .allowlist_var("DISPATCH_TIME_NOW")
85 .allowlist_function("dispatch_get_global_queue")
86 .allowlist_function("dispatch_async_f")
87 .allowlist_function("dispatch_after_f")
88 .allowlist_function("dispatch_time")
89 .allowlist_function("dispatch_source_merge_data")
90 .allowlist_function("dispatch_source_create")
91 .allowlist_function("dispatch_source_set_event_handler_f")
92 .allowlist_function("dispatch_resume")
93 .allowlist_function("dispatch_suspend")
94 .allowlist_function("dispatch_source_cancel")
95 .allowlist_function("dispatch_set_context")
96 .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
97 .layout_tests(false)
98 .generate()
99 .expect("unable to generate bindings");
100
101 let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
102 bindings
103 .write_to_file(out_path.join("dispatch_sys.rs"))
104 .expect("couldn't write dispatch bindings");
105 }
106
107 fn generate_shader_bindings() -> PathBuf {
108 let output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("scene.h");
109 let crate_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
110 let mut config = Config {
111 include_guard: Some("SCENE_H".into()),
112 language: cbindgen::Language::C,
113 no_includes: true,
114 ..Default::default()
115 };
116 config.export.include.extend([
117 "Bounds".into(),
118 "Corners".into(),
119 "Edges".into(),
120 "Size".into(),
121 "Pixels".into(),
122 "PointF".into(),
123 "Hsla".into(),
124 "ContentMask".into(),
125 "Uniforms".into(),
126 "AtlasTile".into(),
127 "PathRasterizationInputIndex".into(),
128 "PathVertex_ScaledPixels".into(),
129 "PathRasterizationVertex".into(),
130 "ShadowInputIndex".into(),
131 "Shadow".into(),
132 "QuadInputIndex".into(),
133 "Underline".into(),
134 "UnderlineInputIndex".into(),
135 "Quad".into(),
136 "BorderStyle".into(),
137 "SpriteInputIndex".into(),
138 "MonochromeSprite".into(),
139 "PolychromeSprite".into(),
140 "PathSprite".into(),
141 "SurfaceInputIndex".into(),
142 "SurfaceBounds".into(),
143 "TransformationMatrix".into(),
144 ]);
145 config.no_includes = true;
146 config.enumeration.prefix_with_name = true;
147
148 let mut builder = cbindgen::Builder::new();
149
150 let src_paths = [
151 crate_dir.join("src/scene.rs"),
152 crate_dir.join("src/geometry.rs"),
153 crate_dir.join("src/color.rs"),
154 crate_dir.join("src/window.rs"),
155 crate_dir.join("src/platform.rs"),
156 crate_dir.join("src/platform/mac/metal_renderer.rs"),
157 ];
158 for src_path in src_paths {
159 println!("cargo:rerun-if-changed={}", src_path.display());
160 builder = builder.with_src(src_path);
161 }
162
163 builder
164 .with_config(config)
165 .generate()
166 .expect("Unable to generate bindings")
167 .write_to_file(&output_path);
168
169 output_path
170 }
171
172 /// To enable runtime compilation, we need to "stitch" the shaders file with the generated header
173 /// so that it is self-contained.
174 #[cfg(feature = "runtime_shaders")]
175 fn emit_stitched_shaders(header_path: &Path) {
176 use std::str::FromStr;
177 fn stitch_header(header: &Path, shader_path: &Path) -> std::io::Result<PathBuf> {
178 let header_contents = std::fs::read_to_string(header)?;
179 let shader_contents = std::fs::read_to_string(shader_path)?;
180 let stitched_contents = format!("{header_contents}\n{shader_contents}");
181 let out_path =
182 PathBuf::from(env::var("OUT_DIR").unwrap()).join("stitched_shaders.metal");
183 std::fs::write(&out_path, stitched_contents)?;
184 Ok(out_path)
185 }
186 let shader_source_path = "./src/platform/mac/shaders.metal";
187 let shader_path = PathBuf::from_str(shader_source_path).unwrap();
188 stitch_header(header_path, &shader_path).unwrap();
189 println!("cargo:rerun-if-changed={}", &shader_source_path);
190 }
191
192 #[cfg(not(feature = "runtime_shaders"))]
193 fn compile_metal_shaders(header_path: &Path) {
194 use std::process::{self, Command};
195 let shader_path = "./src/platform/mac/shaders.metal";
196 let air_output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("shaders.air");
197 let metallib_output_path =
198 PathBuf::from(env::var("OUT_DIR").unwrap()).join("shaders.metallib");
199 println!("cargo:rerun-if-changed={}", shader_path);
200
201 let output = Command::new("xcrun")
202 .args([
203 "-sdk",
204 "macosx",
205 "metal",
206 "-gline-tables-only",
207 "-mmacosx-version-min=10.15.7",
208 "-MO",
209 "-c",
210 shader_path,
211 "-include",
212 (header_path.to_str().unwrap()),
213 "-o",
214 ])
215 .arg(&air_output_path)
216 .output()
217 .unwrap();
218
219 if !output.status.success() {
220 eprintln!(
221 "metal shader compilation failed:\n{}",
222 String::from_utf8_lossy(&output.stderr)
223 );
224 process::exit(1);
225 }
226
227 let output = Command::new("xcrun")
228 .args(["-sdk", "macosx", "metallib"])
229 .arg(air_output_path)
230 .arg("-o")
231 .arg(metallib_output_path)
232 .output()
233 .unwrap();
234
235 if !output.status.success() {
236 eprintln!(
237 "metallib compilation failed:\n{}",
238 String::from_utf8_lossy(&output.stderr)
239 );
240 process::exit(1);
241 }
242 }
243}
244
245#[cfg(target_os = "windows")]
246mod windows {
247 use std::{
248 fs,
249 io::Write,
250 path::{Path, PathBuf},
251 process::{self, Command},
252 };
253
254 pub(super) fn build() {
255 // Compile HLSL shaders
256 #[cfg(not(debug_assertions))]
257 compile_shaders();
258
259 // Embed the Windows manifest and resource file
260 #[cfg(feature = "windows-manifest")]
261 embed_resource();
262 }
263
264 #[cfg(feature = "windows-manifest")]
265 fn embed_resource() {
266 let manifest = std::path::Path::new("resources/windows/gpui.manifest.xml");
267 let rc_file = std::path::Path::new("resources/windows/gpui.rc");
268 println!("cargo:rerun-if-changed={}", manifest.display());
269 println!("cargo:rerun-if-changed={}", rc_file.display());
270 embed_resource::compile(rc_file, embed_resource::NONE)
271 .manifest_required()
272 .unwrap();
273 }
274
275 /// You can set the `GPUI_FXC_PATH` environment variable to specify the path to the fxc.exe compiler.
276 fn compile_shaders() {
277 let shader_path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap())
278 .join("src/platform/windows/shaders.hlsl");
279 let out_dir = std::env::var("OUT_DIR").unwrap();
280
281 println!("cargo:rerun-if-changed={}", shader_path.display());
282
283 // Check if fxc.exe is available
284 let fxc_path = find_fxc_compiler();
285
286 // Define all modules
287 let modules = [
288 "quad",
289 "shadow",
290 "path_rasterization",
291 "path_sprite",
292 "underline",
293 "monochrome_sprite",
294 "polychrome_sprite",
295 ];
296
297 let rust_binding_path = format!("{}/shaders_bytes.rs", out_dir);
298 if Path::new(&rust_binding_path).exists() {
299 fs::remove_file(&rust_binding_path)
300 .expect("Failed to remove existing Rust binding file");
301 }
302 for module in modules {
303 compile_shader_for_module(
304 module,
305 &out_dir,
306 &fxc_path,
307 shader_path.to_str().unwrap(),
308 &rust_binding_path,
309 );
310 }
311 }
312
313 /// You can set the `GPUI_FXC_PATH` environment variable to specify the path to the fxc.exe compiler.
314 fn find_fxc_compiler() -> String {
315 // Check environment variable
316 if let Ok(path) = std::env::var("GPUI_FXC_PATH") {
317 if Path::new(&path).exists() {
318 return path;
319 }
320 }
321
322 // Try to find in PATH
323 if let Ok(output) = std::process::Command::new("where").arg("fxc.exe").output() {
324 if output.status.success() {
325 let path = String::from_utf8_lossy(&output.stdout);
326 return path.trim().to_string();
327 }
328 }
329
330 // Check the default path
331 if Path::new(r"C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64\fxc.exe")
332 .exists()
333 {
334 return r"C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64\fxc.exe"
335 .to_string();
336 }
337
338 panic!("Failed to find fxc.exe");
339 }
340
341 fn compile_shader_for_module(
342 module: &str,
343 out_dir: &str,
344 fxc_path: &str,
345 shader_path: &str,
346 rust_binding_path: &str,
347 ) {
348 // Compile vertex shader
349 let output_file = format!("{}/{}_vs.h", out_dir, module);
350 let const_name = format!("{}_VERTEX_BYTES", module.to_uppercase());
351 compile_shader_impl(
352 fxc_path,
353 &format!("{module}_vertex"),
354 &output_file,
355 &const_name,
356 shader_path,
357 "vs_5_0",
358 );
359 generate_rust_binding(&const_name, &output_file, &rust_binding_path);
360
361 // Compile fragment shader
362 let output_file = format!("{}/{}_ps.h", out_dir, module);
363 let const_name = format!("{}_FRAGMENT_BYTES", module.to_uppercase());
364 compile_shader_impl(
365 fxc_path,
366 &format!("{module}_fragment"),
367 &output_file,
368 &const_name,
369 shader_path,
370 "ps_5_0",
371 );
372 generate_rust_binding(&const_name, &output_file, &rust_binding_path);
373 }
374
375 fn compile_shader_impl(
376 fxc_path: &str,
377 entry_point: &str,
378 output_path: &str,
379 var_name: &str,
380 shader_path: &str,
381 target: &str,
382 ) {
383 let output = Command::new(fxc_path)
384 .args([
385 "/T",
386 target,
387 "/E",
388 entry_point,
389 "/Fh",
390 output_path,
391 "/Vn",
392 var_name,
393 "/O3",
394 shader_path,
395 ])
396 .output();
397
398 match output {
399 Ok(result) => {
400 if result.status.success() {
401 return;
402 }
403 eprintln!(
404 "Pixel shader compilation failed for {}:\n{}",
405 entry_point,
406 String::from_utf8_lossy(&result.stderr)
407 );
408 process::exit(1);
409 }
410 Err(e) => {
411 eprintln!("Failed to run fxc for {}: {}", entry_point, e);
412 process::exit(1);
413 }
414 }
415 }
416
417 fn generate_rust_binding(const_name: &str, head_file: &str, output_path: &str) {
418 let header_content = fs::read_to_string(head_file).expect("Failed to read header file");
419 let const_definition = {
420 let global_var_start = header_content.find("const BYTE").unwrap();
421 let global_var = &header_content[global_var_start..];
422 let equal = global_var.find('=').unwrap();
423 global_var[equal + 1..].trim()
424 };
425 let rust_binding = format!(
426 "const {}: &[u8] = &{}\n",
427 const_name,
428 const_definition.replace('{', "[").replace('}', "]")
429 );
430 let mut options = fs::OpenOptions::new()
431 .create(true)
432 .append(true)
433 .open(output_path)
434 .expect("Failed to open Rust binding file");
435 options
436 .write_all(rust_binding.as_bytes())
437 .expect("Failed to write Rust binding file");
438 }
439}