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