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