#![allow(clippy::disallowed_methods, reason = "build scripts are exempt")] fn main() { #[cfg(target_os = "windows")] { // Compile HLSL shaders #[cfg(not(debug_assertions))] compile_shaders(); } } #[cfg(all(target_os = "windows", not(debug_assertions)))] mod shader_compilation { use std::{ fs, io::Write, path::{Path, PathBuf}, process::{self, Command}, }; pub fn compile_shaders() { let shader_path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()).join("src/shaders.hlsl"); let out_dir = std::env::var("OUT_DIR").unwrap(); println!("cargo:rerun-if-changed={}", shader_path.display()); // Check if fxc.exe is available let fxc_path = find_fxc_compiler(); // Define all modules let modules = [ "quad", "shadow", "path_rasterization", "path_sprite", "underline", "monochrome_sprite", "subpixel_sprite", "polychrome_sprite", ]; let rust_binding_path = format!("{}/shaders_bytes.rs", out_dir); if Path::new(&rust_binding_path).exists() { fs::remove_file(&rust_binding_path) .expect("Failed to remove existing Rust binding file"); } for module in modules { compile_shader_for_module( module, &out_dir, &fxc_path, shader_path.to_str().unwrap(), &rust_binding_path, ); } { let shader_path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()) .join("src/color_text_raster.hlsl"); compile_shader_for_module( "emoji_rasterization", &out_dir, &fxc_path, shader_path.to_str().unwrap(), &rust_binding_path, ); } } /// Locate `binary` in the newest installed Windows SDK. pub fn find_latest_windows_sdk_binary( binary: &str, ) -> Result, Box> { let key = windows_registry::LOCAL_MACHINE .open("SOFTWARE\\WOW6432Node\\Microsoft\\Microsoft SDKs\\Windows\\v10.0")?; let install_folder: String = key.get_string("InstallationFolder")?; // "C:\Program Files (x86)\Windows Kits\10\" let install_folder_bin = Path::new(&install_folder).join("bin"); let mut versions: Vec<_> = std::fs::read_dir(&install_folder_bin)? .flatten() .filter(|entry| entry.path().is_dir()) .filter_map(|entry| entry.file_name().into_string().ok()) .collect(); versions.sort_by_key(|s| { s.split('.') .filter_map(|p| p.parse().ok()) .collect::>() }); let arch = match std::env::consts::ARCH { "x86_64" => "x64", "aarch64" => "arm64", _ => Err(format!( "Unsupported architecture: {}", std::env::consts::ARCH ))?, }; if let Some(highest_version) = versions.last() { return Ok(Some( install_folder_bin .join(highest_version) .join(arch) .join(binary), )); } Ok(None) } /// You can set the `GPUI_FXC_PATH` environment variable to specify the path to the fxc.exe compiler. fn find_fxc_compiler() -> String { // Check environment variable if let Ok(path) = std::env::var("GPUI_FXC_PATH") && Path::new(&path).exists() { return path; } // Try to find in PATH // NOTE: This has to be `where.exe` on Windows, not `where`, it must be ended with `.exe` if let Ok(output) = std::process::Command::new("where.exe") .arg("fxc.exe") .output() && output.status.success() { let path = String::from_utf8_lossy(&output.stdout); return path.trim().to_string(); } if let Ok(Some(path)) = find_latest_windows_sdk_binary("fxc.exe") { return path.to_string_lossy().into_owned(); } panic!("Failed to find fxc.exe"); } fn compile_shader_for_module( module: &str, out_dir: &str, fxc_path: &str, shader_path: &str, rust_binding_path: &str, ) { // Compile vertex shader let output_file = format!("{}/{}_vs.h", out_dir, module); let const_name = format!("{}_VERTEX_BYTES", module.to_uppercase()); compile_shader_impl( fxc_path, &format!("{module}_vertex"), &output_file, &const_name, shader_path, "vs_4_1", ); generate_rust_binding(&const_name, &output_file, rust_binding_path); // Compile fragment shader let output_file = format!("{}/{}_ps.h", out_dir, module); let const_name = format!("{}_FRAGMENT_BYTES", module.to_uppercase()); compile_shader_impl( fxc_path, &format!("{module}_fragment"), &output_file, &const_name, shader_path, "ps_4_1", ); generate_rust_binding(&const_name, &output_file, rust_binding_path); } fn compile_shader_impl( fxc_path: &str, entry_point: &str, output_path: &str, var_name: &str, shader_path: &str, target: &str, ) { let output = Command::new(fxc_path) .args([ "/T", target, "/E", entry_point, "/Fh", output_path, "/Vn", var_name, "/O3", shader_path, ]) .output(); match output { Ok(result) => { if result.status.success() { return; } println!( "cargo::error=Shader compilation failed for {}:\n{}", entry_point, String::from_utf8_lossy(&result.stderr) ); process::exit(1); } Err(e) => { println!("cargo::error=Failed to run fxc for {}: {}", entry_point, e); process::exit(1); } } } fn generate_rust_binding(const_name: &str, head_file: &str, output_path: &str) { let header_content = fs::read_to_string(head_file).expect("Failed to read header file"); let const_definition = { let global_var_start = header_content.find("const BYTE").unwrap(); let global_var = &header_content[global_var_start..]; let equal = global_var.find('=').unwrap(); global_var[equal + 1..].trim() }; let rust_binding = format!( "const {}: &[u8] = &{}\n", const_name, const_definition.replace('{', "[").replace('}', "]") ); let mut options = fs::OpenOptions::new() .create(true) .append(true) .open(output_path) .expect("Failed to open Rust binding file"); options .write_all(rust_binding.as_bytes()) .expect("Failed to write Rust binding file"); } } #[cfg(all(target_os = "windows", not(debug_assertions)))] use shader_compilation::compile_shaders;