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 all(target_os = "macos", 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#[cfg(any(
32 not(any(target_os = "macos", target_os = "windows")),
33 all(target_os = "macos", feature = "macos-blade")
34))]
35fn check_wgsl_shaders() {
36 use std::path::PathBuf;
37 use std::process;
38 use std::str::FromStr;
39
40 let shader_source_path = "./src/platform/blade/shaders.wgsl";
41 let shader_path = PathBuf::from_str(shader_source_path).unwrap();
42 println!("cargo:rerun-if-changed={}", &shader_path.display());
43
44 let shader_source = std::fs::read_to_string(&shader_path).unwrap();
45
46 match naga::front::wgsl::parse_str(&shader_source) {
47 Ok(_) => {
48 // All clear
49 }
50 Err(e) => {
51 eprintln!("WGSL shader compilation failed:\n{}", e);
52 process::exit(1);
53 }
54 }
55}
56#[cfg(target_os = "macos")]
57mod macos {
58 use std::{
59 env,
60 path::{Path, PathBuf},
61 };
62
63 use cbindgen::Config;
64
65 pub(super) fn build() {
66 generate_dispatch_bindings();
67 #[cfg(not(feature = "macos-blade"))]
68 {
69 let header_path = generate_shader_bindings();
70
71 #[cfg(feature = "runtime_shaders")]
72 emit_stitched_shaders(&header_path);
73 #[cfg(not(feature = "runtime_shaders"))]
74 compile_metal_shaders(&header_path);
75 }
76 }
77
78 fn generate_dispatch_bindings() {
79 println!("cargo:rustc-link-lib=framework=System");
80
81 let bindings = bindgen::Builder::default()
82 .header("src/platform/mac/dispatch.h")
83 .allowlist_var("_dispatch_main_q")
84 .allowlist_var("_dispatch_source_type_data_add")
85 .allowlist_var("DISPATCH_QUEUE_PRIORITY_HIGH")
86 .allowlist_var("DISPATCH_TIME_NOW")
87 .allowlist_function("dispatch_get_global_queue")
88 .allowlist_function("dispatch_async_f")
89 .allowlist_function("dispatch_after_f")
90 .allowlist_function("dispatch_time")
91 .allowlist_function("dispatch_source_merge_data")
92 .allowlist_function("dispatch_source_create")
93 .allowlist_function("dispatch_source_set_event_handler_f")
94 .allowlist_function("dispatch_resume")
95 .allowlist_function("dispatch_suspend")
96 .allowlist_function("dispatch_source_cancel")
97 .allowlist_function("dispatch_set_context")
98 .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
99 .layout_tests(false)
100 .generate()
101 .expect("unable to generate bindings");
102
103 let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
104 bindings
105 .write_to_file(out_path.join("dispatch_sys.rs"))
106 .expect("couldn't write dispatch bindings");
107 }
108
109 fn generate_shader_bindings() -> PathBuf {
110 let output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("scene.h");
111 let crate_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
112 let mut config = Config {
113 include_guard: Some("SCENE_H".into()),
114 language: cbindgen::Language::C,
115 no_includes: true,
116 ..Default::default()
117 };
118 config.export.include.extend([
119 "Bounds".into(),
120 "Corners".into(),
121 "Edges".into(),
122 "Size".into(),
123 "Pixels".into(),
124 "PointF".into(),
125 "Hsla".into(),
126 "ContentMask".into(),
127 "Uniforms".into(),
128 "AtlasTile".into(),
129 "PathRasterizationInputIndex".into(),
130 "PathVertex_ScaledPixels".into(),
131 "PathRasterizationVertex".into(),
132 "ShadowInputIndex".into(),
133 "Shadow".into(),
134 "QuadInputIndex".into(),
135 "Underline".into(),
136 "UnderlineInputIndex".into(),
137 "Quad".into(),
138 "BorderStyle".into(),
139 "SpriteInputIndex".into(),
140 "MonochromeSprite".into(),
141 "PolychromeSprite".into(),
142 "PathSprite".into(),
143 "SurfaceInputIndex".into(),
144 "SurfaceBounds".into(),
145 "TransformationMatrix".into(),
146 ]);
147 config.no_includes = true;
148 config.enumeration.prefix_with_name = true;
149
150 let mut builder = cbindgen::Builder::new();
151
152 let src_paths = [
153 crate_dir.join("src/scene.rs"),
154 crate_dir.join("src/geometry.rs"),
155 crate_dir.join("src/color.rs"),
156 crate_dir.join("src/window.rs"),
157 crate_dir.join("src/platform.rs"),
158 crate_dir.join("src/platform/mac/metal_renderer.rs"),
159 ];
160 for src_path in src_paths {
161 println!("cargo:rerun-if-changed={}", src_path.display());
162 builder = builder.with_src(src_path);
163 }
164
165 builder
166 .with_config(config)
167 .generate()
168 .expect("Unable to generate bindings")
169 .write_to_file(&output_path);
170
171 output_path
172 }
173
174 /// To enable runtime compilation, we need to "stitch" the shaders file with the generated header
175 /// so that it is self-contained.
176 #[cfg(feature = "runtime_shaders")]
177 fn emit_stitched_shaders(header_path: &Path) {
178 use std::str::FromStr;
179 fn stitch_header(header: &Path, shader_path: &Path) -> std::io::Result<PathBuf> {
180 let header_contents = std::fs::read_to_string(header)?;
181 let shader_contents = std::fs::read_to_string(shader_path)?;
182 let stitched_contents = format!("{header_contents}\n{shader_contents}");
183 let out_path =
184 PathBuf::from(env::var("OUT_DIR").unwrap()).join("stitched_shaders.metal");
185 std::fs::write(&out_path, stitched_contents)?;
186 Ok(out_path)
187 }
188 let shader_source_path = "./src/platform/mac/shaders.metal";
189 let shader_path = PathBuf::from_str(shader_source_path).unwrap();
190 stitch_header(header_path, &shader_path).unwrap();
191 println!("cargo:rerun-if-changed={}", &shader_source_path);
192 }
193
194 #[cfg(not(feature = "runtime_shaders"))]
195 fn compile_metal_shaders(header_path: &Path) {
196 use std::process::{self, Command};
197 let shader_path = "./src/platform/mac/shaders.metal";
198 let air_output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("shaders.air");
199 let metallib_output_path =
200 PathBuf::from(env::var("OUT_DIR").unwrap()).join("shaders.metallib");
201 println!("cargo:rerun-if-changed={}", shader_path);
202
203 let output = Command::new("xcrun")
204 .args([
205 "-sdk",
206 "macosx",
207 "metal",
208 "-gline-tables-only",
209 "-mmacosx-version-min=10.15.7",
210 "-MO",
211 "-c",
212 shader_path,
213 "-include",
214 (header_path.to_str().unwrap()),
215 "-o",
216 ])
217 .arg(&air_output_path)
218 .output()
219 .unwrap();
220
221 if !output.status.success() {
222 eprintln!(
223 "metal shader compilation failed:\n{}",
224 String::from_utf8_lossy(&output.stderr)
225 );
226 process::exit(1);
227 }
228
229 let output = Command::new("xcrun")
230 .args(["-sdk", "macosx", "metallib"])
231 .arg(air_output_path)
232 .arg("-o")
233 .arg(metallib_output_path)
234 .output()
235 .unwrap();
236
237 if !output.status.success() {
238 eprintln!(
239 "metallib compilation failed:\n{}",
240 String::from_utf8_lossy(&output.stderr)
241 );
242 process::exit(1);
243 }
244 }
245}
246
247#[cfg(target_os = "windows")]
248mod windows {
249 use std::{
250 fs,
251 io::Write,
252 path::{Path, PathBuf},
253 process::{self, Command},
254 };
255
256 pub(super) fn build() {
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 #[cfg(feature = "windows-manifest")]
267 fn embed_resource() {
268 let manifest = std::path::Path::new("resources/windows/gpui.manifest.xml");
269 let rc_file = std::path::Path::new("resources/windows/gpui.rc");
270 println!("cargo:rerun-if-changed={}", manifest.display());
271 println!("cargo:rerun-if-changed={}", rc_file.display());
272 embed_resource::compile(rc_file, embed_resource::NONE)
273 .manifest_required()
274 .unwrap();
275 }
276
277 /// You can set the `GPUI_FXC_PATH` environment variable to specify the path to the fxc.exe compiler.
278 fn compile_shaders() {
279 let shader_path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap())
280 .join("src/platform/windows/shaders.hlsl");
281 let out_dir = std::env::var("OUT_DIR").unwrap();
282
283 println!("cargo:rerun-if-changed={}", shader_path.display());
284
285 // Check if fxc.exe is available
286 let fxc_path = find_fxc_compiler();
287
288 // Define all modules
289 let modules = [
290 "quad",
291 "shadow",
292 "path_rasterization",
293 "path_sprite",
294 "underline",
295 "monochrome_sprite",
296 "polychrome_sprite",
297 ];
298
299 let rust_binding_path = format!("{}/shaders_bytes.rs", out_dir);
300 if Path::new(&rust_binding_path).exists() {
301 fs::remove_file(&rust_binding_path)
302 .expect("Failed to remove existing Rust binding file");
303 }
304 for module in modules {
305 compile_shader_for_module(
306 module,
307 &out_dir,
308 &fxc_path,
309 shader_path.to_str().unwrap(),
310 &rust_binding_path,
311 );
312 }
313
314 {
315 let shader_path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap())
316 .join("src/platform/windows/color_text_raster.hlsl");
317 compile_shader_for_module(
318 "emoji_rasterization",
319 &out_dir,
320 &fxc_path,
321 shader_path.to_str().unwrap(),
322 &rust_binding_path,
323 );
324 }
325 }
326
327 /// You can set the `GPUI_FXC_PATH` environment variable to specify the path to the fxc.exe compiler.
328 fn find_fxc_compiler() -> String {
329 // Check environment variable
330 if let Ok(path) = std::env::var("GPUI_FXC_PATH") {
331 if Path::new(&path).exists() {
332 return path;
333 }
334 }
335
336 // Try to find in PATH
337 // NOTE: This has to be `where.exe` on Windows, not `where`, it must be ended with `.exe`
338 if let Ok(output) = std::process::Command::new("where.exe")
339 .arg("fxc.exe")
340 .output()
341 {
342 if output.status.success() {
343 let path = String::from_utf8_lossy(&output.stdout);
344 return path.trim().to_string();
345 }
346 }
347
348 // Check the default path
349 if Path::new(r"C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64\fxc.exe")
350 .exists()
351 {
352 return r"C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64\fxc.exe"
353 .to_string();
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_4_1",
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_4_1",
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 "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 = fs::read_to_string(head_file).expect("Failed to read header file");
437 let const_definition = {
438 let global_var_start = header_content.find("const BYTE").unwrap();
439 let global_var = &header_content[global_var_start..];
440 let equal = global_var.find('=').unwrap();
441 global_var[equal + 1..].trim()
442 };
443 let rust_binding = format!(
444 "const {}: &[u8] = &{}\n",
445 const_name,
446 const_definition.replace('{', "[").replace('}', "]")
447 );
448 let mut options = fs::OpenOptions::new()
449 .create(true)
450 .append(true)
451 .open(output_path)
452 .expect("Failed to open Rust binding file");
453 options
454 .write_all(rust_binding.as_bytes())
455 .expect("Failed to write Rust binding file");
456 }
457}