mirror of
https://github.com/Sunscreen-tech/Sunscreen.git
synced 2026-01-09 21:58:02 -05:00
345 lines
11 KiB
Rust
345 lines
11 KiB
Rust
use std::process::Output;
|
|
|
|
#[cfg(feature = "cuda")]
|
|
fn compile_cuda_shaders() {
|
|
use std::{path::PathBuf, process::Command};
|
|
|
|
use find_cuda_helper::find_cuda_root;
|
|
|
|
let outdir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
|
|
let cuda_root = find_cuda_root().unwrap();
|
|
let shaders_dir = PathBuf::from(".")
|
|
.join("src")
|
|
.join("cuda_impl")
|
|
.join("shaders");
|
|
|
|
let nvcc = cuda_root.join("bin").join("nvcc");
|
|
let nvlink = cuda_root.join("bin").join("nvlink");
|
|
let is_cu_file = |file: &std::fs::DirEntry| {
|
|
file.file_name().to_string_lossy().ends_with(".cu") && file.file_type().unwrap().is_file()
|
|
};
|
|
|
|
println!("cargo:rerun-if-changed=src/cuda_impl/shaders");
|
|
|
|
for config in ["test", "release"] {
|
|
let shaders = std::fs::read_dir(&shaders_dir)
|
|
.unwrap()
|
|
.filter_map(Result::ok)
|
|
.filter(is_cu_file);
|
|
|
|
let mut out_files = vec![];
|
|
|
|
for s in shaders {
|
|
let filename = s.file_name().to_string_lossy().into_owned();
|
|
let srcfile = shaders_dir.join(&filename);
|
|
let outfile = outdir.join(format!("{filename}.ptx"));
|
|
|
|
let c = Command::new(&nvcc)
|
|
.arg("-Werror")
|
|
.arg("all-warnings")
|
|
.arg("-I")
|
|
.arg("src/cuda_impl/shaders/include")
|
|
.arg("--ptx")
|
|
.arg("--relocatable-device-code")
|
|
.arg("true")
|
|
.arg("--generate-line-info")
|
|
.arg("-O4")
|
|
.arg("-D")
|
|
.arg("CUDA_C")
|
|
.arg("-D")
|
|
.arg(config.to_uppercase())
|
|
.arg("-o")
|
|
.arg(&outfile)
|
|
.arg(srcfile)
|
|
.output()
|
|
.unwrap();
|
|
|
|
validate_command_output(c, "nvcc compilation failed.");
|
|
|
|
out_files.push(outfile);
|
|
}
|
|
|
|
let binary = outdir.join(format!("sunscreen_math.{config}.fatbin"));
|
|
|
|
let c = Command::new(&nvlink)
|
|
.arg("--arch")
|
|
.arg("sm_75")
|
|
.arg("-o")
|
|
.arg(&binary)
|
|
.arg("--verbose")
|
|
.args(out_files)
|
|
.output()
|
|
.unwrap();
|
|
|
|
validate_command_output(c, "nvcc compilation failed.");
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "opencl")]
|
|
fn compile_opencl_shaders() {
|
|
use std::{env, ffi::CString, fs::read_to_string, path::PathBuf, process::Command};
|
|
|
|
use ocl::{Context, Device, Platform, Program};
|
|
|
|
// We first preprocess `main.cl` with Clang so we don't
|
|
// need to fool with include paths when calling `Program::with_source()`. We
|
|
// then include_str!() the contents of the preprocessed files in
|
|
// a constant vector in OUT_DIR/{test,release}-shaders.rs.
|
|
//
|
|
// This allows us to have a fairly sane developer workflow for shaders
|
|
// whereby you can have multiple .cl files and headers under
|
|
// `src/opencl_impl/sharders/include`.
|
|
//
|
|
// We're ultimately targeting OpenCL 1.2, as this is the version with
|
|
// broadest support from vendors. The OpenCL 3.0 features that make it
|
|
// compelling (i.e. offline compiler, C++, and SPIRV support) are in
|
|
// fact optional and not supported by Nvidia, who is one of the most
|
|
// important vendors.
|
|
let outdir = PathBuf::from(env::var("OUT_DIR").unwrap());
|
|
|
|
let shader_dir = PathBuf::from(".")
|
|
.join("src")
|
|
.join("opencl_impl")
|
|
.join("shaders");
|
|
|
|
let include_dir = PathBuf::from(".")
|
|
.join("src")
|
|
.join("opencl_impl")
|
|
.join("shaders")
|
|
.join("include");
|
|
|
|
println!("cargo:rerun-if-changed={}", shader_dir.to_string_lossy());
|
|
|
|
// We precompile the sources with e.g. -DTEST or -DRELEASE so you can conditionally compile
|
|
// shaders for using with `cargo test` that don't get included in released libraries.
|
|
for config in ["test", "release"] {
|
|
println!("Profile {}", config);
|
|
|
|
let main_cl = PathBuf::from(&shader_dir).join("main.cl");
|
|
let outfile = PathBuf::from(&outdir).join(format!("{}_main.cl", config));
|
|
|
|
let output = Command::new("clang")
|
|
.arg("--precompile")
|
|
.arg("-cl-no-stdinc")
|
|
.arg("-I")
|
|
.arg(&include_dir)
|
|
.arg(format!("-D{}", config.to_ascii_uppercase()))
|
|
.arg("-o")
|
|
.arg(&outfile)
|
|
.arg(&main_cl)
|
|
.output()
|
|
.unwrap();
|
|
|
|
validate_command_output(output, "Shader precompilation failed.");
|
|
|
|
let src = CString::new(read_to_string(outfile).unwrap()).unwrap();
|
|
|
|
let platform = Platform::first().unwrap();
|
|
let device = Device::first(platform).unwrap();
|
|
let ctx = Context::builder().devices(device).build().unwrap();
|
|
|
|
// Assert we can compile our program and print any errors if not.
|
|
let program =
|
|
Program::with_source(&ctx, &[src], Some(&[device]), &CString::new("").unwrap());
|
|
|
|
match program {
|
|
Ok(_) => {}
|
|
Err(e) => {
|
|
println!("{}", e);
|
|
panic!();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "webgpu")]
|
|
// This simply concatenates all the wgsl shaders, which get compiled at runtime.
|
|
fn compile_wgsl_shaders() {
|
|
use std::fs::{read_to_string, DirEntry, File};
|
|
use std::io::Write;
|
|
use std::path::PathBuf;
|
|
|
|
use naga::valid::{Capabilities, ValidationFlags};
|
|
use wgpu_core::pipeline::{CreateShaderModuleError, ShaderError};
|
|
|
|
let outdir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
|
|
let shader_dir = PathBuf::from(".")
|
|
.join("src")
|
|
.join("webgpu_impl")
|
|
.join("shaders");
|
|
|
|
println!("cargo:rerun-if-changed=src/webgpu_impl/shaders");
|
|
|
|
for config in ["test", "release"] {
|
|
let is_wgsl_file = |file: &DirEntry| {
|
|
file.file_name().to_string_lossy().ends_with(".wgsl")
|
|
&& file.file_type().unwrap().is_file()
|
|
};
|
|
|
|
let is_test_wgsl_file = |file: &DirEntry| {
|
|
file.file_name().to_string_lossy().ends_with(".test.wgsl")
|
|
&& file.file_type().unwrap().is_file()
|
|
};
|
|
|
|
let include_file: Box<dyn Fn(&DirEntry) -> bool> = if config == "test" {
|
|
Box::new(is_wgsl_file)
|
|
} else {
|
|
Box::new(|file: &DirEntry| is_wgsl_file(file) && !is_test_wgsl_file(file))
|
|
};
|
|
|
|
let shaders = std::fs::read_dir(&shader_dir)
|
|
.unwrap()
|
|
.filter_map(Result::ok)
|
|
.filter(include_file);
|
|
|
|
let out_file_path = outdir.join(format!("shaders-{config}.wgsl"));
|
|
|
|
{
|
|
let mut out_file = File::create(&out_file_path).unwrap();
|
|
|
|
for s in shaders {
|
|
let data = read_to_string(s.path()).unwrap();
|
|
|
|
writeln!(out_file, "{}", data).unwrap();
|
|
}
|
|
};
|
|
|
|
// Validate the shader
|
|
let shader_contents = read_to_string(&out_file_path).unwrap();
|
|
|
|
let parse_result = naga::front::wgsl::parse_str(&shader_contents);
|
|
|
|
if let Err(e) = parse_result {
|
|
let e = ShaderError {
|
|
source: shader_contents,
|
|
label: None,
|
|
inner: Box::new(e),
|
|
};
|
|
|
|
let e = CreateShaderModuleError::Parsing(e);
|
|
panic!("{}", e);
|
|
}
|
|
|
|
let mut validator =
|
|
naga::valid::Validator::new(ValidationFlags::all(), Capabilities::empty());
|
|
|
|
let validation_results = validator.validate(&parse_result.unwrap());
|
|
|
|
if let Err(e) = validation_results {
|
|
let e = ShaderError {
|
|
source: shader_contents,
|
|
label: None,
|
|
inner: Box::new(e),
|
|
};
|
|
|
|
let e = CreateShaderModuleError::Validation(e);
|
|
|
|
panic!("{}", e.to_string());
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "metal")]
|
|
fn compile_metal_shaders() {
|
|
use std::{path::PathBuf, process::Command};
|
|
|
|
let outdir = std::env::var("OUT_DIR").unwrap();
|
|
let shader_dir = PathBuf::from(".")
|
|
.join("src")
|
|
.join("metal_impl")
|
|
.join("shaders");
|
|
|
|
println!("cargo:rerun-if-changed=src/metal_impl/shaders");
|
|
|
|
let include_dir = shader_dir.join("include");
|
|
|
|
for config in ["test", "release"] {
|
|
let mut air_files = vec![];
|
|
|
|
let is_metal_file = |file: &std::fs::DirEntry| {
|
|
file.file_name().to_string_lossy().ends_with(".metal")
|
|
&& file.file_type().unwrap().is_file()
|
|
};
|
|
|
|
let shaders = std::fs::read_dir(&shader_dir)
|
|
.unwrap()
|
|
.filter_map(Result::ok)
|
|
.filter(is_metal_file);
|
|
|
|
for file in shaders {
|
|
let filename = file.file_name().to_string_lossy().into_owned();
|
|
|
|
let out_name = if !file.file_name().to_string_lossy().ends_with(".metal")
|
|
|| !file.file_type().unwrap().is_file()
|
|
{
|
|
continue;
|
|
} else {
|
|
format!("{}.air", filename.strip_suffix(".metal").unwrap())
|
|
};
|
|
|
|
let outfile = PathBuf::from(&outdir).join(&out_name);
|
|
|
|
air_files.push(outfile.clone());
|
|
|
|
let output = Command::new("xcrun")
|
|
.arg("-sdk")
|
|
.arg("macosx")
|
|
.arg("metal")
|
|
.arg("-g")
|
|
.arg("-Wall")
|
|
.arg("-Werror")
|
|
.arg(format!("-D{}", config.to_uppercase()))
|
|
.arg("-c")
|
|
.arg(file.path())
|
|
.arg("-I")
|
|
.arg(&include_dir)
|
|
.arg("-o")
|
|
.arg(outfile)
|
|
.output()
|
|
.unwrap();
|
|
|
|
validate_command_output(output, "Shader compilation failed.");
|
|
}
|
|
|
|
let libout = PathBuf::from(&outdir).join(format!("curve25519-dalek.{config}.metallib"));
|
|
|
|
let output = Command::new("xcrun")
|
|
.arg("-sdk")
|
|
.arg("macosx")
|
|
.arg("metallib")
|
|
.args(air_files)
|
|
.arg("-o")
|
|
.arg(libout)
|
|
.output()
|
|
.unwrap();
|
|
|
|
validate_command_output(output, "Shader compilation failed.");
|
|
}
|
|
}
|
|
|
|
#[allow(unused)]
|
|
fn validate_command_output(output: Output, panic_msg: &str) {
|
|
println!("===stderr===");
|
|
println!("{}", String::from_utf8_lossy(&output.stderr));
|
|
|
|
println!("===stdout===");
|
|
println!("{}", String::from_utf8_lossy(&output.stdout));
|
|
if !output.status.success() {
|
|
panic!("{}", panic_msg);
|
|
}
|
|
}
|
|
|
|
fn main() {
|
|
#[cfg(feature = "metal")]
|
|
compile_metal_shaders();
|
|
|
|
#[cfg(feature = "webgpu")]
|
|
compile_wgsl_shaders();
|
|
|
|
#[cfg(feature = "opencl")]
|
|
compile_opencl_shaders();
|
|
|
|
#[cfg(feature = "cuda")]
|
|
compile_cuda_shaders();
|
|
}
|