mod amd;
mod intel;
mod nvidia;
mod sysfs;
mod types;
mod utilization;
pub use types::{GpuDevice, GpuUtilization, GpuVendor};
pub use utilization::GpuUtilizationReader;
pub fn detect_gpus() -> Vec<GpuDevice> {
let mut devices = Vec::new();
devices.extend(nvidia::detect_nvidia());
devices.extend(amd::detect_amd());
devices.extend(intel::detect_intel());
for (i, d) in devices.iter_mut().enumerate() {
d.index = i as u32;
}
devices
}
pub fn manufacturer_label(v: GpuVendor) -> &'static str {
match v {
GpuVendor::Nvidia => "NVIDIA",
GpuVendor::Amd => "AMD",
GpuVendor::Intel => "Intel",
}
}
pub fn has_nvidia() -> bool {
!nvidia::detect_nvidia().is_empty()
}
pub fn supports_av1_encode(device: &GpuDevice) -> bool {
match device.vendor {
GpuVendor::Nvidia => true,
GpuVendor::Amd => true,
GpuVendor::Intel => true,
}
}
#[cfg(windows)]
fn windows_video_controllers() -> &'static [(String, u16, u16)] {
use std::sync::OnceLock;
static CACHE: OnceLock<Vec<(String, u16, u16)>> = OnceLock::new();
CACHE.get_or_init(|| {
let output = std::process::Command::new("powershell")
.args([
"-NoProfile",
"-NonInteractive",
"-Command",
"Get-CimInstance Win32_VideoController | \
ForEach-Object { \"$($_.Name)|$($_.PNPDeviceID)\" }",
])
.output();
let Ok(output) = output else {
return Vec::new();
};
String::from_utf8_lossy(&output.stdout)
.lines()
.filter_map(|line| {
let (name, pnp) = line.split_once('|')?;
let vendor_id = win_hex_after(pnp, "VEN_")?;
let device_id = win_hex_after(pnp, "DEV_").unwrap_or(0);
Some((name.trim().to_string(), vendor_id, device_id))
})
.collect()
})
}
#[cfg(windows)]
fn win_hex_after(s: &str, marker: &str) -> Option<u16> {
let start = s.find(marker)? + marker.len();
let hex: String = s[start..].chars().take(4).collect();
u16::from_str_radix(&hex, 16).ok()
}
#[cfg(windows)]
fn detect_windows_vendor(vendor: GpuVendor, vendor_id: u16) -> Vec<GpuDevice> {
let vendor_hex = format!("0x{vendor_id:04x}");
windows_video_controllers()
.iter()
.filter(|(_, vid, _)| *vid == vendor_id)
.enumerate()
.map(|(idx, (name, _vid, did))| {
let device = format!("0x{did:04x}");
let generation = match vendor {
GpuVendor::Amd => amd::amd_generation_from_device_id(&device),
GpuVendor::Intel => intel::intel_generation_from_device_id(&device),
GpuVendor::Nvidia => "Unknown".into(),
};
GpuDevice {
vendor,
name: name.clone(),
index: idx as u32,
vendor_index: idx as u32,
generation,
pci_id: format!("{vendor_hex}:{device}"),
vram_mib: 0, serial: None,
host_pci_address: String::new(),
vendor_id_hex: vendor_hex.clone(),
}
})
.collect()
}