use std::env;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Platform {
LinuxNative,
WSL2,
MacOS,
Windows,
Web,
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RenderBackend {
Vulkan,
Metal,
DirectX12,
OpenGL,
WebGPU,
WebGL2,
Software,
Auto,
}
impl RenderBackend {
pub fn as_wgpu_env(&self) -> &'static str {
match self {
RenderBackend::Vulkan => "vulkan",
RenderBackend::Metal => "metal",
RenderBackend::DirectX12 => "dx12",
RenderBackend::OpenGL => "gl",
RenderBackend::WebGPU => "webgpu",
RenderBackend::WebGL2 => "webgl2",
RenderBackend::Software => "software",
RenderBackend::Auto => "auto",
}
}
pub fn name(&self) -> &'static str {
match self {
RenderBackend::Vulkan => "Vulkan",
RenderBackend::Metal => "Metal",
RenderBackend::DirectX12 => "Direct3D 12",
RenderBackend::OpenGL => "OpenGL",
RenderBackend::WebGPU => "WebGPU",
RenderBackend::WebGL2 => "WebGL2",
RenderBackend::Software => "Software",
RenderBackend::Auto => "Auto",
}
}
}
#[derive(Debug, Clone)]
pub struct BackendConfig {
pub preferred: Option<RenderBackend>,
pub fallbacks: Vec<RenderBackend>,
pub debug_logging: bool,
pub force_headless: bool,
}
impl Default for BackendConfig {
fn default() -> Self {
Self::new()
}
}
impl BackendConfig {
pub fn new() -> Self {
let platform = detect_platform();
let (preferred, fallbacks) = match platform {
Platform::LinuxNative => (
Some(RenderBackend::Vulkan),
vec![RenderBackend::OpenGL, RenderBackend::WebGPU],
),
Platform::WSL2 => (
Some(RenderBackend::WebGPU),
vec![RenderBackend::OpenGL, RenderBackend::Software],
),
Platform::MacOS => (
Some(RenderBackend::Metal),
vec![RenderBackend::OpenGL, RenderBackend::WebGPU],
),
Platform::Windows => (
Some(RenderBackend::DirectX12),
vec![
RenderBackend::Vulkan,
RenderBackend::OpenGL,
RenderBackend::WebGPU,
],
),
Platform::Web => (Some(RenderBackend::WebGPU), vec![RenderBackend::WebGL2]),
Platform::Unknown => (
Some(RenderBackend::Auto),
vec![RenderBackend::WebGPU, RenderBackend::OpenGL],
),
};
Self {
preferred,
fallbacks,
debug_logging: env::var("WGPU_LOG").is_ok(),
force_headless: true,
}
}
pub fn headless() -> Self {
let mut config = Self::new();
config.force_headless = true;
config
}
pub fn wsl2() -> Self {
Self {
preferred: Some(RenderBackend::WebGPU),
fallbacks: vec![RenderBackend::OpenGL, RenderBackend::Software],
debug_logging: true,
force_headless: true,
}
}
pub fn selected_backend(&self) -> RenderBackend {
if let Ok(backend_str) = env::var("WGPU_BACKEND") {
if let Some(backend) = parse_backend(&backend_str) {
return backend;
}
}
self.preferred.unwrap_or(RenderBackend::Auto)
}
pub fn backends_to_try(&self) -> Vec<RenderBackend> {
let mut backends = vec![self.selected_backend()];
backends.extend(self.fallbacks.iter().copied());
backends
}
pub fn apply_env(&self) {
let backend = self.selected_backend();
env::set_var("WGPU_BACKEND", backend.as_wgpu_env());
if self.debug_logging {
env::set_var("WGPU_LOG", "warn");
}
if self.force_headless {
env::set_var("WGPU_FORCE_OFFSCREEN", "1");
}
}
}
pub fn detect_platform() -> Platform {
#[cfg(target_os = "linux")]
{
if is_wsl2() {
Platform::WSL2
} else {
Platform::LinuxNative
}
}
#[cfg(target_os = "macos")]
{
Platform::MacOS
}
#[cfg(target_os = "windows")]
{
Platform::Windows
}
#[cfg(target_arch = "wasm32")]
{
Platform::Web
}
#[cfg(not(any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_arch = "wasm32"
)))]
{
Platform::Unknown
}
}
pub fn is_wsl2() -> bool {
if let Ok(version) = std::fs::read_to_string("/proc/version") {
let version_lower = version.to_lowercase();
return version_lower.contains("microsoft") || version_lower.contains("wsl");
}
false
}
pub fn has_display() -> bool {
env::var("DISPLAY").is_ok() || env::var("WAYLAND_DISPLAY").is_ok()
}
fn parse_backend(s: &str) -> Option<RenderBackend> {
match s.to_lowercase().as_str() {
"vulkan" => Some(RenderBackend::Vulkan),
"metal" => Some(RenderBackend::Metal),
"dx12" | "d3d12" | "directx12" => Some(RenderBackend::DirectX12),
"gl" | "opengl" => Some(RenderBackend::OpenGL),
"webgpu" | "web" => Some(RenderBackend::WebGPU),
"webgl2" | "webgl" => Some(RenderBackend::WebGL2),
"software" | "cpu" => Some(RenderBackend::Software),
"auto" => Some(RenderBackend::Auto),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_backend_env_strings() {
assert_eq!(RenderBackend::Vulkan.as_wgpu_env(), "vulkan");
assert_eq!(RenderBackend::Metal.as_wgpu_env(), "metal");
assert_eq!(RenderBackend::WebGPU.as_wgpu_env(), "webgpu");
assert_eq!(RenderBackend::OpenGL.as_wgpu_env(), "gl");
}
#[test]
fn test_backend_names() {
assert_eq!(RenderBackend::Vulkan.name(), "Vulkan");
assert_eq!(RenderBackend::WebGPU.name(), "WebGPU");
assert_eq!(RenderBackend::DirectX12.name(), "Direct3D 12");
}
#[test]
fn test_parse_backend() {
assert_eq!(parse_backend("vulkan"), Some(RenderBackend::Vulkan));
assert_eq!(parse_backend("VULKAN"), Some(RenderBackend::Vulkan));
assert_eq!(parse_backend("webgpu"), Some(RenderBackend::WebGPU));
assert_eq!(parse_backend("invalid"), None);
}
#[test]
fn test_backend_config_default() {
let config = BackendConfig::default();
assert!(config.selected_backend() != RenderBackend::Auto || config.preferred.is_none());
assert!(!config.fallbacks.is_empty());
}
#[test]
fn test_backend_config_headless() {
let config = BackendConfig::headless();
assert!(config.force_headless);
}
#[test]
fn test_backends_to_try() {
let config = BackendConfig::wsl2();
let backends = config.backends_to_try();
assert!(!backends.is_empty());
assert_eq!(backends[0], RenderBackend::WebGPU);
}
#[test]
fn test_is_wsl2_detection() {
let is_wsl = is_wsl2();
let _ = is_wsl;
}
}