use std::sync::Mutex;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct AdapterInfo {
pub vendor: String,
pub architecture: String,
pub device: String,
pub description: String,
pub is_fallback: bool,
pub shader_f16: bool,
pub subgroups: bool,
pub max_buffer_size: u64,
pub max_storage_buffer_binding_size: u64,
pub max_compute_workgroup_size_x: u32,
pub max_compute_invocations_per_workgroup: u32,
}
static BOUND: Mutex<Option<AdapterInfo>> = Mutex::new(None);
pub fn bind_adapter(info: AdapterInfo) {
*BOUND.lock().unwrap_or_else(|e| e.into_inner()) = Some(info);
}
pub fn unbind_adapter() {
*BOUND.lock().unwrap_or_else(|e| e.into_inner()) = None;
}
pub fn bound_adapter() -> Option<AdapterInfo> {
BOUND.lock().unwrap_or_else(|e| e.into_inner()).clone()
}
pub fn is_available() -> bool {
BOUND
.lock()
.unwrap_or_else(|e| e.into_inner())
.as_ref()
.is_some_and(|a| !a.is_fallback)
}
pub fn enumerate() -> Vec<AdapterInfo> {
match bound_adapter() {
Some(a) if !a.is_fallback => vec![a],
_ => Vec::new(),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn with_clean_binding<T>(f: impl FnOnce() -> T) -> T {
static GUARD: Mutex<()> = Mutex::new(());
let _g = GUARD.lock().unwrap_or_else(|e| e.into_inner());
unbind_adapter();
let out = f();
unbind_adapter();
out
}
fn sample() -> AdapterInfo {
AdapterInfo {
vendor: "nvidia".into(),
architecture: "ampere".into(),
shader_f16: true,
subgroups: true,
max_buffer_size: 1 << 31,
..Default::default()
}
}
#[test]
fn unbound_is_unavailable_and_enumerates_empty() {
with_clean_binding(|| {
assert!(!is_available());
assert!(enumerate().is_empty());
});
}
#[test]
fn bound_adapter_round_trips() {
with_clean_binding(|| {
bind_adapter(sample());
assert!(is_available());
assert_eq!(enumerate(), vec![sample()]);
assert_eq!(bound_adapter(), Some(sample()));
});
}
#[test]
fn fallback_adapter_is_not_offered() {
with_clean_binding(|| {
bind_adapter(AdapterInfo {
is_fallback: true,
..sample()
});
assert!(!is_available(), "software fallback must not be offered");
assert!(enumerate().is_empty());
});
}
#[test]
fn unbind_clears() {
with_clean_binding(|| {
bind_adapter(sample());
unbind_adapter();
assert!(!is_available());
assert_eq!(bound_adapter(), None);
});
}
}