use std::path::PathBuf;
use std::sync::Mutex;
pub use wgpu::Backends;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BackendChoice {
Auto,
Off,
Use(Backends),
}
pub fn parse_backends(s: &str) -> Option<BackendChoice> {
let s = s.trim().to_ascii_lowercase();
match s.as_str() {
"" => None,
"auto" | "default" => Some(BackendChoice::Auto),
"off" | "none" | "cpu" | "0" | "false" | "no" => Some(BackendChoice::Off),
"glow" => Some(BackendChoice::Auto),
_ => {
let b = Backends::from_comma_list(&s);
(!b.is_empty()).then_some(BackendChoice::Use(b))
}
}
}
pub fn choose(app_var: &str, app: Option<&str>, wgpu: Option<&str>) -> BackendChoice {
for (name, v) in [(app_var, app), ("WGPU_BACKEND", wgpu)] {
let Some(v) = v else { continue };
match parse_backends(v) {
Some(BackendChoice::Off) if name == "WGPU_BACKEND" => {
log::warn!("gpu: WGPU_BACKEND={v} ignored (use {app_var}=off)")
}
Some(c) => return c,
None => log::warn!("gpu: {name}={v} names no known backend; ignored"),
}
}
BackendChoice::Auto
}
pub fn default_compute_backends(os: &str) -> Backends {
match os {
"windows" => Backends::DX12,
"macos" | "ios" => Backends::METAL,
_ => Backends::PRIMARY,
}
}
pub fn default_window_backends(os: &str) -> Backends {
match os {
"windows" => Backends::DX12,
"macos" | "ios" => Backends::METAL,
_ => Backends::PRIMARY | Backends::GL,
}
}
pub fn resolve(choice: BackendChoice, default: Backends, compiled: Backends) -> Option<Backends> {
match choice {
BackendChoice::Off => None,
BackendChoice::Auto => Some(default),
BackendChoice::Use(b) if b.intersects(compiled) => Some(b & compiled),
BackendChoice::Use(b) => {
log::warn!("gpu: backend {b:?} is not in this build ({compiled:?}); using {default:?}");
Some(default)
}
}
}
fn env_choice(app_var: &str) -> BackendChoice {
let app = std::env::var(app_var).ok();
let wgpu = std::env::var("WGPU_BACKEND").ok();
choose(app_var, app.as_deref(), wgpu.as_deref())
}
pub fn compute_backends(app_var: &str) -> Option<Backends> {
resolve(
env_choice(app_var),
default_compute_backends(std::env::consts::OS),
wgpu::Instance::enabled_backend_features(),
)
}
pub fn window_backends(app_var: &str) -> Backends {
let default = default_window_backends(std::env::consts::OS);
let choice = match env_choice(app_var) {
BackendChoice::Off => BackendChoice::Auto,
c => c,
};
resolve(choice, default, wgpu::Instance::enabled_backend_features()).unwrap_or(default)
}
pub fn dx12_compiler(env: Option<&str>) -> wgpu::Dx12Compiler {
let Some(v) = env else {
return wgpu::Dx12Compiler::Fxc;
};
v.parse().unwrap_or_else(|e| {
log::warn!("gpu: WGPU_DX12_COMPILER={v} ignored: {e}");
wgpu::Dx12Compiler::Fxc
})
}
pub fn backend_options() -> wgpu::BackendOptions {
let mut o = wgpu::BackendOptions::from_env_or_default();
o.dx12.shader_compiler = dx12_compiler(std::env::var("WGPU_DX12_COMPILER").ok().as_deref());
o
}
pub fn instance_flags(flags: wgpu::InstanceFlags, env: Option<&str>) -> wgpu::InstanceFlags {
if env.is_some_and(|v| v != "0") {
flags | wgpu::InstanceFlags::VALIDATION_INDIRECT_CALL
} else {
flags - wgpu::InstanceFlags::VALIDATION_INDIRECT_CALL
}
}
pub fn instance_flags_from_env(flags: wgpu::InstanceFlags) -> wgpu::InstanceFlags {
instance_flags(
flags,
std::env::var("WGPU_VALIDATION_INDIRECT_CALL")
.ok()
.as_deref(),
)
}
pub fn env_off(app_var: &str) -> bool {
env_choice(app_var) == BackendChoice::Off
}
#[derive(Default)]
struct MarkerState {
path: Option<PathBuf>,
startup_active: bool,
}
#[derive(Default)]
struct MarkerTracker {
state: Mutex<MarkerState>,
active: Mutex<u32>,
}
static MARKER: MarkerTracker = MarkerTracker {
state: Mutex::new(MarkerState {
path: None,
startup_active: false,
}),
active: Mutex::new(0),
};
impl MarkerTracker {
fn set_init_marker(&self, path: Option<PathBuf>) {
let mut state = self.state.lock().unwrap_or_else(|e| e.into_inner());
let old = state.path.clone();
let clear_old = path.is_none();
let same_path = old == path;
state.path = path;
if !same_path && state.startup_active {
if let Some(old) = old.as_deref() {
disarm(old, &self.active);
}
state.startup_active = false;
}
drop(state);
if clear_old && let Some(old) = old {
let _ = std::fs::remove_file(old);
}
}
fn begin_startup_marker(&self, backends: Backends) -> bool {
let mut state = self.state.lock().unwrap_or_else(|e| e.into_inner());
let Some(path) = state.path.as_deref() else {
return false;
};
if !state.startup_active {
arm(
path,
&self.active,
&format!("startup (backends {backends:?})"),
);
state.startup_active = true;
}
true
}
fn startup_succeeded(&self) {
let mut state = self.state.lock().unwrap_or_else(|e| e.into_inner());
if state.startup_active {
if let Some(path) = state.path.as_deref() {
disarm(path, &self.active);
}
state.startup_active = false;
}
}
fn with_marker<T>(&self, what: &str, f: impl FnOnce() -> T) -> T {
let marker = self
.state
.lock()
.unwrap_or_else(|e| e.into_inner())
.path
.clone();
if let Some(m) = &marker {
arm(m, &self.active, what);
}
let _armed = Armed {
marker,
active: &self.active,
};
f()
}
}
pub fn set_init_marker(path: Option<PathBuf>) {
MARKER.set_init_marker(path);
}
pub fn begin_startup_marker(backends: Backends) -> bool {
MARKER.begin_startup_marker(backends)
}
pub fn startup_succeeded() {
MARKER.startup_succeeded();
}
pub fn startup_failed() {
startup_succeeded();
}
fn marker_text(what: &str, secs: u64) -> String {
format!("GPU {what} started (unix time {secs})\n")
}
fn arm(marker: &std::path::Path, active: &Mutex<u32>, what: &str) {
let mut n = active.lock().unwrap_or_else(|e| e.into_inner());
if *n == 0 {
if let Some(d) = marker.parent() {
let _ = std::fs::create_dir_all(d);
}
let secs = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
let _ = rightkit_fs::atomic_write(marker, marker_text(what, secs).as_bytes());
}
*n = n.saturating_add(1);
}
fn disarm(marker: &std::path::Path, active: &Mutex<u32>) {
let mut n = active.lock().unwrap_or_else(|e| e.into_inner());
*n = n.saturating_sub(1);
if *n == 0 {
let _ = std::fs::remove_file(marker);
}
}
struct Armed<'a> {
marker: Option<PathBuf>,
active: &'a Mutex<u32>,
}
impl Drop for Armed<'_> {
fn drop(&mut self) {
if let Some(m) = &self.marker {
disarm(m, self.active);
}
}
}
pub fn with_marker<T>(what: &str, f: impl FnOnce() -> T) -> T {
MARKER.with_marker(what, f)
}
pub fn with_init_marker<T>(backends: Backends, f: impl FnOnce() -> T) -> T {
with_marker(
&format!("device and kernel creation (backends {backends:?})"),
f,
)
}
pub fn take_init_marker(path: &std::path::Path) -> Option<String> {
let text = read_init_marker(path)?;
let _ = std::fs::remove_file(path);
Some(text)
}
pub fn read_init_marker(path: &std::path::Path) -> Option<String> {
let text = std::fs::read_to_string(path).ok()?;
Some(text.trim().to_string())
}