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