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 /// You can set the `GPUI_FXC_PATH` environment variable to specify the path to the fxc.exe compiler.
316 fn find_fxc_compiler() -> String {
317 // Check environment variable
318 if let Ok(path) = std::env::var("GPUI_FXC_PATH") {
319 if Path::new(&path).exists() {
320 return path;
321 }
322 }
323
324 // Try to find in PATH
325 // NOTE: This has to be `where.exe` on Windows, not `where`, it must be ended with `.exe`
326 if let Ok(output) = std::process::Command::new("where.exe")
327 .arg("fxc.exe")
328 .output()
329 {
330 if output.status.success() {
331 let path = String::from_utf8_lossy(&output.stdout);
332 return path.trim().to_string();
333 }
334 }
335
336 // Check the default path
337 if Path::new(r"C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64\fxc.exe")
338 .exists()
339 {
340 return r"C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64\fxc.exe"
341 .to_string();
342 }
343
344 panic!("Failed to find fxc.exe");
345 }
346
347 fn compile_shader_for_module(
348 module: &str,
349 out_dir: &str,
350 fxc_path: &str,
351 shader_path: &str,
352 rust_binding_path: &str,
353 ) {
354 // Compile vertex shader
355 let output_file = format!("{}/{}_vs.h", out_dir, module);
356 let const_name = format!("{}_VERTEX_BYTES", module.to_uppercase());
357 compile_shader_impl(
358 fxc_path,
359 &format!("{module}_vertex"),
360 &output_file,
361 &const_name,
362 shader_path,
363 "vs_4_1",
364 );
365 generate_rust_binding(&const_name, &output_file, &rust_binding_path);
366
367 // Compile fragment shader
368 let output_file = format!("{}/{}_ps.h", out_dir, module);
369 let const_name = format!("{}_FRAGMENT_BYTES", module.to_uppercase());
370 compile_shader_impl(
371 fxc_path,
372 &format!("{module}_fragment"),
373 &output_file,
374 &const_name,
375 shader_path,
376 "ps_4_1",
377 );
378 generate_rust_binding(&const_name, &output_file, &rust_binding_path);
379 }
380
381 fn compile_shader_impl(
382 fxc_path: &str,
383 entry_point: &str,
384 output_path: &str,
385 var_name: &str,
386 shader_path: &str,
387 target: &str,
388 ) {
389 let output = Command::new(fxc_path)
390 .args([
391 "/T",
392 target,
393 "/E",
394 entry_point,
395 "/Fh",
396 output_path,
397 "/Vn",
398 var_name,
399 "/O3",
400 shader_path,
401 ])
402 .output();
403
404 match output {
405 Ok(result) => {
406 if result.status.success() {
407 return;
408 }
409 eprintln!(
410 "Shader compilation failed for {}:\n{}",
411 entry_point,
412 String::from_utf8_lossy(&result.stderr)
413 );
414 process::exit(1);
415 }
416 Err(e) => {
417 eprintln!("Failed to run fxc for {}: {}", entry_point, e);
418 process::exit(1);
419 }
420 }
421 }
422
423 fn generate_rust_binding(const_name: &str, head_file: &str, output_path: &str) {
424 let header_content = fs::read_to_string(head_file).expect("Failed to read header file");
425 let const_definition = {
426 let global_var_start = header_content.find("const BYTE").unwrap();
427 let global_var = &header_content[global_var_start..];
428 let equal = global_var.find('=').unwrap();
429 global_var[equal + 1..].trim()
430 };
431 let rust_binding = format!(
432 "const {}: &[u8] = &{}\n",
433 const_name,
434 const_definition.replace('{', "[").replace('}', "]")
435 );
436 let mut options = fs::OpenOptions::new()
437 .create(true)
438 .append(true)
439 .open(output_path)
440 .expect("Failed to open Rust binding file");
441 options
442 .write_all(rust_binding.as_bytes())
443 .expect("Failed to write Rust binding file");
444 }
445}