use core::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GpuType {
Discrete,
Integrated,
Cpu,
}
impl GpuType {
#[cfg(feature = "gpu-wgpu")]
pub fn detect_primary() -> Option<Self> {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(),
flags: wgpu::InstanceFlags::default(),
memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
backend_options: wgpu::BackendOptions::default(),
display: None,
});
let adapter =
match pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: None,
force_fallback_adapter: false,
})) {
Ok(a) => a,
Err(_) => return None,
};
let info = adapter.get_info();
Some(GpuType::from(GpuDeviceType::from(info.device_type)))
}
#[cfg(not(feature = "gpu-wgpu"))]
pub fn detect_primary() -> Option<Self> {
None
}
pub fn description(&self) -> &'static str {
match self {
GpuType::Discrete => "Discrete GPU",
GpuType::Integrated => "Integrated GPU",
GpuType::Cpu => "CPU Software Rendering",
}
}
}
impl From<GpuDeviceType> for GpuType {
fn from(device_type: GpuDeviceType) -> Self {
match device_type {
GpuDeviceType::DiscreteGpu => GpuType::Discrete,
GpuDeviceType::IntegratedGpu => GpuType::Integrated,
GpuDeviceType::VirtualGpu => GpuType::Integrated,
GpuDeviceType::Other => GpuType::Integrated,
GpuDeviceType::Cpu => GpuType::Cpu,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GpuDeviceType {
DiscreteGpu,
IntegratedGpu,
VirtualGpu,
Other,
Cpu,
}
impl GpuDeviceType {
pub fn priority(&self) -> u8 {
match self {
Self::DiscreteGpu => 5,
Self::IntegratedGpu => 3,
Self::VirtualGpu => 2,
Self::Other => 2,
Self::Cpu => 1,
}
}
}
impl PartialOrd for GpuDeviceType {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for GpuDeviceType {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.priority().cmp(&other.priority())
}
}
impl GpuDeviceType {
pub fn is_hardware_gpu(&self) -> bool {
!matches!(self, GpuDeviceType::Cpu)
}
pub fn is_discrete(&self) -> bool {
matches!(self, GpuDeviceType::DiscreteGpu)
}
pub fn is_integrated(&self) -> bool {
matches!(self, GpuDeviceType::IntegratedGpu)
}
pub fn is_cpu(&self) -> bool {
matches!(self, GpuDeviceType::Cpu)
}
pub fn description(&self) -> &'static str {
match self {
GpuDeviceType::DiscreteGpu => "Discrete GPU",
GpuDeviceType::IntegratedGpu => "Integrated GPU",
GpuDeviceType::VirtualGpu => "Virtual GPU",
GpuDeviceType::Other => "Other GPU",
GpuDeviceType::Cpu => "CPU Software Rendering",
}
}
pub fn performance_tier(&self) -> u8 {
match self {
GpuDeviceType::DiscreteGpu => 5,
GpuDeviceType::IntegratedGpu => 3,
GpuDeviceType::VirtualGpu => 2,
GpuDeviceType::Other => 2,
GpuDeviceType::Cpu => 1,
}
}
}
impl fmt::Display for GpuDeviceType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.description())
}
}
#[cfg(feature = "gpu-wgpu")]
impl From<wgpu::DeviceType> for GpuDeviceType {
fn from(device_type: wgpu::DeviceType) -> Self {
match device_type {
wgpu::DeviceType::DiscreteGpu => GpuDeviceType::DiscreteGpu,
wgpu::DeviceType::IntegratedGpu => GpuDeviceType::IntegratedGpu,
wgpu::DeviceType::VirtualGpu => GpuDeviceType::VirtualGpu,
wgpu::DeviceType::Other => GpuDeviceType::Other,
wgpu::DeviceType::Cpu => GpuDeviceType::Cpu,
}
}
}
#[derive(Debug, Clone)]
pub struct AdapterInfo {
pub device_type: GpuDeviceType,
pub vendor: String,
pub name: String,
pub backend: String,
pub driver: String,
pub driver_version: u64,
pub is_selected: bool,
}
impl AdapterInfo {
#[cfg(feature = "gpu-wgpu")]
pub fn from_wgpu(info: &wgpu::AdapterInfo) -> Self {
Self {
device_type: info.device_type.into(),
vendor: format!("{:04x}", info.vendor),
name: info.name.clone(),
backend: format!("{:?}", info.backend),
driver: info.driver.clone(),
driver_version: 0, is_selected: false,
}
}
pub fn cpu_fallback() -> Self {
Self {
device_type: GpuDeviceType::Cpu,
vendor: "CPU".to_string(),
name: "Software Renderer".to_string(),
backend: "CPU".to_string(),
driver: "Software".to_string(),
driver_version: 0,
is_selected: true,
}
}
pub fn supports_high_quality(&self) -> bool {
self.device_type.performance_tier() >= 3
}
pub fn is_suitable_for_quality(&self, quality: crate::quality::QualityLevel) -> bool {
match quality {
crate::quality::QualityLevel::High => self.device_type.performance_tier() >= 4,
crate::quality::QualityLevel::Medium => self.device_type.performance_tier() >= 2,
crate::quality::QualityLevel::Low => true,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum AdapterSelectionStrategy {
PreferPerformance,
PreferPowerEfficiency,
ForceDiscrete,
ForceIntegrated,
ForceCpu,
#[default]
Auto,
}
pub struct AdapterSelector {
strategy: AdapterSelectionStrategy,
allow_fallback: bool,
}
impl AdapterSelector {
pub fn new() -> Self {
Self { strategy: AdapterSelectionStrategy::Auto, allow_fallback: true }
}
pub fn with_strategy(strategy: AdapterSelectionStrategy) -> Self {
Self { strategy, allow_fallback: true }
}
pub fn allow_fallback(mut self, allow: bool) -> Self {
self.allow_fallback = allow;
self
}
#[cfg(feature = "gpu-wgpu")]
pub async fn enumerate_adapters(&self) -> Vec<AdapterInfo> {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(),
flags: wgpu::InstanceFlags::default(),
memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
backend_options: wgpu::BackendOptions::default(),
display: None,
});
let adapters = instance.enumerate_adapters(wgpu::Backends::all()).await;
adapters
.into_iter()
.map(|adapter| {
let info = adapter.get_info();
AdapterInfo::from_wgpu(&info)
})
.collect()
}
#[cfg(feature = "gpu-wgpu")]
pub async fn select_adapter_with_fallback(
&self,
compatible_surface: Option<&wgpu::Surface<'static>>,
) -> Result<AdapterInfo, AdapterSelectionError> {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(),
flags: wgpu::InstanceFlags::default(),
memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
backend_options: wgpu::BackendOptions::default(),
display: None,
});
let adapter = match self.strategy {
AdapterSelectionStrategy::PreferPerformance | AdapterSelectionStrategy::Auto => {
if let Ok(adapter) = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface,
force_fallback_adapter: false,
})
.await
{
let info = adapter.get_info();
if info.device_type == wgpu::DeviceType::DiscreteGpu {
return Ok(AdapterInfo::from_wgpu(&info));
}
if self.allow_fallback {
return Ok(AdapterInfo::from_wgpu(&info));
}
}
if self.allow_fallback {
Some(
match instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::LowPower,
compatible_surface,
force_fallback_adapter: false,
})
.await
{
Ok(adapter) => adapter,
Err(e) => {
return Err(AdapterSelectionError::RequestFailed(e.to_string()))
}
},
)
} else {
None
}
}
AdapterSelectionStrategy::PreferPowerEfficiency => Some(
instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::LowPower,
compatible_surface,
force_fallback_adapter: false,
})
.await
.map_err(|e| AdapterSelectionError::RequestFailed(e.to_string()))?,
),
AdapterSelectionStrategy::ForceDiscrete => {
let a = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface,
force_fallback_adapter: false,
})
.await
.map_err(|e| AdapterSelectionError::RequestFailed(e.to_string()))?;
if a.get_info().device_type != wgpu::DeviceType::DiscreteGpu {
return Err(AdapterSelectionError::DiscreteGpuNotFound);
}
Some(a)
}
AdapterSelectionStrategy::ForceIntegrated => {
let a = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::LowPower,
compatible_surface,
force_fallback_adapter: false,
})
.await
.map_err(|e| AdapterSelectionError::RequestFailed(e.to_string()))?;
if a.get_info().device_type != wgpu::DeviceType::IntegratedGpu {
return Err(AdapterSelectionError::IntegratedGpuNotFound);
}
Some(a)
}
AdapterSelectionStrategy::ForceCpu => Some(
instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::LowPower,
compatible_surface,
force_fallback_adapter: true,
})
.await
.map_err(|e| AdapterSelectionError::RequestFailed(e.to_string()))?,
),
};
match adapter {
Some(adapter) => {
let info = adapter.get_info();
Ok(AdapterInfo::from_wgpu(&info))
}
None => {
if self.allow_fallback {
Ok(AdapterInfo::cpu_fallback())
} else {
Err(AdapterSelectionError::NoAdapterFound)
}
}
}
}
pub fn strategy(&self) -> AdapterSelectionStrategy {
self.strategy
}
pub fn set_strategy(&mut self, strategy: AdapterSelectionStrategy) {
self.strategy = strategy;
}
}
impl Default for AdapterSelector {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AdapterSelectionError {
NoAdapterFound,
DiscreteGpuNotFound,
IntegratedGpuNotFound,
RequestFailed(String),
}
impl fmt::Display for AdapterSelectionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NoAdapterFound => write!(f, "No GPU adapter found"),
Self::DiscreteGpuNotFound => write!(f, "Discrete GPU not found"),
Self::IntegratedGpuNotFound => write!(f, "Integrated GPU not found"),
Self::RequestFailed(msg) => write!(f, "Adapter request failed: {}", msg),
}
}
}
#[cfg(not(feature = "mini"))]
impl std::error::Error for AdapterSelectionError {}
#[cfg(target_arch = "wasm32")]
pub fn detect_browser_forced_integrated_gpu() -> bool {
true }
#[cfg(not(target_arch = "wasm32"))]
pub fn detect_browser_forced_integrated_gpu() -> bool {
false }
#[cfg(target_os = "windows")]
pub fn detect_windows_browser_forced_igpu() -> Option<String> {
#[cfg(not(feature = "mini"))]
use std::env;
let browser_processes = ["chrome.exe", "firefox.exe", "msedge.exe", "opera.exe"];
if let Ok(parent) = env::var("RW_PARENT_PROCESS") {
for browser in &browser_processes {
if parent.to_lowercase().contains(browser) {
return Some(format!("Detected browser: {}", browser));
}
}
}
if let Ok(electron) = env::var("RW_ELECTRON_APP") {
if !electron.is_empty() {
return Some(format!("Detected Electron app: {}", electron));
}
}
None
}
#[cfg(not(target_os = "windows"))]
pub fn detect_windows_browser_forced_igpu() -> Option<String> {
None
}
#[cfg(test)]
#[allow(clippy::items_after_test_module)]
mod tests {
use super::*;
#[test]
fn test_gpu_device_type_priority() {
assert!(GpuDeviceType::DiscreteGpu > GpuDeviceType::IntegratedGpu);
assert!(GpuDeviceType::IntegratedGpu > GpuDeviceType::Cpu);
assert!(GpuDeviceType::DiscreteGpu > GpuDeviceType::Cpu);
}
#[test]
fn test_gpu_device_type_checks() {
assert!(GpuDeviceType::DiscreteGpu.is_discrete());
assert!(GpuDeviceType::IntegratedGpu.is_integrated());
assert!(GpuDeviceType::Cpu.is_cpu());
assert!(!GpuDeviceType::Cpu.is_hardware_gpu());
assert!(GpuDeviceType::DiscreteGpu.is_hardware_gpu());
}
#[test]
fn test_performance_tier() {
assert_eq!(GpuDeviceType::DiscreteGpu.performance_tier(), 5);
assert_eq!(GpuDeviceType::IntegratedGpu.performance_tier(), 3);
assert_eq!(GpuDeviceType::Cpu.performance_tier(), 1);
}
#[test]
fn test_adapter_info_cpu_fallback() {
let info = AdapterInfo::cpu_fallback();
assert!(info.device_type.is_cpu());
assert!(info.is_selected);
assert!(!info.supports_high_quality());
}
#[test]
fn test_adapter_selection_error_display() {
let err = AdapterSelectionError::NoAdapterFound;
assert_eq!(err.to_string(), "No GPU adapter found");
}
#[test]
fn test_gpu_type_from_device_type() {
assert!(matches!(GpuType::from(GpuDeviceType::DiscreteGpu), GpuType::Discrete));
assert!(matches!(GpuType::from(GpuDeviceType::IntegratedGpu), GpuType::Integrated));
assert!(matches!(GpuType::from(GpuDeviceType::Cpu), GpuType::Cpu));
}
#[test]
fn test_gpu_type_description() {
assert_eq!(GpuType::Discrete.description(), "Discrete GPU");
assert_eq!(GpuType::Integrated.description(), "Integrated GPU");
assert_eq!(GpuType::Cpu.description(), "CPU Software Rendering");
}
}
pub struct GpuAdapter;
impl GpuAdapter {
pub fn detect_primary_gpu_type() -> Option<GpuType> {
GpuType::detect_primary()
}
pub fn detect_gpu_memory_mb() -> u32 {
512
}
pub fn detect_battery_status() -> bool {
false
}
}