1use std::{
2 env,
3 path::{Path, PathBuf},
4};
5
6use cbindgen::Config;
7
8fn main() {
9 generate_dispatch_bindings();
10 let header_path = generate_shader_bindings();
11 #[cfg(feature = "runtime_shaders")]
12 emit_stitched_shaders(&header_path);
13 #[cfg(not(feature = "runtime_shaders"))]
14 compile_metal_shaders(&header_path);
15}
16
17fn generate_dispatch_bindings() {
18 println!("cargo:rustc-link-lib=framework=System");
19 println!("cargo:rerun-if-changed=src/platform/mac/dispatch.h");
20
21 let bindings = bindgen::Builder::default()
22 .header("src/platform/mac/dispatch.h")
23 .allowlist_var("_dispatch_main_q")
24 .allowlist_var("DISPATCH_QUEUE_PRIORITY_DEFAULT")
25 .allowlist_var("DISPATCH_QUEUE_PRIORITY_HIGH")
26 .allowlist_var("DISPATCH_TIME_NOW")
27 .allowlist_function("dispatch_get_global_queue")
28 .allowlist_function("dispatch_async_f")
29 .allowlist_function("dispatch_after_f")
30 .allowlist_function("dispatch_time")
31 .parse_callbacks(Box::new(bindgen::CargoCallbacks))
32 .layout_tests(false)
33 .generate()
34 .expect("unable to generate bindings");
35
36 let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
37 bindings
38 .write_to_file(out_path.join("dispatch_sys.rs"))
39 .expect("couldn't write dispatch bindings");
40}
41
42fn generate_shader_bindings() -> PathBuf {
43 let output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("scene.h");
44 let crate_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
45 let mut config = Config::default();
46 config.include_guard = Some("SCENE_H".into());
47 config.language = cbindgen::Language::C;
48 config.export.include.extend([
49 "Bounds".into(),
50 "Corners".into(),
51 "Edges".into(),
52 "Size".into(),
53 "Pixels".into(),
54 "PointF".into(),
55 "Hsla".into(),
56 "ContentMask".into(),
57 "Uniforms".into(),
58 "AtlasTile".into(),
59 "PathRasterizationInputIndex".into(),
60 "PathVertex_ScaledPixels".into(),
61 "ShadowInputIndex".into(),
62 "Shadow".into(),
63 "QuadInputIndex".into(),
64 "Underline".into(),
65 "UnderlineInputIndex".into(),
66 "Quad".into(),
67 "SpriteInputIndex".into(),
68 "MonochromeSprite".into(),
69 "PolychromeSprite".into(),
70 "PathSprite".into(),
71 "SurfaceInputIndex".into(),
72 "SurfaceBounds".into(),
73 ]);
74 config.no_includes = true;
75 config.enumeration.prefix_with_name = true;
76
77 let mut builder = cbindgen::Builder::new();
78
79 let src_paths = [
80 crate_dir.join("src/scene.rs"),
81 crate_dir.join("src/geometry.rs"),
82 crate_dir.join("src/color.rs"),
83 crate_dir.join("src/window.rs"),
84 crate_dir.join("src/platform.rs"),
85 crate_dir.join("src/platform/mac/metal_renderer.rs"),
86 ];
87 for src_path in src_paths {
88 println!("cargo:rerun-if-changed={}", src_path.display());
89 builder = builder.with_src(src_path);
90 }
91
92 builder
93 .with_config(config)
94 .generate()
95 .expect("Unable to generate bindings")
96 .write_to_file(&output_path);
97
98 output_path
99}
100
101/// To enable runtime compilation, we need to "stitch" the shaders file with the generated header
102/// so that it is self-contained.
103#[cfg(feature = "runtime_shaders")]
104fn emit_stitched_shaders(header_path: &Path) {
105 use std::str::FromStr;
106 fn stitch_header(header: &Path, shader_path: &Path) -> std::io::Result<PathBuf> {
107 let header_contents = std::fs::read_to_string(header)?;
108 let shader_contents = std::fs::read_to_string(shader_path)?;
109 let stitched_contents = format!("{header_contents}\n{shader_contents}");
110 let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("stitched_shaders.metal");
111 let _ = std::fs::write(&out_path, stitched_contents)?;
112 Ok(out_path)
113 }
114 let shader_source_path = "./src/platform/mac/shaders.metal";
115 let shader_path = PathBuf::from_str(shader_source_path).unwrap();
116 stitch_header(header_path, &shader_path).unwrap();
117 println!("cargo:rerun-if-changed={}", &shader_source_path);
118}
119#[cfg(not(feature = "runtime_shaders"))]
120fn compile_metal_shaders(header_path: &Path) {
121 use std::process::{self, Command};
122 let shader_path = "./src/platform/mac/shaders.metal";
123 let air_output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("shaders.air");
124 let metallib_output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("shaders.metallib");
125 println!("cargo:rerun-if-changed={}", shader_path);
126
127 let output = Command::new("xcrun")
128 .args([
129 "-sdk",
130 "macosx",
131 "metal",
132 "-gline-tables-only",
133 "-mmacosx-version-min=10.15.7",
134 "-MO",
135 "-c",
136 shader_path,
137 "-include",
138 &header_path.to_str().unwrap(),
139 "-o",
140 ])
141 .arg(&air_output_path)
142 .output()
143 .unwrap();
144
145 if !output.status.success() {
146 eprintln!(
147 "metal shader compilation failed:\n{}",
148 String::from_utf8_lossy(&output.stderr)
149 );
150 process::exit(1);
151 }
152
153 let output = Command::new("xcrun")
154 .args(["-sdk", "macosx", "metallib"])
155 .arg(air_output_path)
156 .arg("-o")
157 .arg(metallib_output_path)
158 .output()
159 .unwrap();
160
161 if !output.status.success() {
162 eprintln!(
163 "metallib compilation failed:\n{}",
164 String::from_utf8_lossy(&output.stderr)
165 );
166 process::exit(1);
167 }
168}