use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum GpuVendor {
Nvidia,
Amd,
Intel,
Apple,
Qualcomm,
Arm,
ImgTec,
Microsoft,
Mesa,
Broadcom,
Unknown,
}
impl fmt::Display for GpuVendor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
GpuVendor::Nvidia => "NVIDIA",
GpuVendor::Amd => "AMD",
GpuVendor::Intel => "Intel",
GpuVendor::Apple => "Apple",
GpuVendor::Qualcomm => "Qualcomm",
GpuVendor::Arm => "ARM",
GpuVendor::ImgTec => "Imagination",
GpuVendor::Microsoft => "Microsoft",
GpuVendor::Mesa => "Mesa",
GpuVendor::Broadcom => "Broadcom",
GpuVendor::Unknown => "Unknown",
};
f.write_str(s)
}
}
pub fn detect_gpu_vendor(adapter_name: &str) -> GpuVendor {
let name = adapter_name.to_lowercase();
if name.contains("nvidia")
|| name.contains("geforce")
|| name.contains("quadro")
|| name.contains("tesla")
{
GpuVendor::Nvidia
} else if name.contains("amd")
|| name.contains("radeon")
|| name.contains("rx ")
|| name.contains("firepro")
{
GpuVendor::Amd
} else if name.contains("intel")
|| name.contains("uhd")
|| name.contains("iris")
|| name.contains("hd graphics")
{
GpuVendor::Intel
} else if name.contains("apple")
|| name.contains("m1")
|| name.contains("m2")
|| name.contains("m3")
{
GpuVendor::Apple
} else if name.contains("qualcomm") || name.contains("adreno") {
GpuVendor::Qualcomm
} else if name.contains("arm") || name.contains("mali") {
GpuVendor::Arm
} else if name.contains("imgtec") || name.contains("powervr") {
GpuVendor::ImgTec
} else if name.contains("microsoft") || name.contains("direct3d12") {
GpuVendor::Microsoft
} else if name.contains("mesa") || name.contains("llvmpipe") || name.contains("swiftshader") {
GpuVendor::Mesa
} else if name.contains("broadcom") || name.contains("videocore") {
GpuVendor::Broadcom
} else {
GpuVendor::Unknown
}
}
#[derive(Debug, Clone)]
pub struct GpuCapabilities {
pub vendor: GpuVendor,
pub adapter_name: String,
pub backend: String,
}
impl GpuCapabilities {
pub fn detect(adapter_name: &str, backend: impl Into<String>) -> Self {
Self {
vendor: detect_gpu_vendor(adapter_name),
adapter_name: adapter_name.to_string(),
backend: backend.into(),
}
}
}
impl fmt::Display for GpuCapabilities {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} GPU '{}' on {} backend",
self.vendor, self.adapter_name, self.backend
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn detects_nvidia() {
assert_eq!(
detect_gpu_vendor("NVIDIA GeForce RTX 3080"),
GpuVendor::Nvidia
);
assert_eq!(detect_gpu_vendor("Quadro P4000"), GpuVendor::Nvidia);
}
#[test]
fn detects_amd() {
assert_eq!(detect_gpu_vendor("AMD Radeon RX 6800 XT"), GpuVendor::Amd);
assert_eq!(detect_gpu_vendor("Radeon Pro 5500M"), GpuVendor::Amd);
}
#[test]
fn detects_intel() {
assert_eq!(
detect_gpu_vendor("Intel UHD Graphics 630"),
GpuVendor::Intel
);
assert_eq!(detect_gpu_vendor("Intel Iris Xe"), GpuVendor::Intel);
}
#[test]
fn detects_apple() {
assert_eq!(detect_gpu_vendor("Apple M1 Pro"), GpuVendor::Apple);
assert_eq!(detect_gpu_vendor("Apple M2 GPU"), GpuVendor::Apple);
}
#[test]
fn detects_qualcomm() {
assert_eq!(
detect_gpu_vendor("Qualcomm Adreno 660"),
GpuVendor::Qualcomm
);
}
#[test]
fn detects_arm_mali() {
assert_eq!(detect_gpu_vendor("ARM Mali-G78"), GpuVendor::Arm);
}
#[test]
fn detects_mesa_software() {
assert_eq!(detect_gpu_vendor("Mesa llvmpipe"), GpuVendor::Mesa);
assert_eq!(detect_gpu_vendor("Google SwiftShader"), GpuVendor::Mesa);
}
#[test]
fn unknown_for_garbage() {
assert_eq!(detect_gpu_vendor("Custom XYZ GPU 9000"), GpuVendor::Unknown);
}
#[test]
fn case_insensitive() {
assert_eq!(
detect_gpu_vendor("nvidia geforce gtx 1080"),
GpuVendor::Nvidia
);
assert_eq!(detect_gpu_vendor("AMD RADEON VII"), GpuVendor::Amd);
}
#[test]
fn capabilities_display() {
let caps = GpuCapabilities::detect("NVIDIA GeForce RTX 3080", "Vulkan");
let s = format!("{}", caps);
assert!(s.contains("NVIDIA"));
assert!(s.contains("GeForce"));
assert!(s.contains("Vulkan"));
}
}