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 // Compile HLSL shaders
255 #[cfg(not(debug_assertions))]
256 compile_shaders();
257
258 // Embed the Windows manifest and resource file
259 #[cfg(feature = "windows-manifest")]
260 embed_resource();
261 }
262
263 #[cfg(feature = "windows-manifest")]
264 fn embed_resource() {
265 let manifest = std::path::Path::new("resources/windows/gpui.manifest.xml");
266 let rc_file = std::path::Path::new("resources/windows/gpui.rc");
267 println!("cargo:rerun-if-changed={}", manifest.display());
268 println!("cargo:rerun-if-changed={}", rc_file.display());
269 embed_resource::compile(rc_file, embed_resource::NONE)
270 .manifest_required()
271 .unwrap();
272 }
273
274 /// You can set the `GPUI_FXC_PATH` environment variable to specify the path to the fxc.exe compiler.
275 fn compile_shaders() {
276 let shader_path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap())
277 .join("src/platform/windows/shaders.hlsl");
278 let out_dir = std::env::var("OUT_DIR").unwrap();
279
280 println!("cargo:rerun-if-changed={}", shader_path.display());
281
282 // Check if fxc.exe is available
283 let fxc_path = find_fxc_compiler();
284
285 // Define all modules
286 let modules = [
287 "quad",
288 "shadow",
289 "paths",
290 "underline",
291 "monochrome_sprite",
292 "polychrome_sprite",
293 ];
294
295 let rust_binding_path = format!("{}/shaders_bytes.rs", out_dir);
296 if Path::new(&rust_binding_path).exists() {
297 fs::remove_file(&rust_binding_path)
298 .expect("Failed to remove existing Rust binding file");
299 }
300 for module in modules {
301 compile_shader_for_module(
302 module,
303 &out_dir,
304 &fxc_path,
305 shader_path.to_str().unwrap(),
306 &rust_binding_path,
307 );
308 }
309
310 {
311 let shader_path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap())
312 .join("src/platform/windows/color_text_raster.hlsl");
313 compile_shader_for_module(
314 "emoji_rasterization",
315 &out_dir,
316 &fxc_path,
317 shader_path.to_str().unwrap(),
318 &rust_binding_path,
319 );
320 }
321 }
322
323 /// You can set the `GPUI_FXC_PATH` environment variable to specify the path to the fxc.exe compiler.
324 fn find_fxc_compiler() -> String {
325 // Check environment variable
326 if let Ok(path) = std::env::var("GPUI_FXC_PATH") {
327 if Path::new(&path).exists() {
328 return path;
329 }
330 }
331
332 // Try to find in PATH
333 if let Ok(output) = std::process::Command::new("where").arg("fxc.exe").output() {
334 if output.status.success() {
335 let path = String::from_utf8_lossy(&output.stdout);
336 return path.trim().to_string();
337 }
338 }
339
340 // Check the default path
341 if Path::new(r"C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64\fxc.exe")
342 .exists()
343 {
344 return r"C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64\fxc.exe"
345 .to_string();
346 }
347
348 panic!("Failed to find fxc.exe");
349 }
350
351 fn compile_shader_for_module(
352 module: &str,
353 out_dir: &str,
354 fxc_path: &str,
355 shader_path: &str,
356 rust_binding_path: &str,
357 ) {
358 // Compile vertex shader
359 let output_file = format!("{}/{}_vs.h", out_dir, module);
360 let const_name = format!("{}_VERTEX_BYTES", module.to_uppercase());
361 compile_shader_impl(
362 fxc_path,
363 &format!("{module}_vertex"),
364 &output_file,
365 &const_name,
366 shader_path,
367 "vs_5_0",
368 );
369 generate_rust_binding(&const_name, &output_file, &rust_binding_path);
370
371 // Compile fragment shader
372 let output_file = format!("{}/{}_ps.h", out_dir, module);
373 let const_name = format!("{}_FRAGMENT_BYTES", module.to_uppercase());
374 compile_shader_impl(
375 fxc_path,
376 &format!("{module}_fragment"),
377 &output_file,
378 &const_name,
379 shader_path,
380 "ps_5_0",
381 );
382 generate_rust_binding(&const_name, &output_file, &rust_binding_path);
383 }
384
385 fn compile_shader_impl(
386 fxc_path: &str,
387 entry_point: &str,
388 output_path: &str,
389 var_name: &str,
390 shader_path: &str,
391 target: &str,
392 ) {
393 let output = Command::new(fxc_path)
394 .args([
395 "/T",
396 target,
397 "/E",
398 entry_point,
399 "/Fh",
400 output_path,
401 "/Vn",
402 var_name,
403 "/O3",
404 shader_path,
405 ])
406 .output();
407
408 match output {
409 Ok(result) => {
410 if result.status.success() {
411 return;
412 }
413 eprintln!(
414 "Pixel shader compilation failed for {}:\n{}",
415 entry_point,
416 String::from_utf8_lossy(&result.stderr)
417 );
418 process::exit(1);
419 }
420 Err(e) => {
421 eprintln!("Failed to run fxc for {}: {}", entry_point, e);
422 process::exit(1);
423 }
424 }
425 }
426
427 fn generate_rust_binding(const_name: &str, head_file: &str, output_path: &str) {
428 let header_content = fs::read_to_string(head_file).expect("Failed to read header file");
429 let const_definition = {
430 let global_var_start = header_content.find("const BYTE").unwrap();
431 let global_var = &header_content[global_var_start..];
432 let equal = global_var.find('=').unwrap();
433 global_var[equal + 1..].trim()
434 };
435 let rust_binding = format!(
436 "const {}: &[u8] = &{}\n",
437 const_name,
438 const_definition.replace('{', "[").replace('}', "]")
439 );
440 let mut options = fs::OpenOptions::new()
441 .create(true)
442 .append(true)
443 .open(output_path)
444 .expect("Failed to open Rust binding file");
445 options
446 .write_all(rust_binding.as_bytes())
447 .expect("Failed to write Rust binding file");
448 }
449}