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