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 && Path::new(&path).exists()
332 {
333 return path;
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 && output.status.success()
342 {
343 let path = String::from_utf8_lossy(&output.stdout);
344 return path.trim().to_string();
345 }
346
347 // Check the default path
348 if Path::new(r"C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64\fxc.exe")
349 .exists()
350 {
351 return r"C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64\fxc.exe"
352 .to_string();
353 }
354
355 panic!("Failed to find fxc.exe");
356 }
357
358 fn compile_shader_for_module(
359 module: &str,
360 out_dir: &str,
361 fxc_path: &str,
362 shader_path: &str,
363 rust_binding_path: &str,
364 ) {
365 // Compile vertex shader
366 let output_file = format!("{}/{}_vs.h", out_dir, module);
367 let const_name = format!("{}_VERTEX_BYTES", module.to_uppercase());
368 compile_shader_impl(
369 fxc_path,
370 &format!("{module}_vertex"),
371 &output_file,
372 &const_name,
373 shader_path,
374 "vs_4_1",
375 );
376 generate_rust_binding(&const_name, &output_file, rust_binding_path);
377
378 // Compile fragment shader
379 let output_file = format!("{}/{}_ps.h", out_dir, module);
380 let const_name = format!("{}_FRAGMENT_BYTES", module.to_uppercase());
381 compile_shader_impl(
382 fxc_path,
383 &format!("{module}_fragment"),
384 &output_file,
385 &const_name,
386 shader_path,
387 "ps_4_1",
388 );
389 generate_rust_binding(&const_name, &output_file, rust_binding_path);
390 }
391
392 fn compile_shader_impl(
393 fxc_path: &str,
394 entry_point: &str,
395 output_path: &str,
396 var_name: &str,
397 shader_path: &str,
398 target: &str,
399 ) {
400 let output = Command::new(fxc_path)
401 .args([
402 "/T",
403 target,
404 "/E",
405 entry_point,
406 "/Fh",
407 output_path,
408 "/Vn",
409 var_name,
410 "/O3",
411 shader_path,
412 ])
413 .output();
414
415 match output {
416 Ok(result) => {
417 if result.status.success() {
418 return;
419 }
420 eprintln!(
421 "Shader compilation failed for {}:\n{}",
422 entry_point,
423 String::from_utf8_lossy(&result.stderr)
424 );
425 process::exit(1);
426 }
427 Err(e) => {
428 eprintln!("Failed to run fxc for {}: {}", entry_point, e);
429 process::exit(1);
430 }
431 }
432 }
433
434 fn generate_rust_binding(const_name: &str, head_file: &str, output_path: &str) {
435 let header_content = fs::read_to_string(head_file).expect("Failed to read header file");
436 let const_definition = {
437 let global_var_start = header_content.find("const BYTE").unwrap();
438 let global_var = &header_content[global_var_start..];
439 let equal = global_var.find('=').unwrap();
440 global_var[equal + 1..].trim()
441 };
442 let rust_binding = format!(
443 "const {}: &[u8] = &{}\n",
444 const_name,
445 const_definition.replace('{', "[").replace('}', "]")
446 );
447 let mut options = fs::OpenOptions::new()
448 .create(true)
449 .append(true)
450 .open(output_path)
451 .expect("Failed to open Rust binding file");
452 options
453 .write_all(rust_binding.as_bytes())
454 .expect("Failed to write Rust binding file");
455 }
456}