165 lines
4.1 KiB
Go
165 lines
4.1 KiB
Go
//go:build windows
|
|||
|
|
|
||
|
|
package gpu
|
||
|
|
|
||
|
|
import (
|
||
|
|
"log"
|
||
|
|
"os/exec"
|
||
|
|
"strings"
|
||
|
|
)
|
||
|
|
|
||
|
|
type gpuName struct {
|
||
|
|
Name string
|
||
|
|
Vendor string
|
||
|
|
}
|
||
|
|
|
||
|
|
func detectGPUNames() []gpuName {
|
||
|
|
// Try PowerShell first, then wmic
|
||
|
|
gpus := psGPUList()
|
||
|
|
if len(gpus) > 0 {
|
||
|
|
log.Printf("[gpu] PowerShell found %d GPUs", len(gpus))
|
||
|
|
for _, g := range gpus {
|
||
|
|
log.Printf("[gpu] %s -> vendor=%s", g.Name, g.Vendor)
|
||
|
|
}
|
||
|
|
return gpus
|
||
|
|
}
|
||
|
|
|
||
|
|
gpus = wmicGPUList()
|
||
|
|
if len(gpus) > 0 {
|
||
|
|
log.Printf("[gpu] WMIC found %d GPUs", len(gpus))
|
||
|
|
for _, g := range gpus {
|
||
|
|
log.Printf("[gpu] %s -> vendor=%s", g.Name, g.Vendor)
|
||
|
|
}
|
||
|
|
return gpus
|
||
|
|
}
|
||
|
|
|
||
|
|
log.Printf("[gpu] No GPUs detected via PowerShell or WMIC")
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func psGPUList() []gpuName {
|
||
|
|
out, err := exec.Command("powershell", "-NoProfile", "-Command",
|
||
|
|
"Get-CimInstance Win32_VideoController | Select-Object Name,AdapterCompatibility,DriverVersion | ConvertTo-Csv -NoTypeInformation",
|
||
|
|
).CombinedOutput()
|
||
|
|
log.Printf("[gpu] PS output:\n%s", string(out))
|
||
|
|
if err != nil {
|
||
|
|
log.Printf("[gpu] PS error: %v", err)
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
return parseCSV(string(out))
|
||
|
|
}
|
||
|
|
|
||
|
|
func wmicGPUList() []gpuName {
|
||
|
|
out, err := exec.Command("wmic", "path", "win32_VideoController",
|
||
|
|
"get", "Name,AdapterCompatibility", "/format:csv").CombinedOutput()
|
||
|
|
log.Printf("[gpu] WMIC output:\n%s", string(out))
|
||
|
|
if err != nil {
|
||
|
|
log.Printf("[gpu] WMIC error: %v", err)
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
return parseWMIC(string(out))
|
||
|
|
}
|
||
|
|
|
||
|
|
func parseCSV(out string) []gpuName {
|
||
|
|
var gpus []gpuName
|
||
|
|
seen := map[string]bool{}
|
||
|
|
lines := strings.Split(out, "\n")
|
||
|
|
for _, line := range lines {
|
||
|
|
line = strings.TrimSpace(line)
|
||
|
|
if line == "" || strings.HasPrefix(line, "\"Name\"") || strings.HasPrefix(line, "Name") {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
// Remove quotes: "Name","AdapterCompatibility"
|
||
|
|
line = strings.ReplaceAll(line, "\"", "")
|
||
|
|
parts := strings.SplitN(line, ",", 2)
|
||
|
|
if len(parts) < 1 {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
name := strings.TrimSpace(parts[0])
|
||
|
|
vendor := ""
|
||
|
|
if len(parts) >= 2 {
|
||
|
|
vendor = strings.TrimSpace(parts[1])
|
||
|
|
}
|
||
|
|
if name == "" || seen[name] {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
if isVirtualGPU(name) {
|
||
|
|
log.Printf("[gpu] SKIP virtual: %s", name)
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
vendor = classifyVendor(vendor, name)
|
||
|
|
if vendor == "unknown" {
|
||
|
|
log.Printf("[gpu] SKIP unknown vendor (vendor=%q name=%q)", vendor, name)
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
seen[name] = true
|
||
|
|
gpus = append(gpus, gpuName{Name: name, Vendor: vendor})
|
||
|
|
}
|
||
|
|
return gpus
|
||
|
|
}
|
||
|
|
|
||
|
|
func parseWMIC(out string) []gpuName {
|
||
|
|
var gpus []gpuName
|
||
|
|
seen := map[string]bool{}
|
||
|
|
lines := strings.Split(out, "\n")
|
||
|
|
for _, line := range lines {
|
||
|
|
line = strings.TrimSpace(line)
|
||
|
|
if line == "" || strings.HasPrefix(line, "Node,") {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
parts := strings.SplitN(line, ",", 3)
|
||
|
|
if len(parts) < 3 {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
name := strings.TrimSpace(parts[2])
|
||
|
|
vendor := strings.TrimSpace(parts[1])
|
||
|
|
if name == "" || seen[name] {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
if isVirtualGPU(name) {
|
||
|
|
log.Printf("[gpu] SKIP virtual: %s", name)
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
vendor = classifyVendor(vendor, name)
|
||
|
|
if vendor == "unknown" {
|
||
|
|
log.Printf("[gpu] SKIP unknown vendor (vendor=%q name=%q)", vendor, name)
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
seen[name] = true
|
||
|
|
gpus = append(gpus, gpuName{Name: name, Vendor: vendor})
|
||
|
|
}
|
||
|
|
return gpus
|
||
|
|
}
|
||
|
|
|
||
|
|
func classifyVendor(vendor, name string) string {
|
||
|
|
lower := strings.ToLower(vendor + " " + name)
|
||
|
|
switch {
|
||
|
|
case strings.Contains(lower, "nvidia"):
|
||
|
|
return "nvidia"
|
||
|
|
case strings.Contains(lower, "intel") || strings.Contains(lower, "uhd graphics") ||
|
||
|
|
strings.Contains(lower, "iris") || strings.Contains(lower, "hd graphics"):
|
||
|
|
return "intel"
|
||
|
|
case strings.Contains(lower, "amd") || strings.Contains(lower, "radeon") || strings.Contains(lower, "ati"):
|
||
|
|
return "amd"
|
||
|
|
default:
|
||
|
|
return "unknown"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func isVirtualGPU(name string) bool {
|
||
|
|
lower := strings.ToLower(name)
|
||
|
|
// Filter out virtual/remote/display-only adapters that don't have encoders
|
||
|
|
virtual := []string{
|
||
|
|
"virtual", "remote", "rdp", "citrix", "vmware", "hyper-v",
|
||
|
|
"mirror", "indirect", "parsec", "splashtop", "idm",
|
||
|
|
"mirage", "vnc", "displayonly", "basicdisplay",
|
||
|
|
"microsoft basic", "microsoft remote",
|
||
|
|
}
|
||
|
|
for _, v := range virtual {
|
||
|
|
if strings.Contains(lower, v) {
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return false
|
||
|
|
}
|