1use std::env;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum Platform {
16 LinuxNative,
18 WSL2,
20 MacOS,
22 Windows,
24 Web,
26 Unknown,
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
32pub enum RenderBackend {
33 Vulkan,
35 Metal,
37 DirectX12,
39 OpenGL,
41 WebGPU,
43 WebGL2,
45 Software,
47 Auto,
49}
50
51impl RenderBackend {
52 pub fn as_wgpu_env(&self) -> &'static str {
54 match self {
55 RenderBackend::Vulkan => "vulkan",
56 RenderBackend::Metal => "metal",
57 RenderBackend::DirectX12 => "dx12",
58 RenderBackend::OpenGL => "gl",
59 RenderBackend::WebGPU => "webgpu",
60 RenderBackend::WebGL2 => "webgl2",
61 RenderBackend::Software => "software",
62 RenderBackend::Auto => "auto",
63 }
64 }
65
66 pub fn name(&self) -> &'static str {
68 match self {
69 RenderBackend::Vulkan => "Vulkan",
70 RenderBackend::Metal => "Metal",
71 RenderBackend::DirectX12 => "Direct3D 12",
72 RenderBackend::OpenGL => "OpenGL",
73 RenderBackend::WebGPU => "WebGPU",
74 RenderBackend::WebGL2 => "WebGL2",
75 RenderBackend::Software => "Software",
76 RenderBackend::Auto => "Auto",
77 }
78 }
79}
80
81#[derive(Debug, Clone)]
83pub struct BackendConfig {
84 pub preferred: Option<RenderBackend>,
86 pub fallbacks: Vec<RenderBackend>,
88 pub debug_logging: bool,
90 pub force_headless: bool,
92}
93
94impl Default for BackendConfig {
95 fn default() -> Self {
96 Self::new()
97 }
98}
99
100impl BackendConfig {
101 pub fn new() -> Self {
103 let platform = detect_platform();
104
105 let (preferred, fallbacks) = match platform {
107 Platform::LinuxNative => (
108 Some(RenderBackend::Vulkan),
109 vec![RenderBackend::OpenGL, RenderBackend::WebGPU],
110 ),
111 Platform::WSL2 => (
112 Some(RenderBackend::WebGPU),
113 vec![RenderBackend::OpenGL, RenderBackend::Software],
114 ),
115 Platform::MacOS => (
116 Some(RenderBackend::Metal),
117 vec![RenderBackend::OpenGL, RenderBackend::WebGPU],
118 ),
119 Platform::Windows => (
120 Some(RenderBackend::DirectX12),
121 vec![
122 RenderBackend::Vulkan,
123 RenderBackend::OpenGL,
124 RenderBackend::WebGPU,
125 ],
126 ),
127 Platform::Web => (Some(RenderBackend::WebGPU), vec![RenderBackend::WebGL2]),
128 Platform::Unknown => (
129 Some(RenderBackend::Auto),
130 vec![RenderBackend::WebGPU, RenderBackend::OpenGL],
131 ),
132 };
133
134 Self {
135 preferred,
136 fallbacks,
137 debug_logging: env::var("WGPU_LOG").is_ok(),
138 force_headless: true,
139 }
140 }
141
142 pub fn headless() -> Self {
144 let mut config = Self::new();
145 config.force_headless = true;
146 config
147 }
148
149 pub fn wsl2() -> Self {
151 Self {
152 preferred: Some(RenderBackend::WebGPU),
153 fallbacks: vec![RenderBackend::OpenGL, RenderBackend::Software],
154 debug_logging: true,
155 force_headless: true,
156 }
157 }
158
159 pub fn selected_backend(&self) -> RenderBackend {
161 if let Ok(backend_str) = env::var("WGPU_BACKEND") {
163 if let Some(backend) = parse_backend(&backend_str) {
164 return backend;
165 }
166 }
167
168 self.preferred.unwrap_or(RenderBackend::Auto)
170 }
171
172 pub fn backends_to_try(&self) -> Vec<RenderBackend> {
174 let mut backends = vec![self.selected_backend()];
175 backends.extend(self.fallbacks.iter().copied());
176 backends
177 }
178
179 pub fn apply_env(&self) {
181 let backend = self.selected_backend();
182 env::set_var("WGPU_BACKEND", backend.as_wgpu_env());
183
184 if self.debug_logging {
185 env::set_var("WGPU_LOG", "warn");
186 }
187
188 if self.force_headless {
189 env::set_var("WGPU_FORCE_OFFSCREEN", "1");
190 }
191 }
192}
193
194pub fn detect_platform() -> Platform {
196 #[cfg(target_os = "linux")]
197 {
198 if is_wsl2() {
199 Platform::WSL2
200 } else {
201 Platform::LinuxNative
202 }
203 }
204
205 #[cfg(target_os = "macos")]
206 {
207 Platform::MacOS
208 }
209
210 #[cfg(target_os = "windows")]
211 {
212 Platform::Windows
213 }
214
215 #[cfg(target_arch = "wasm32")]
216 {
217 Platform::Web
218 }
219
220 #[cfg(not(any(
221 target_os = "linux",
222 target_os = "macos",
223 target_os = "windows",
224 target_arch = "wasm32"
225 )))]
226 {
227 Platform::Unknown
228 }
229}
230
231pub fn is_wsl2() -> bool {
233 if let Ok(version) = std::fs::read_to_string("/proc/version") {
234 let version_lower = version.to_lowercase();
235 return version_lower.contains("microsoft") || version_lower.contains("wsl");
236 }
237 false
238}
239
240pub fn has_display() -> bool {
242 env::var("DISPLAY").is_ok() || env::var("WAYLAND_DISPLAY").is_ok()
243}
244
245fn parse_backend(s: &str) -> Option<RenderBackend> {
247 match s.to_lowercase().as_str() {
248 "vulkan" => Some(RenderBackend::Vulkan),
249 "metal" => Some(RenderBackend::Metal),
250 "dx12" | "d3d12" | "directx12" => Some(RenderBackend::DirectX12),
251 "gl" | "opengl" => Some(RenderBackend::OpenGL),
252 "webgpu" | "web" => Some(RenderBackend::WebGPU),
253 "webgl2" | "webgl" => Some(RenderBackend::WebGL2),
254 "software" | "cpu" => Some(RenderBackend::Software),
255 "auto" => Some(RenderBackend::Auto),
256 _ => None,
257 }
258}
259
260#[cfg(test)]
261mod tests {
262 use super::*;
263
264 #[test]
265 fn test_backend_env_strings() {
266 assert_eq!(RenderBackend::Vulkan.as_wgpu_env(), "vulkan");
267 assert_eq!(RenderBackend::Metal.as_wgpu_env(), "metal");
268 assert_eq!(RenderBackend::WebGPU.as_wgpu_env(), "webgpu");
269 assert_eq!(RenderBackend::OpenGL.as_wgpu_env(), "gl");
270 }
271
272 #[test]
273 fn test_backend_names() {
274 assert_eq!(RenderBackend::Vulkan.name(), "Vulkan");
275 assert_eq!(RenderBackend::WebGPU.name(), "WebGPU");
276 assert_eq!(RenderBackend::DirectX12.name(), "Direct3D 12");
277 }
278
279 #[test]
280 fn test_parse_backend() {
281 assert_eq!(parse_backend("vulkan"), Some(RenderBackend::Vulkan));
282 assert_eq!(parse_backend("VULKAN"), Some(RenderBackend::Vulkan));
283 assert_eq!(parse_backend("webgpu"), Some(RenderBackend::WebGPU));
284 assert_eq!(parse_backend("invalid"), None);
285 }
286
287 #[test]
288 fn test_backend_config_default() {
289 let config = BackendConfig::default();
290 assert!(config.selected_backend() != RenderBackend::Auto || config.preferred.is_none());
291 assert!(!config.fallbacks.is_empty());
292 }
293
294 #[test]
295 fn test_backend_config_headless() {
296 let config = BackendConfig::headless();
297 assert!(config.force_headless);
298 }
299
300 #[test]
301 fn test_backends_to_try() {
302 let config = BackendConfig::wsl2();
303 let backends = config.backends_to_try();
304 assert!(!backends.is_empty());
305 assert_eq!(backends[0], RenderBackend::WebGPU);
306 }
307
308 #[test]
309 fn test_is_wsl2_detection() {
310 let is_wsl = is_wsl2();
312 let _ = is_wsl;
314 }
315}