1use std::path::PathBuf;
20use std::sync::Mutex;
21
22pub use wgpu::Backends;
23
24#[derive(Clone, Copy, Debug, PartialEq, Eq)]
26pub enum BackendChoice {
27 Auto,
29 Off,
31 Use(Backends),
33}
34
35pub fn parse_backends(s: &str) -> Option<BackendChoice> {
38 let s = s.trim().to_ascii_lowercase();
39 match s.as_str() {
40 "" => None,
41 "auto" | "default" => Some(BackendChoice::Auto),
42 "off" | "none" | "cpu" | "0" | "false" | "no" => Some(BackendChoice::Off),
43 "glow" => Some(BackendChoice::Auto),
45 _ => {
46 let b = Backends::from_comma_list(&s);
47 (!b.is_empty()).then_some(BackendChoice::Use(b))
48 }
49 }
50}
51
52pub fn choose(app_var: &str, app: Option<&str>, wgpu: Option<&str>) -> BackendChoice {
55 for (name, v) in [(app_var, app), ("WGPU_BACKEND", wgpu)] {
56 let Some(v) = v else { continue };
57 match parse_backends(v) {
58 Some(BackendChoice::Off) if name == "WGPU_BACKEND" => {
60 log::warn!("gpu: WGPU_BACKEND={v} ignored (use {app_var}=off)")
61 }
62 Some(c) => return c,
63 None => log::warn!("gpu: {name}={v} names no known backend; ignored"),
64 }
65 }
66 BackendChoice::Auto
67}
68
69pub fn default_compute_backends(os: &str) -> Backends {
71 match os {
72 "windows" => Backends::DX12,
74 "macos" | "ios" => Backends::METAL,
75 _ => Backends::PRIMARY,
76 }
77}
78
79pub fn default_window_backends(os: &str) -> Backends {
81 match os {
82 "windows" => Backends::DX12,
83 "macos" | "ios" => Backends::METAL,
84 _ => Backends::PRIMARY | Backends::GL,
86 }
87}
88
89pub fn resolve(choice: BackendChoice, default: Backends, compiled: Backends) -> Option<Backends> {
92 match choice {
93 BackendChoice::Off => None,
94 BackendChoice::Auto => Some(default),
95 BackendChoice::Use(b) if b.intersects(compiled) => Some(b & compiled),
96 BackendChoice::Use(b) => {
97 log::warn!("gpu: backend {b:?} is not in this build ({compiled:?}); using {default:?}");
98 Some(default)
99 }
100 }
101}
102
103fn env_choice(app_var: &str) -> BackendChoice {
104 let app = std::env::var(app_var).ok();
105 let wgpu = std::env::var("WGPU_BACKEND").ok();
106 choose(app_var, app.as_deref(), wgpu.as_deref())
107}
108
109pub fn compute_backends(app_var: &str) -> Option<Backends> {
111 resolve(
112 env_choice(app_var),
113 default_compute_backends(std::env::consts::OS),
114 wgpu::Instance::enabled_backend_features(),
115 )
116}
117
118pub fn window_backends(app_var: &str) -> Backends {
121 let default = default_window_backends(std::env::consts::OS);
122 let choice = match env_choice(app_var) {
123 BackendChoice::Off => BackendChoice::Auto,
124 c => c,
125 };
126 resolve(choice, default, wgpu::Instance::enabled_backend_features()).unwrap_or(default)
127}
128
129pub fn dx12_compiler(env: Option<&str>) -> wgpu::Dx12Compiler {
138 let Some(v) = env else {
139 return wgpu::Dx12Compiler::Fxc;
140 };
141 v.parse().unwrap_or_else(|e| {
142 log::warn!("gpu: WGPU_DX12_COMPILER={v} ignored: {e}");
143 wgpu::Dx12Compiler::Fxc
144 })
145}
146
147pub fn backend_options() -> wgpu::BackendOptions {
150 let mut o = wgpu::BackendOptions::from_env_or_default();
151 o.dx12.shader_compiler = dx12_compiler(std::env::var("WGPU_DX12_COMPILER").ok().as_deref());
152 o
153}
154
155pub fn instance_flags(flags: wgpu::InstanceFlags, env: Option<&str>) -> wgpu::InstanceFlags {
162 if env.is_some_and(|v| v != "0") {
163 flags | wgpu::InstanceFlags::VALIDATION_INDIRECT_CALL
164 } else {
165 flags - wgpu::InstanceFlags::VALIDATION_INDIRECT_CALL
166 }
167}
168
169pub fn instance_flags_from_env(flags: wgpu::InstanceFlags) -> wgpu::InstanceFlags {
171 instance_flags(
172 flags,
173 std::env::var("WGPU_VALIDATION_INDIRECT_CALL")
174 .ok()
175 .as_deref(),
176 )
177}
178
179pub fn env_off(app_var: &str) -> bool {
181 env_choice(app_var) == BackendChoice::Off
182}
183
184#[derive(Default)]
185struct MarkerState {
186 path: Option<PathBuf>,
187 startup_active: bool,
188}
189
190#[derive(Default)]
192struct MarkerTracker {
193 state: Mutex<MarkerState>,
194 active: Mutex<u32>,
195}
196
197static MARKER: MarkerTracker = MarkerTracker {
198 state: Mutex::new(MarkerState {
199 path: None,
200 startup_active: false,
201 }),
202 active: Mutex::new(0),
203};
204
205impl MarkerTracker {
206 fn set_init_marker(&self, path: Option<PathBuf>) {
207 let mut state = self.state.lock().unwrap_or_else(|e| e.into_inner());
208 let old = state.path.clone();
209 let clear_old = path.is_none();
210 let same_path = old == path;
211 state.path = path;
212 if !same_path && state.startup_active {
213 if let Some(old) = old.as_deref() {
214 disarm(old, &self.active);
215 }
216 state.startup_active = false;
217 }
218 drop(state);
219 if clear_old && let Some(old) = old {
220 let _ = std::fs::remove_file(old);
221 }
222 }
223
224 fn begin_startup_marker(&self, backends: Backends) -> bool {
225 let mut state = self.state.lock().unwrap_or_else(|e| e.into_inner());
226 let Some(path) = state.path.as_deref() else {
227 return false;
228 };
229 if !state.startup_active {
230 arm(
231 path,
232 &self.active,
233 &format!("startup (backends {backends:?})"),
234 );
235 state.startup_active = true;
236 }
237 true
238 }
239
240 fn startup_succeeded(&self) {
241 let mut state = self.state.lock().unwrap_or_else(|e| e.into_inner());
242 if state.startup_active {
243 if let Some(path) = state.path.as_deref() {
244 disarm(path, &self.active);
245 }
246 state.startup_active = false;
247 }
248 }
249
250 fn with_marker<T>(&self, what: &str, f: impl FnOnce() -> T) -> T {
251 let marker = self
252 .state
253 .lock()
254 .unwrap_or_else(|e| e.into_inner())
255 .path
256 .clone();
257 if let Some(m) = &marker {
258 arm(m, &self.active, what);
259 }
260 let _armed = Armed {
261 marker,
262 active: &self.active,
263 };
264 f()
265 }
266}
267
268pub fn set_init_marker(path: Option<PathBuf>) {
271 MARKER.set_init_marker(path);
272}
273
274pub fn begin_startup_marker(backends: Backends) -> bool {
278 MARKER.begin_startup_marker(backends)
279}
280
281pub fn startup_succeeded() {
283 MARKER.startup_succeeded();
284}
285
286pub fn startup_failed() {
288 startup_succeeded();
289}
290
291fn marker_text(what: &str, secs: u64) -> String {
293 format!("GPU {what} started (unix time {secs})\n")
294}
295
296fn arm(marker: &std::path::Path, active: &Mutex<u32>, what: &str) {
298 let mut n = active.lock().unwrap_or_else(|e| e.into_inner());
299 if *n == 0 {
300 if let Some(d) = marker.parent() {
301 let _ = std::fs::create_dir_all(d);
302 }
303 let secs = std::time::SystemTime::now()
304 .duration_since(std::time::UNIX_EPOCH)
305 .map(|d| d.as_secs())
306 .unwrap_or(0);
307 let _ = rightkit_fs::atomic_write(marker, marker_text(what, secs).as_bytes());
308 }
309 *n = n.saturating_add(1);
310}
311
312fn disarm(marker: &std::path::Path, active: &Mutex<u32>) {
314 let mut n = active.lock().unwrap_or_else(|e| e.into_inner());
315 *n = n.saturating_sub(1);
316 if *n == 0 {
317 let _ = std::fs::remove_file(marker);
318 }
319}
320
321struct Armed<'a> {
323 marker: Option<PathBuf>,
324 active: &'a Mutex<u32>,
325}
326
327impl Drop for Armed<'_> {
328 fn drop(&mut self) {
329 if let Some(m) = &self.marker {
330 disarm(m, self.active);
331 }
332 }
333}
334
335pub fn with_marker<T>(what: &str, f: impl FnOnce() -> T) -> T {
338 MARKER.with_marker(what, f)
339}
340
341pub fn with_init_marker<T>(backends: Backends, f: impl FnOnce() -> T) -> T {
343 with_marker(
344 &format!("device and kernel creation (backends {backends:?})"),
345 f,
346 )
347}
348
349pub fn take_init_marker(path: &std::path::Path) -> Option<String> {
352 let text = read_init_marker(path)?;
353 let _ = std::fs::remove_file(path);
354 Some(text)
355}
356
357pub fn read_init_marker(path: &std::path::Path) -> Option<String> {
361 let text = std::fs::read_to_string(path).ok()?;
362 Some(text.trim().to_string())
363}