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 fs,
252 io::Write,
253 path::{Path, PathBuf},
254 process::{self, Command},
255 };
256
257 pub(super) fn build() {
258 // Compile HLSL shaders
259 #[cfg(not(debug_assertions))]
260 compile_shaders();
261
262 // Embed the Windows manifest and resource file
263 #[cfg(feature = "windows-manifest")]
264 embed_resource();
265 }
266
267 #[cfg(feature = "windows-manifest")]
268 fn embed_resource() {
269 let manifest = std::path::Path::new("resources/windows/gpui.manifest.xml");
270 let rc_file = std::path::Path::new("resources/windows/gpui.rc");
271 println!("cargo:rerun-if-changed={}", manifest.display());
272 println!("cargo:rerun-if-changed={}", rc_file.display());
273 embed_resource::compile(rc_file, embed_resource::NONE)
274 .manifest_required()
275 .unwrap();
276 }
277
278 /// You can set the `GPUI_FXC_PATH` environment variable to specify the path to the fxc.exe compiler.
279 fn compile_shaders() {
280 let shader_path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap())
281 .join("src/platform/windows/shaders.hlsl");
282 let out_dir = std::env::var("OUT_DIR").unwrap();
283
284 println!("cargo:rerun-if-changed={}", shader_path.display());
285
286 // Check if fxc.exe is available
287 let fxc_path = find_fxc_compiler();
288
289 // Define all modules
290 let modules = [
291 "quad",
292 "shadow",
293 "path_rasterization",
294 "path_sprite",
295 "underline",
296 "monochrome_sprite",
297 "polychrome_sprite",
298 ];
299
300 let rust_binding_path = format!("{}/shaders_bytes.rs", out_dir);
301 if Path::new(&rust_binding_path).exists() {
302 fs::remove_file(&rust_binding_path)
303 .expect("Failed to remove existing Rust binding file");
304 }
305 for module in modules {
306 compile_shader_for_module(
307 module,
308 &out_dir,
309 &fxc_path,
310 shader_path.to_str().unwrap(),
311 &rust_binding_path,
312 );
313 }
314
315 {
316 let shader_path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap())
317 .join("src/platform/windows/color_text_raster.hlsl");
318 compile_shader_for_module(
319 "emoji_rasterization",
320 &out_dir,
321 &fxc_path,
322 shader_path.to_str().unwrap(),
323 &rust_binding_path,
324 );
325 }
326 }
327
328 /// You can set the `GPUI_FXC_PATH` environment variable to specify the path to the fxc.exe compiler.
329 fn find_fxc_compiler() -> String {
330 // Check environment variable
331 if let Ok(path) = std::env::var("GPUI_FXC_PATH")
332 && Path::new(&path).exists()
333 {
334 return path;
335 }
336
337 // Try to find in PATH
338 // NOTE: This has to be `where.exe` on Windows, not `where`, it must be ended with `.exe`
339 if let Ok(output) = std::process::Command::new("where.exe")
340 .arg("fxc.exe")
341 .output()
342 && output.status.success()
343 {
344 let path = String::from_utf8_lossy(&output.stdout);
345 return path.trim().to_string();
346 }
347
348 // Check the default path
349 if Path::new(r"C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64\fxc.exe")
350 .exists()
351 {
352 return r"C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64\fxc.exe"
353 .to_string();
354 }
355
356 panic!("Failed to find fxc.exe");
357 }
358
359 fn compile_shader_for_module(
360 module: &str,
361 out_dir: &str,
362 fxc_path: &str,
363 shader_path: &str,
364 rust_binding_path: &str,
365 ) {
366 // Compile vertex shader
367 let output_file = format!("{}/{}_vs.h", out_dir, module);
368 let const_name = format!("{}_VERTEX_BYTES", module.to_uppercase());
369 compile_shader_impl(
370 fxc_path,
371 &format!("{module}_vertex"),
372 &output_file,
373 &const_name,
374 shader_path,
375 "vs_4_1",
376 );
377 generate_rust_binding(&const_name, &output_file, rust_binding_path);
378
379 // Compile fragment shader
380 let output_file = format!("{}/{}_ps.h", out_dir, module);
381 let const_name = format!("{}_FRAGMENT_BYTES", module.to_uppercase());
382 compile_shader_impl(
383 fxc_path,
384 &format!("{module}_fragment"),
385 &output_file,
386 &const_name,
387 shader_path,
388 "ps_4_1",
389 );
390 generate_rust_binding(&const_name, &output_file, rust_binding_path);
391 }
392
393 fn compile_shader_impl(
394 fxc_path: &str,
395 entry_point: &str,
396 output_path: &str,
397 var_name: &str,
398 shader_path: &str,
399 target: &str,
400 ) {
401 let output = Command::new(fxc_path)
402 .args([
403 "/T",
404 target,
405 "/E",
406 entry_point,
407 "/Fh",
408 output_path,
409 "/Vn",
410 var_name,
411 "/O3",
412 shader_path,
413 ])
414 .output();
415
416 match output {
417 Ok(result) => {
418 if result.status.success() {
419 return;
420 }
421 println!(
422 "cargo::error=Shader compilation failed for {}:\n{}",
423 entry_point,
424 String::from_utf8_lossy(&result.stderr)
425 );
426 process::exit(1);
427 }
428 Err(e) => {
429 println!("cargo::error=Failed to run fxc for {}: {}", entry_point, e);
430 process::exit(1);
431 }
432 }
433 }
434
435 fn generate_rust_binding(const_name: &str, head_file: &str, output_path: &str) {
436 let header_content = fs::read_to_string(head_file).expect("Failed to read header file");
437 let const_definition = {
438 let global_var_start = header_content.find("const BYTE").unwrap();
439 let global_var = &header_content[global_var_start..];
440 let equal = global_var.find('=').unwrap();
441 global_var[equal + 1..].trim()
442 };
443 let rust_binding = format!(
444 "const {}: &[u8] = &{}\n",
445 const_name,
446 const_definition.replace('{', "[").replace('}', "]")
447 );
448 let mut options = fs::OpenOptions::new()
449 .create(true)
450 .append(true)
451 .open(output_path)
452 .expect("Failed to open Rust binding file");
453 options
454 .write_all(rust_binding.as_bytes())
455 .expect("Failed to write Rust binding file");
456 }
457}