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