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