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 }
331
332 fn find_fxc_compiler() -> String {
333 // Try to find in PATH
334 if let Ok(output) = std::process::Command::new("where").arg("fxc.exe").output() {
335 if output.status.success() {
336 let path = String::from_utf8_lossy(&output.stdout);
337 return path.trim().to_string();
338 }
339 }
340
341 // Check the default path
342 if Path::new(r"C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64\fxc.exe")
343 .exists()
344 {
345 return r"C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64\fxc.exe"
346 .to_string();
347 }
348
349 // Check environment variable
350 if let Ok(path) = std::env::var("GPUI_FXC_PATH") {
351 if Path::new(&path).exists() {
352 return path;
353 }
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_5_0",
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_5_0",
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 eprintln!(
422 "Pixel 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 eprintln!("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 =
437 std::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 = std::fs::OpenOptions::new()
450 .write(true)
451 .create(true)
452 .append(true)
453 .open(output_path)
454 .expect("Failed to open Rust binding file");
455 options
456 .write_all(rust_binding.as_bytes())
457 .expect("Failed to write Rust binding file");
458 }
459}