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
297 let shader_path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap())
298 .join("src/platform/windows/shaders.hlsl");
299 let out_dir = std::env::var("OUT_DIR").unwrap();
300
301 println!("cargo:rerun-if-changed={}", shader_path.display());
302
303 // Check if fxc.exe is available
304 let fxc_path = find_fxc_compiler();
305
306 // Define all modules
307 let modules = [
308 "quad",
309 "shadow",
310 "paths",
311 "underline",
312 "monochrome_sprite",
313 "polychrome_sprite",
314 ];
315
316 let rust_binding_path = format!("{}/shaders_bytes.rs", out_dir);
317 if Path::new(&rust_binding_path).exists() {
318 fs::remove_file(&rust_binding_path)
319 .expect("Failed to remove existing Rust binding file");
320 }
321 for module in &modules {
322 compile_shader_for_module(
323 module,
324 &out_dir,
325 &fxc_path,
326 shader_path.to_str().unwrap(),
327 &rust_binding_path,
328 );
329 }
330 println!(
331 "cargo:warning=Successfully compiled shaders. Output written to: {}",
332 rust_binding_path
333 );
334 }
335
336 fn find_fxc_compiler() -> String {
337 // Try to find in PATH
338 if let Ok(output) = std::process::Command::new("where").arg("fxc.exe").output() {
339 if output.status.success() {
340 let path = String::from_utf8_lossy(&output.stdout);
341 return path.trim().to_string();
342 }
343 }
344
345 // Check the default path
346 if Path::new(r"C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64\fxc.exe")
347 .exists()
348 {
349 return r"C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64\fxc.exe"
350 .to_string();
351 }
352
353 // Check environment variable
354 if let Ok(path) = std::env::var("GPUI_FXC_PATH") {
355 if Path::new(&path).exists() {
356 return path;
357 }
358 }
359
360 panic!("Failed to find fxc.exe");
361 }
362
363 fn compile_shader_for_module(
364 module: &str,
365 out_dir: &str,
366 fxc_path: &str,
367 shader_path: &str,
368 rust_binding_path: &str,
369 ) {
370 // Compile vertex shader
371 let output_file = format!("{}/{}_vs.h", out_dir, module);
372 let const_name = format!("{}_VERTEX_BYTES", module.to_uppercase());
373 compile_shader_impl(
374 fxc_path,
375 &format!("{module}_vertex"),
376 &output_file,
377 &const_name,
378 shader_path,
379 "vs_5_0",
380 );
381 generate_rust_binding(&const_name, &output_file, &rust_binding_path);
382
383 // Compile fragment shader
384 let output_file = format!("{}/{}_ps.h", out_dir, module);
385 let const_name = format!("{}_FRAGMENT_BYTES", module.to_uppercase());
386 compile_shader_impl(
387 fxc_path,
388 &format!("{module}_fragment"),
389 &output_file,
390 &const_name,
391 shader_path,
392 "ps_5_0",
393 );
394 generate_rust_binding(&const_name, &output_file, &rust_binding_path);
395 }
396
397 fn compile_shader_impl(
398 fxc_path: &str,
399 entry_point: &str,
400 output_path: &str,
401 var_name: &str,
402 shader_path: &str,
403 target: &str,
404 ) {
405 let output = Command::new(fxc_path)
406 .args([
407 "/T",
408 target,
409 "/E",
410 entry_point,
411 "/Fh",
412 output_path,
413 "/Vn",
414 var_name,
415 "/O3",
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}