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