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 "polychrome_sprite",
301 ];
302
303 let rust_binding_path = format!("{}/shaders_bytes.rs", out_dir);
304 if Path::new(&rust_binding_path).exists() {
305 fs::remove_file(&rust_binding_path)
306 .expect("Failed to remove existing Rust binding file");
307 }
308 for module in modules {
309 compile_shader_for_module(
310 module,
311 &out_dir,
312 &fxc_path,
313 shader_path.to_str().unwrap(),
314 &rust_binding_path,
315 );
316 }
317
318 {
319 let shader_path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap())
320 .join("src/platform/windows/color_text_raster.hlsl");
321 compile_shader_for_module(
322 "emoji_rasterization",
323 &out_dir,
324 &fxc_path,
325 shader_path.to_str().unwrap(),
326 &rust_binding_path,
327 );
328 }
329 }
330
331 /// Locate `binary` in the newest installed Windows SDK.
332 pub fn find_latest_windows_sdk_binary(
333 binary: &str,
334 ) -> Result<Option<PathBuf>, Box<dyn std::error::Error>> {
335 let key = windows_registry::LOCAL_MACHINE
336 .open("SOFTWARE\\WOW6432Node\\Microsoft\\Microsoft SDKs\\Windows\\v10.0")?;
337
338 let install_folder: String = key.get_string("InstallationFolder")?; // "C:\Program Files (x86)\Windows Kits\10\"
339 let install_folder_bin = Path::new(&install_folder).join("bin");
340
341 let mut versions: Vec<_> = std::fs::read_dir(&install_folder_bin)?
342 .flatten()
343 .filter(|entry| entry.path().is_dir())
344 .filter_map(|entry| entry.file_name().into_string().ok())
345 .collect();
346
347 versions.sort_by_key(|s| {
348 s.split('.')
349 .filter_map(|p| p.parse().ok())
350 .collect::<Vec<u32>>()
351 });
352
353 let arch = match std::env::consts::ARCH {
354 "x86_64" => "x64",
355 "aarch64" => "arm64",
356 _ => Err(format!(
357 "Unsupported architecture: {}",
358 std::env::consts::ARCH
359 ))?,
360 };
361
362 if let Some(highest_version) = versions.last() {
363 return Ok(Some(
364 install_folder_bin
365 .join(highest_version)
366 .join(arch)
367 .join(binary),
368 ));
369 }
370
371 Ok(None)
372 }
373
374 /// You can set the `GPUI_FXC_PATH` environment variable to specify the path to the fxc.exe compiler.
375 fn find_fxc_compiler() -> String {
376 // Check environment variable
377 if let Ok(path) = std::env::var("GPUI_FXC_PATH")
378 && Path::new(&path).exists()
379 {
380 return path;
381 }
382
383 // Try to find in PATH
384 // NOTE: This has to be `where.exe` on Windows, not `where`, it must be ended with `.exe`
385 if let Ok(output) = std::process::Command::new("where.exe")
386 .arg("fxc.exe")
387 .output()
388 && output.status.success()
389 {
390 let path = String::from_utf8_lossy(&output.stdout);
391 return path.trim().to_string();
392 }
393
394 if let Ok(Some(path)) = find_latest_windows_sdk_binary("fxc.exe") {
395 return path.to_string_lossy().into_owned();
396 }
397
398 panic!("Failed to find fxc.exe");
399 }
400
401 fn compile_shader_for_module(
402 module: &str,
403 out_dir: &str,
404 fxc_path: &str,
405 shader_path: &str,
406 rust_binding_path: &str,
407 ) {
408 // Compile vertex shader
409 let output_file = format!("{}/{}_vs.h", out_dir, module);
410 let const_name = format!("{}_VERTEX_BYTES", module.to_uppercase());
411 compile_shader_impl(
412 fxc_path,
413 &format!("{module}_vertex"),
414 &output_file,
415 &const_name,
416 shader_path,
417 "vs_4_1",
418 );
419 generate_rust_binding(&const_name, &output_file, rust_binding_path);
420
421 // Compile fragment shader
422 let output_file = format!("{}/{}_ps.h", out_dir, module);
423 let const_name = format!("{}_FRAGMENT_BYTES", module.to_uppercase());
424 compile_shader_impl(
425 fxc_path,
426 &format!("{module}_fragment"),
427 &output_file,
428 &const_name,
429 shader_path,
430 "ps_4_1",
431 );
432 generate_rust_binding(&const_name, &output_file, rust_binding_path);
433 }
434
435 fn compile_shader_impl(
436 fxc_path: &str,
437 entry_point: &str,
438 output_path: &str,
439 var_name: &str,
440 shader_path: &str,
441 target: &str,
442 ) {
443 let output = Command::new(fxc_path)
444 .args([
445 "/T",
446 target,
447 "/E",
448 entry_point,
449 "/Fh",
450 output_path,
451 "/Vn",
452 var_name,
453 "/O3",
454 shader_path,
455 ])
456 .output();
457
458 match output {
459 Ok(result) => {
460 if result.status.success() {
461 return;
462 }
463 println!(
464 "cargo::error=Shader compilation failed for {}:\n{}",
465 entry_point,
466 String::from_utf8_lossy(&result.stderr)
467 );
468 process::exit(1);
469 }
470 Err(e) => {
471 println!("cargo::error=Failed to run fxc for {}: {}", entry_point, e);
472 process::exit(1);
473 }
474 }
475 }
476
477 fn generate_rust_binding(const_name: &str, head_file: &str, output_path: &str) {
478 let header_content = fs::read_to_string(head_file).expect("Failed to read header file");
479 let const_definition = {
480 let global_var_start = header_content.find("const BYTE").unwrap();
481 let global_var = &header_content[global_var_start..];
482 let equal = global_var.find('=').unwrap();
483 global_var[equal + 1..].trim()
484 };
485 let rust_binding = format!(
486 "const {}: &[u8] = &{}\n",
487 const_name,
488 const_definition.replace('{', "[").replace('}', "]")
489 );
490 let mut options = fs::OpenOptions::new()
491 .create(true)
492 .append(true)
493 .open(output_path)
494 .expect("Failed to open Rust binding file");
495 options
496 .write_all(rust_binding.as_bytes())
497 .expect("Failed to write Rust binding file");
498 }
499}