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