bevy_sensor/
backend.rs

1//! WebGPU and cross-platform backend support for rendering.
2//!
3//! This module provides utilities for selecting and configuring rendering backends
4//! across different platforms. It handles:
5//!
6//! - Platform detection (Linux, macOS, Windows, WSL2)
7//! - Backend preference ordering (native > WebGPU > software)
8//! - Environment configuration (WGPU_BACKEND, etc.)
9//! - Fallback strategies for restricted environments
10
11use std::env;
12
13/// Detected platform and rendering environment
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum Platform {
16    /// Linux with native GPU support
17    LinuxNative,
18    /// WSL2 (Windows Subsystem for Linux 2)
19    WSL2,
20    /// macOS
21    MacOS,
22    /// Windows
23    Windows,
24    /// Web/WASM target
25    Web,
26    /// Unknown platform
27    Unknown,
28}
29
30/// Available rendering backends
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
32pub enum RenderBackend {
33    /// Vulkan (Linux, Windows, Android)
34    Vulkan,
35    /// Metal (macOS, iOS)
36    Metal,
37    /// Direct3D 12 (Windows)
38    DirectX12,
39    /// OpenGL (Linux, Windows, macOS)
40    OpenGL,
41    /// WebGPU (cross-platform, especially good for WSL2 and headless)
42    WebGPU,
43    /// WebGL2 (Web)
44    WebGL2,
45    /// Software/CPU rendering (fallback)
46    Software,
47    /// Automatic selection (wgpu default)
48    Auto,
49}
50
51impl RenderBackend {
52    /// Get the wgpu environment variable value for this backend
53    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    /// Get a human-readable name for this backend
67    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/// Configuration for backend selection
82#[derive(Debug, Clone)]
83pub struct BackendConfig {
84    /// Preferred backend (if available)
85    pub preferred: Option<RenderBackend>,
86    /// Fallback backends in order of preference
87    pub fallbacks: Vec<RenderBackend>,
88    /// Whether to enable debug logging
89    pub debug_logging: bool,
90    /// Force headless mode (don't create windows)
91    pub force_headless: bool,
92}
93
94impl Default for BackendConfig {
95    fn default() -> Self {
96        Self::new()
97    }
98}
99
100impl BackendConfig {
101    /// Create a new configuration with sensible defaults for the current platform
102    pub fn new() -> Self {
103        let platform = detect_platform();
104
105        // Select backends based on platform
106        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    /// Create a configuration optimized for headless rendering
143    pub fn headless() -> Self {
144        let mut config = Self::new();
145        config.force_headless = true;
146        config
147    }
148
149    /// Create a configuration optimized for WSL2
150    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    /// Get the selected backend, considering environment overrides
160    pub fn selected_backend(&self) -> RenderBackend {
161        // Check for environment override
162        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        // Use preferred backend
169        self.preferred.unwrap_or(RenderBackend::Auto)
170    }
171
172    /// Get a list of backends to try in order (preferred + fallbacks)
173    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    /// Apply this configuration to the environment
180    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
194/// Detect the current platform
195pub 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
231/// Check if running on WSL2 (Windows Subsystem for Linux 2)
232pub 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
240/// Check if a display is available for windowed rendering
241pub fn has_display() -> bool {
242    env::var("DISPLAY").is_ok() || env::var("WAYLAND_DISPLAY").is_ok()
243}
244
245/// Parse a backend string from environment variable or user input
246fn 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        // This test will only be meaningful on WSL2 or Linux
311        let is_wsl = is_wsl2();
312        // We just verify it doesn't panic
313        let _ = is_wsl;
314    }
315}