|
| 1 | +//! Rust GPU shader crate builder. |
| 2 | +//! |
| 3 | +//! This program and library allows you to easily compile your rust-gpu shaders, |
| 4 | +//! without requiring you to fix your entire project to a specific toolchain. |
| 5 | +//! |
| 6 | +//! # How it works |
| 7 | +//! |
| 8 | +//! This program primarily manages installations of `rustc_codegen_spirv`, the |
| 9 | +//! codegen backend of rust-gpu to generate SPIR-V shader binaries. The codegen |
| 10 | +//! backend builds on internal, ever-changing interfaces of rustc, which requires |
| 11 | +//! fixing a version of rust-gpu to a specific version of the rustc compiler. |
| 12 | +//! Usually, this would require you to fix your entire project to that specific |
| 13 | +//! toolchain, but this project loosens that requirement by managing installations |
| 14 | +//! of `rustc_codegen_spirv` and their associated toolchains for you. |
| 15 | +//! |
| 16 | +//! We continue to use rust-gpu's `spirv_builder` crate to pass the many additional |
| 17 | +//! parameters required to configure rustc and our codegen backend, but provide you |
| 18 | +//! with a toolchain agnostic version that you may use from stable rustc. And a |
| 19 | +//! `cargo gpu` cmdline utility to simplify shader building even more. |
| 20 | +//! |
| 21 | +//! ## Where the binaries are |
| 22 | +//! |
| 23 | +//! We store our prebuild `rustc_spirv_builder` binaries in the default cache |
| 24 | +//! directory of your OS: |
| 25 | +//! * Windows: `C:/users/<user>/AppData/Local/rust-gpu` |
| 26 | +//! * Mac: `~/Library/Caches/rust-gpu` |
| 27 | +//! * Linux: `~/.cache/rust-gpu` |
| 28 | +//! |
| 29 | +//! ## How we build the backend |
| 30 | +//! |
| 31 | +//! * retrieve the version of rust-gpu you want to use based on the version of the |
| 32 | +//! `spirv-std` dependency in your shader crate. |
| 33 | +//! * create a dummy project at `<cache_dir>/codegen/<version>/` that depends on |
| 34 | +//! `rustc_codegen_spirv` |
| 35 | +//! * use `cargo metadata` to `cargo update` the dummy project, which downloads the |
| 36 | +//! `rustc_codegen_spirv` crate into cargo's cache, and retrieve the path to the |
| 37 | +//! download location. |
| 38 | +//! * search for the required toolchain in `build.rs` of `rustc_codegen_spirv` |
| 39 | +//! * build it with the required toolchain version |
| 40 | +//! * copy out the binary and clean the target dir |
| 41 | +//! |
| 42 | +//! ## Building shader crates |
| 43 | +//! |
| 44 | +//! `cargo-gpu` takes a path to a shader crate to build, as well as a path to a directory |
| 45 | +//! to put the compiled `spv` source files. It also takes a path to an output manifest |
| 46 | +//! file where all shader entry points will be mapped to their `spv` source files. This |
| 47 | +//! manifest file can be used by build scripts (`build.rs` files) to generate linkage or |
| 48 | +//! conduct other post-processing, like converting the `spv` files into `wgsl` files, |
| 49 | +//! for example. |
| 50 | +
|
| 51 | +use anyhow::Context as _; |
| 52 | + |
| 53 | +use build::Build; |
| 54 | +use install::Install; |
| 55 | +use show::Show; |
| 56 | + |
| 57 | +pub mod args; |
| 58 | +pub mod build; |
| 59 | +pub mod config; |
| 60 | +pub mod install; |
| 61 | +pub mod install_toolchain; |
| 62 | +pub mod linkage; |
| 63 | +pub mod lockfile; |
| 64 | +pub mod metadata; |
| 65 | +pub mod show; |
| 66 | +pub mod spirv_source; |
| 67 | + |
| 68 | +/// Central function to write to the user. |
| 69 | +#[macro_export] |
| 70 | +macro_rules! user_output { |
| 71 | + ($($args: tt)*) => { |
| 72 | + #[allow( |
| 73 | + clippy::allow_attributes, |
| 74 | + clippy::useless_attribute, |
| 75 | + unused_imports, |
| 76 | + reason = "`std::io::Write` is only sometimes called??" |
| 77 | + )] |
| 78 | + use std::io::Write as _; |
| 79 | + |
| 80 | + #[expect( |
| 81 | + clippy::non_ascii_literal, |
| 82 | + reason = "CRAB GOOD. CRAB IMPORTANT." |
| 83 | + )] |
| 84 | + { |
| 85 | + print!("🦀 "); |
| 86 | + } |
| 87 | + print!($($args)*); |
| 88 | + std::io::stdout().flush().unwrap(); |
| 89 | + } |
| 90 | +} |
| 91 | + |
| 92 | +/// All of the available subcommands for `cargo gpu` |
| 93 | +#[derive(clap::Subcommand)] |
| 94 | +pub enum Command { |
| 95 | + /// Install rust-gpu compiler artifacts. |
| 96 | + Install(Box<Install>), |
| 97 | + |
| 98 | + /// Compile a shader crate to SPIR-V. |
| 99 | + Build(Box<Build>), |
| 100 | + |
| 101 | + /// Show some useful values. |
| 102 | + Show(Show), |
| 103 | + |
| 104 | + /// A hidden command that can be used to recursively print out all the subcommand help messages: |
| 105 | + /// `cargo gpu dump-usage` |
| 106 | + /// Useful for updating the README. |
| 107 | + #[clap(hide(true))] |
| 108 | + DumpUsage, |
| 109 | +} |
| 110 | + |
| 111 | +/// The main cli args of `cargo gpu` |
| 112 | +#[derive(clap::Parser)] |
| 113 | +#[clap(author, version, about, subcommand_required = true)] |
| 114 | +pub struct Cli { |
| 115 | + /// The command to run. |
| 116 | + #[clap(subcommand)] |
| 117 | + pub command: Command, |
| 118 | +} |
| 119 | + |
| 120 | +/// returns the path to the cache dir of cargo gpu |
| 121 | +pub fn cache_dir() -> anyhow::Result<std::path::PathBuf> { |
| 122 | + let dir = directories::BaseDirs::new() |
| 123 | + .with_context(|| "could not find the user home directory")? |
| 124 | + .cache_dir() |
| 125 | + .join("rust-gpu"); |
| 126 | + |
| 127 | + Ok(if cfg!(test) { |
| 128 | + let thread_id = std::thread::current().id(); |
| 129 | + let id = format!("{thread_id:?}").replace('(', "-").replace(')', ""); |
| 130 | + dir.join("tests").join(id) |
| 131 | + } else { |
| 132 | + dir |
| 133 | + }) |
| 134 | +} |
| 135 | + |
| 136 | +/// Location of the target spec metadata files |
| 137 | +pub fn target_spec_dir() -> anyhow::Result<std::path::PathBuf> { |
| 138 | + let dir = cache_dir()?.join("target-specs"); |
| 139 | + std::fs::create_dir_all(&dir)?; |
| 140 | + Ok(dir) |
| 141 | +} |
| 142 | + |
| 143 | +/// Returns a string suitable to use as a directory. |
| 144 | +/// |
| 145 | +/// Created from the spirv-builder source dep and the rustc channel. |
| 146 | +fn to_dirname(text: &str) -> String { |
| 147 | + text.replace( |
| 148 | + [std::path::MAIN_SEPARATOR, '\\', '/', '.', ':', '@', '='], |
| 149 | + "_", |
| 150 | + ) |
| 151 | + .split(['{', '}', ' ', '\n', '"', '\'']) |
| 152 | + .collect::<Vec<_>>() |
| 153 | + .concat() |
| 154 | +} |
| 155 | + |
| 156 | +#[cfg(test)] |
| 157 | +mod test { |
| 158 | + use crate::cache_dir; |
| 159 | + use std::io::Write as _; |
| 160 | + |
| 161 | + fn copy_dir_all( |
| 162 | + src: impl AsRef<std::path::Path>, |
| 163 | + dst: impl AsRef<std::path::Path>, |
| 164 | + ) -> anyhow::Result<()> { |
| 165 | + std::fs::create_dir_all(&dst)?; |
| 166 | + for maybe_entry in std::fs::read_dir(src)? { |
| 167 | + let entry = maybe_entry?; |
| 168 | + let ty = entry.file_type()?; |
| 169 | + if ty.is_dir() { |
| 170 | + copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?; |
| 171 | + } else { |
| 172 | + std::fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?; |
| 173 | + } |
| 174 | + } |
| 175 | + Ok(()) |
| 176 | + } |
| 177 | + |
| 178 | + pub fn shader_crate_template_path() -> std::path::PathBuf { |
| 179 | + let project_base = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); |
| 180 | + project_base.join("../shader-crate-template") |
| 181 | + } |
| 182 | + |
| 183 | + pub fn shader_crate_test_path() -> std::path::PathBuf { |
| 184 | + let shader_crate_path = crate::cache_dir().unwrap().join("shader_crate"); |
| 185 | + copy_dir_all(shader_crate_template_path(), shader_crate_path.clone()).unwrap(); |
| 186 | + shader_crate_path |
| 187 | + } |
| 188 | + |
| 189 | + pub fn overwrite_shader_cargo_toml(shader_crate_path: &std::path::Path) -> std::fs::File { |
| 190 | + let cargo_toml = shader_crate_path.join("Cargo.toml"); |
| 191 | + let mut file = std::fs::OpenOptions::new() |
| 192 | + .write(true) |
| 193 | + .truncate(true) |
| 194 | + .open(cargo_toml) |
| 195 | + .unwrap(); |
| 196 | + writeln!(file, "[package]").unwrap(); |
| 197 | + writeln!(file, "name = \"test\"").unwrap(); |
| 198 | + file |
| 199 | + } |
| 200 | + |
| 201 | + pub fn tests_teardown() { |
| 202 | + let cache_dir = cache_dir().unwrap(); |
| 203 | + if !cache_dir.exists() { |
| 204 | + return; |
| 205 | + } |
| 206 | + std::fs::remove_dir_all(cache_dir).unwrap(); |
| 207 | + } |
| 208 | +} |
0 commit comments