1#![allow(clippy::disallowed_methods, reason = "build scripts are exempt")]
2
3fn main() {
4 #[cfg(target_os = "windows")]
5 {
6 // Compile HLSL shaders
7 #[cfg(not(debug_assertions))]
8 compile_shaders();
9 }
10}
11
12#[cfg(all(target_os = "windows", not(debug_assertions)))]
13mod shader_compilation {
14 use std::{
15 fs,
16 io::Write,
17 path::{Path, PathBuf},
18 process::{self, Command},
19 };
20
21 pub fn compile_shaders() {
22 let shader_path =
23 PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()).join("src/shaders.hlsl");
24 let out_dir = std::env::var("OUT_DIR").unwrap();
25
26 println!("cargo:rerun-if-changed={}", shader_path.display());
27
28 // Check if fxc.exe is available
29 let fxc_path = find_fxc_compiler();
30
31 // Define all modules
32 let modules = [
33 "quad",
34 "shadow",
35 "path_rasterization",
36 "path_sprite",
37 "underline",
38 "monochrome_sprite",
39 "subpixel_sprite",
40 "polychrome_sprite",
41 ];
42
43 let rust_binding_path = format!("{}/shaders_bytes.rs", out_dir);
44 if Path::new(&rust_binding_path).exists() {
45 fs::remove_file(&rust_binding_path)
46 .expect("Failed to remove existing Rust binding file");
47 }
48 for module in modules {
49 compile_shader_for_module(
50 module,
51 &out_dir,
52 &fxc_path,
53 shader_path.to_str().unwrap(),
54 &rust_binding_path,
55 );
56 }
57
58 {
59 let shader_path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap())
60 .join("src/color_text_raster.hlsl");
61 compile_shader_for_module(
62 "emoji_rasterization",
63 &out_dir,
64 &fxc_path,
65 shader_path.to_str().unwrap(),
66 &rust_binding_path,
67 );
68 }
69 }
70
71 /// Locate `binary` in the newest installed Windows SDK.
72 pub fn find_latest_windows_sdk_binary(
73 binary: &str,
74 ) -> Result<Option<PathBuf>, Box<dyn std::error::Error>> {
75 let key = windows_registry::LOCAL_MACHINE
76 .open("SOFTWARE\\WOW6432Node\\Microsoft\\Microsoft SDKs\\Windows\\v10.0")?;
77
78 let install_folder: String = key.get_string("InstallationFolder")?; // "C:\Program Files (x86)\Windows Kits\10\"
79 let install_folder_bin = Path::new(&install_folder).join("bin");
80
81 let mut versions: Vec<_> = std::fs::read_dir(&install_folder_bin)?
82 .flatten()
83 .filter(|entry| entry.path().is_dir())
84 .filter_map(|entry| entry.file_name().into_string().ok())
85 .collect();
86
87 versions.sort_by_key(|s| {
88 s.split('.')
89 .filter_map(|p| p.parse().ok())
90 .collect::<Vec<u32>>()
91 });
92
93 let arch = match std::env::consts::ARCH {
94 "x86_64" => "x64",
95 "aarch64" => "arm64",
96 _ => Err(format!(
97 "Unsupported architecture: {}",
98 std::env::consts::ARCH
99 ))?,
100 };
101
102 if let Some(highest_version) = versions.last() {
103 return Ok(Some(
104 install_folder_bin
105 .join(highest_version)
106 .join(arch)
107 .join(binary),
108 ));
109 }
110
111 Ok(None)
112 }
113
114 /// You can set the `GPUI_FXC_PATH` environment variable to specify the path to the fxc.exe compiler.
115 fn find_fxc_compiler() -> String {
116 // Check environment variable
117 if let Ok(path) = std::env::var("GPUI_FXC_PATH")
118 && Path::new(&path).exists()
119 {
120 return path;
121 }
122
123 // Try to find in PATH
124 // NOTE: This has to be `where.exe` on Windows, not `where`, it must be ended with `.exe`
125 if let Ok(output) = std::process::Command::new("where.exe")
126 .arg("fxc.exe")
127 .output()
128 && output.status.success()
129 {
130 let path = String::from_utf8_lossy(&output.stdout);
131 return path.trim().to_string();
132 }
133
134 if let Ok(Some(path)) = find_latest_windows_sdk_binary("fxc.exe") {
135 return path.to_string_lossy().into_owned();
136 }
137
138 panic!("Failed to find fxc.exe");
139 }
140
141 fn compile_shader_for_module(
142 module: &str,
143 out_dir: &str,
144 fxc_path: &str,
145 shader_path: &str,
146 rust_binding_path: &str,
147 ) {
148 // Compile vertex shader
149 let output_file = format!("{}/{}_vs.h", out_dir, module);
150 let const_name = format!("{}_VERTEX_BYTES", module.to_uppercase());
151 compile_shader_impl(
152 fxc_path,
153 &format!("{module}_vertex"),
154 &output_file,
155 &const_name,
156 shader_path,
157 "vs_4_1",
158 );
159 generate_rust_binding(&const_name, &output_file, rust_binding_path);
160
161 // Compile fragment shader
162 let output_file = format!("{}/{}_ps.h", out_dir, module);
163 let const_name = format!("{}_FRAGMENT_BYTES", module.to_uppercase());
164 compile_shader_impl(
165 fxc_path,
166 &format!("{module}_fragment"),
167 &output_file,
168 &const_name,
169 shader_path,
170 "ps_4_1",
171 );
172 generate_rust_binding(&const_name, &output_file, rust_binding_path);
173 }
174
175 fn compile_shader_impl(
176 fxc_path: &str,
177 entry_point: &str,
178 output_path: &str,
179 var_name: &str,
180 shader_path: &str,
181 target: &str,
182 ) {
183 let output = Command::new(fxc_path)
184 .args([
185 "/T",
186 target,
187 "/E",
188 entry_point,
189 "/Fh",
190 output_path,
191 "/Vn",
192 var_name,
193 "/O3",
194 shader_path,
195 ])
196 .output();
197
198 match output {
199 Ok(result) => {
200 if result.status.success() {
201 return;
202 }
203 println!(
204 "cargo::error=Shader compilation failed for {}:\n{}",
205 entry_point,
206 String::from_utf8_lossy(&result.stderr)
207 );
208 process::exit(1);
209 }
210 Err(e) => {
211 println!("cargo::error=Failed to run fxc for {}: {}", entry_point, e);
212 process::exit(1);
213 }
214 }
215 }
216
217 fn generate_rust_binding(const_name: &str, head_file: &str, output_path: &str) {
218 let header_content = fs::read_to_string(head_file).expect("Failed to read header file");
219 let const_definition = {
220 let global_var_start = header_content.find("const BYTE").unwrap();
221 let global_var = &header_content[global_var_start..];
222 let equal = global_var.find('=').unwrap();
223 global_var[equal + 1..].trim()
224 };
225 let rust_binding = format!(
226 "const {}: &[u8] = &{}\n",
227 const_name,
228 const_definition.replace('{', "[").replace('}', "]")
229 );
230 let mut options = fs::OpenOptions::new()
231 .create(true)
232 .append(true)
233 .open(output_path)
234 .expect("Failed to open Rust binding file");
235 options
236 .write_all(rust_binding.as_bytes())
237 .expect("Failed to write Rust binding file");
238 }
239}
240
241#[cfg(all(target_os = "windows", not(debug_assertions)))]
242use shader_compilation::compile_shaders;