1use serde::{Deserialize, Serialize};
10use thiserror::Error;
11
12pub const RUNTIME_ENV: &str = "DSPK_RUNTIME";
15
16pub const TEST_RUNTIMES_ENV: &str = "DSPK_TEST_RUNTIMES";
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
22#[serde(rename_all = "lowercase")]
23pub enum ComputeTarget {
24 Wgpu,
26 Cpu,
28 Cuda,
30 Hip,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq, Error)]
35pub enum ComputeError {
36 #[error("unknown compute runtime '{0}' (expected wgpu, cpu, cuda or hip)")]
37 Unknown(String),
38 #[error("compute runtime {0:?} is not compiled in (enable the `{1}` feature)")]
39 Unavailable(ComputeTarget, &'static str),
40 #[error("no compute runtime is compiled in (enable one of the wgpu, cpu, cuda, hip features)")]
41 NoneAvailable,
42}
43
44impl ComputeTarget {
45 pub const ALL: [ComputeTarget; 4] = [Self::Wgpu, Self::Cuda, Self::Hip, Self::Cpu];
46
47 pub fn name(self) -> &'static str {
48 match self {
49 Self::Wgpu => "wgpu",
50 Self::Cpu => "cpu",
51 Self::Cuda => "cuda",
52 Self::Hip => "hip",
53 }
54 }
55
56 pub fn parse(name: &str) -> Result<Self, ComputeError> {
57 match name.trim().to_ascii_lowercase().as_str() {
58 "wgpu" | "webgpu" | "vulkan" | "metal" | "gpu" => Ok(Self::Wgpu),
59 "cpu" => Ok(Self::Cpu),
60 "cuda" => Ok(Self::Cuda),
61 "hip" | "rocm" => Ok(Self::Hip),
62 other => Err(ComputeError::Unknown(other.to_string())),
63 }
64 }
65
66 pub fn is_available(self) -> bool {
68 match self {
69 Self::Wgpu => cfg!(feature = "wgpu"),
70 Self::Cpu => cfg!(feature = "cpu"),
71 Self::Cuda => cfg!(feature = "cuda"),
72 Self::Hip => cfg!(feature = "hip"),
73 }
74 }
75
76 pub fn available() -> Vec<Self> {
78 Self::ALL.into_iter().filter(|t| t.is_available()).collect()
79 }
80
81 pub fn for_tests() -> Vec<Self> {
88 match std::env::var(TEST_RUNTIMES_ENV) {
89 Ok(list) if !list.trim().is_empty() => {
90 let wanted: Vec<Self> = list
91 .split(',')
92 .map(|name| Self::parse(name).unwrap_or_else(|e| panic!("{TEST_RUNTIMES_ENV}: {e}")))
93 .collect();
94 Self::available().into_iter().filter(|t| wanted.contains(t)).collect()
95 }
96 _ => Self::available(),
97 }
98 }
99
100 pub fn from_env() -> Result<Self, ComputeError> {
102 match std::env::var(RUNTIME_ENV) {
103 Ok(name) if !name.trim().is_empty() => Self::parse(&name)?.checked(),
104 _ => Self::available().first().copied().ok_or(ComputeError::NoneAvailable),
105 }
106 }
107
108 pub fn checked(self) -> Result<Self, ComputeError> {
110 if self.is_available() { Ok(self) } else { Err(ComputeError::Unavailable(self, self.name())) }
111 }
112}
113
114impl std::fmt::Display for ComputeTarget {
115 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116 f.write_str(self.name())
117 }
118}
119
120impl std::str::FromStr for ComputeTarget {
121 type Err = ComputeError;
122 fn from_str(s: &str) -> Result<Self, Self::Err> {
123 Self::parse(s)
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[test]
132 fn parses_names_and_reports_missing_runtimes() {
133 assert_eq!(ComputeTarget::parse("ROCm").unwrap(), ComputeTarget::Hip);
134 assert!(ComputeTarget::parse("tpu").is_err());
135 for target in ComputeTarget::ALL.into_iter().filter(|t| !t.is_available()) {
136 assert!(matches!(target.checked(), Err(ComputeError::Unavailable(..))));
137 }
138 }
139}