#[cfg(feature = "hw")]
use ai_hwaccel::{AcceleratorFamily, AcceleratorProfile, AcceleratorRegistry};
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize,
)]
#[non_exhaustive]
pub enum QualityTier {
Low,
#[default]
Medium,
High,
Ultra,
}
impl std::fmt::Display for QualityTier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
QualityTier::Low => write!(f, "Low"),
QualityTier::Medium => write!(f, "Medium"),
QualityTier::High => write!(f, "High"),
QualityTier::Ultra => write!(f, "Ultra"),
}
}
}
#[cfg(feature = "hw")]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct HardwareProfile {
pub quality: QualityTier,
pub has_gpu: bool,
pub gpu_memory_bytes: u64,
pub device_name: String,
pub accelerator_count: usize,
}
#[cfg(feature = "hw")]
impl Default for HardwareProfile {
fn default() -> Self {
Self {
quality: QualityTier::Medium,
has_gpu: false,
gpu_memory_bytes: 0,
device_name: "Unknown".into(),
accelerator_count: 0,
}
}
}
#[cfg(feature = "hw")]
impl HardwareProfile {
#[must_use]
pub fn detect() -> Self {
let registry = AcceleratorRegistry::detect();
let profile = Self::from_registry(®istry);
tracing::debug!(
quality = %profile.quality,
device = %profile.device_name,
gpu = profile.has_gpu,
"hardware detected"
);
profile
}
#[must_use]
pub fn from_registry(registry: &AcceleratorRegistry) -> Self {
let has_gpu = registry.has_accelerator();
let gpu_memory_bytes = registry.total_accelerator_memory();
let accelerator_count = registry
.available()
.iter()
.filter(|p| !matches!(p.accelerator.family(), AcceleratorFamily::Cpu))
.count();
let device_name = registry
.best_available()
.map(|p| p.accelerator.to_string())
.unwrap_or_else(|| "CPU".into());
let quality = classify_quality(registry.best_available(), gpu_memory_bytes);
Self {
quality,
has_gpu,
gpu_memory_bytes,
device_name,
accelerator_count,
}
}
#[must_use]
pub fn gpu_memory_display(&self) -> String {
if self.gpu_memory_bytes == 0 {
return "N/A".into();
}
let gib = self.gpu_memory_bytes as f64 / (1024.0 * 1024.0 * 1024.0);
if gib >= 1.0 {
format!("{gib:.1} GiB")
} else {
let mib = self.gpu_memory_bytes as f64 / (1024.0 * 1024.0);
format!("{mib:.0} MiB")
}
}
}
#[cfg(feature = "hw")]
fn classify_quality(best: Option<&AcceleratorProfile>, total_vram: u64) -> QualityTier {
let Some(profile) = best else {
return QualityTier::Low;
};
if matches!(profile.accelerator.family(), AcceleratorFamily::Cpu) {
return QualityTier::Low;
}
let gib = total_vram as f64 / (1024.0 * 1024.0 * 1024.0);
if gib < 4.0 {
QualityTier::Medium
} else if gib < 8.0 {
QualityTier::High
} else {
QualityTier::Ultra
}
}
#[cfg(all(test, feature = "hw"))]
mod tests {
use super::*;
#[test]
fn default_profile() {
let p = HardwareProfile::default();
assert_eq!(p.quality, QualityTier::Medium);
assert!(!p.has_gpu);
}
#[test]
fn quality_tier_display() {
assert_eq!(QualityTier::Low.to_string(), "Low");
assert_eq!(QualityTier::Ultra.to_string(), "Ultra");
}
#[test]
fn classify_no_gpu() {
assert_eq!(classify_quality(None, 0), QualityTier::Low);
}
#[test]
fn classify_cpu_only() {
let cpu = AcceleratorProfile::cpu(16 * 1024 * 1024 * 1024);
assert_eq!(classify_quality(Some(&cpu), 0), QualityTier::Low);
}
#[test]
fn classify_mid_gpu() {
let gpu = AcceleratorProfile::cuda(0, 6 * 1024 * 1024 * 1024);
assert_eq!(
classify_quality(Some(&gpu), 6 * 1024 * 1024 * 1024),
QualityTier::High
);
}
#[test]
fn classify_high_gpu() {
let gpu = AcceleratorProfile::cuda(0, 8 * 1024 * 1024 * 1024);
assert_eq!(
classify_quality(Some(&gpu), 8 * 1024 * 1024 * 1024),
QualityTier::Ultra
);
}
#[test]
fn classify_boundary_4gib() {
let gpu = AcceleratorProfile::cuda(0, 4 * 1024 * 1024 * 1024);
assert_eq!(
classify_quality(Some(&gpu), 4 * 1024 * 1024 * 1024),
QualityTier::High
);
}
#[test]
fn detect_returns_valid() {
let p = HardwareProfile::detect();
assert!(!p.device_name.is_empty());
}
#[test]
fn gpu_memory_display() {
let mut p = HardwareProfile::default();
assert_eq!(p.gpu_memory_display(), "N/A");
p.gpu_memory_bytes = 8 * 1024 * 1024 * 1024;
assert_eq!(p.gpu_memory_display(), "8.0 GiB");
}
}