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