use tracing::debug;
use crate::error::DetectionError;
use crate::hardware::AcceleratorType;
use crate::profile::AcceleratorProfile;
use super::command::{DEFAULT_TIMEOUT, run_tool};
#[cfg(target_os = "windows")]
const NVIDIA_SMI_PATHS: &[&str] = &[
r"C:\Windows\System32\nvidia-smi.exe",
r"C:\Program Files\NVIDIA Corporation\NVSMI\nvidia-smi.exe",
];
pub(crate) fn detect_windows_gpu(
profiles: &mut Vec<AcceleratorProfile>,
warnings: &mut Vec<DetectionError>,
) {
#[cfg(target_os = "windows")]
if let Some(nvsmi) = find_nvidia_smi_windows() {
debug!(path = nvsmi, "found nvidia-smi.exe at known Windows path");
}
if let Ok(output) = run_tool(
"wmic",
&[
"path",
"Win32_VideoController",
"get",
"Name,AdapterRAM,DriverVersion,VideoProcessor",
"/format:csv",
],
DEFAULT_TIMEOUT,
) && parse_wmic_output(&output.stdout, profiles)
{
return;
}
if let Ok(output) = run_tool(
"powershell",
&[
"-NoProfile",
"-Command",
"Get-CimInstance Win32_VideoController | Select-Object Name,AdapterRAM,DriverVersion | ConvertTo-Csv -NoTypeInformation",
],
DEFAULT_TIMEOUT,
) && parse_powershell_csv(&output.stdout, profiles)
{
return;
}
debug!("no Windows GPU detection method available");
let _ = warnings;
}
pub(crate) fn parse_wmic_output(output: &str, profiles: &mut Vec<AcceleratorProfile>) -> bool {
let mut found = false;
let mut device_id: u32 = 0;
for line in output.lines().skip(1) {
let line = line.trim();
if line.is_empty() {
continue;
}
let fields: Vec<&str> = line.split(',').collect();
if fields.len() < 4 {
continue;
}
let adapter_ram: u64 = fields[1].trim().parse().unwrap_or(0);
let driver_version = {
let v = fields[2].trim();
if v.is_empty() {
None
} else {
Some(v.to_string())
}
};
let name = fields[3].trim().to_string();
let name_lower = name.to_lowercase();
if name_lower.contains("basic display")
|| name_lower.contains("remote desktop")
|| name_lower.contains("microsoft")
{
continue;
}
debug!(
device_id,
name = %name,
adapter_ram_mb = adapter_ram / (1024 * 1024),
"Windows GPU detected via WMI"
);
profiles.push(AcceleratorProfile {
accelerator: AcceleratorType::VulkanGpu { device_id },
available: true,
memory_bytes: adapter_ram,
driver_version,
device_name: Some(name),
..Default::default()
});
device_id += 1;
found = true;
}
found
}
pub(crate) fn parse_powershell_csv(output: &str, profiles: &mut Vec<AcceleratorProfile>) -> bool {
let mut found = false;
let mut device_id: u32 = 0;
for line in output.lines().skip(1) {
let line = line.trim();
if line.is_empty() {
continue;
}
let fields: Vec<&str> = line
.split(',')
.map(|f| f.trim().trim_matches('"'))
.collect();
if fields.len() < 3 {
continue;
}
let name = fields[0].to_string();
let adapter_ram: u64 = fields[1].parse().unwrap_or(0);
let driver_version = {
let v = fields[2];
if v.is_empty() {
None
} else {
Some(v.to_string())
}
};
let name_lower = name.to_lowercase();
if name_lower.contains("basic display")
|| name_lower.contains("remote desktop")
|| name_lower.contains("microsoft")
{
continue;
}
debug!(
device_id,
name = %name,
adapter_ram_mb = adapter_ram / (1024 * 1024),
"Windows GPU detected via PowerShell"
);
profiles.push(AcceleratorProfile {
accelerator: AcceleratorType::VulkanGpu { device_id },
available: true,
memory_bytes: adapter_ram,
driver_version,
device_name: Some(name),
..Default::default()
});
device_id += 1;
found = true;
}
found
}
#[cfg(target_os = "windows")]
#[must_use]
pub(crate) fn find_nvidia_smi_windows() -> Option<&'static str> {
NVIDIA_SMI_PATHS
.iter()
.find(|path| std::path::Path::new(path).exists())
.copied()
}