//go:build windows package gpu import ( "log" "os/exec" "syscall" "unsafe" ) var hideWindow = &syscall.SysProcAttr{HideWindow: true} type gpuName struct { Name string Vendor string } // --- Method 1: DXGI --- var ( dxgi = syscall.NewLazyDLL("dxgi.dll") createDXGIFactory1 = dxgi.NewProc("CreateDXGIFactory1") IID_IDXGIFactory1 = &syscall.GUID{0x770aae78, 0xf26f, 0x4dba, [8]byte{0xa8, 0x29, 0x25, 0x3c, 0x83, 0xd1, 0xb3, 0x87}} ) type dxgiDesc struct { Description [128]uint16 VendorId uint32 DeviceId uint32 SubSysId uint32 Revision uint32 DedicatedVideoMemory uint64 _ uint64 _ uint64 _ [8]byte } func dxgiGPUList() []gpuName { var factory uintptr r, _, _ := createDXGIFactory1.Call(uintptr(unsafe.Pointer(IID_IDXGIFactory1)), uintptr(unsafe.Pointer(&factory))) if r != 0 { return nil } defer release(factory) var gpus []gpuName for i := uint32(0); ; i++ { var adapter uintptr fn := callVtable(factory, 12) // EnumAdapters1 r, _, _ = syscall.SyscallN(fn, factory, uintptr(i), uintptr(unsafe.Pointer(&adapter))) if r != 0 { break } var desc dxgiDesc syscall.SyscallN(callVtable(adapter, 8), adapter, uintptr(unsafe.Pointer(&desc))) // GetDesc release(adapter) name := syscall.UTF16ToString(desc.Description[:]) vendor := classifyByVID(desc.VendorId) log.Printf("[gpu][dxgi] %d: %s (VID=0x%04x)", i, name, desc.VendorId) if name == "" || isVirtualGPU(name) || vendor == "unknown" { continue } gpus = append(gpus, gpuName{Name: name, Vendor: vendor}) } return gpus } func callVtable(obj uintptr, idx int) uintptr { vtablePtr := *(**uintptr)(unsafe.Pointer(obj)) arr := (*[64]uintptr)(unsafe.Pointer(vtablePtr)) return arr[idx] } func release(obj uintptr) { syscall.SyscallN(callVtable(obj, 2), obj) } // --- Method 2: Registry --- func registryGPUList() []gpuName { cmd := exec.Command("reg", "query", `HKLM\SYSTEM\CurrentControlSet\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}`, "/s", "/v", "DriverDesc") cmd.SysProcAttr = hideWindow out, err := cmd.CombinedOutput() if err != nil { return nil } return parseRegOutput(string(out)) } func parseRegOutput(out string) []gpuName { var gpus []gpuName seen := map[string]bool{} for _, line := range splitLines(out) { line = trim(line) if !startsWith(line, "DriverDesc") { continue } // Format: " DriverDesc REG_SZ NVIDIA GeForce RTX 4090" parts := splitN(line, "REG_SZ", 2) if len(parts) < 2 { continue } name := trim(parts[1]) if name == "" || seen[name] || isVirtualGPU(name) { continue } vendor := classifyByName(name) if vendor == "unknown" { log.Printf("[gpu][reg] unknown vendor: %s", name) continue } seen[name] = true gpus = append(gpus, gpuName{Name: name, Vendor: vendor}) } return gpus } // --- Method 3: PowerShell --- func psGPUList() []gpuName { cmd := exec.Command("powershell", "-NoProfile", "-Command", `Get-CimInstance Win32_VideoController | ForEach-Object { $_.Name + "|" + $_.AdapterCompatibility }`, ) cmd.SysProcAttr = hideWindow out, err := cmd.CombinedOutput() if err != nil { return nil } var gpus []gpuName seen := map[string]bool{} for _, line := range splitLines(string(out)) { line = trim(line) if line == "" { continue } parts := splitN(line, "|", 2) name := trim(parts[0]) vendorStr := "" if len(parts) > 1 { vendorStr = trim(parts[1]) } if name == "" || seen[name] || isVirtualGPU(name) { continue } vendor := classifyByName(vendorStr + " " + name) if vendor == "unknown" { continue } seen[name] = true gpus = append(gpus, gpuName{Name: name, Vendor: vendor}) } return gpus } // --- CPU Detection --- func detectCPUName() string { cmd := exec.Command("reg", "query", `HKLM\HARDWARE\DESCRIPTION\System\CentralProcessor\0`, "/v", "ProcessorNameString") cmd.SysProcAttr = hideWindow out, err := cmd.CombinedOutput() if err != nil { return detectCPUByEnv() } for _, line := range splitLines(string(out)) { line = trim(line) if !startsWith(line, "ProcessorNameString") { continue } parts := splitN(line, "REG_SZ", 2) if len(parts) >= 2 { return trim(parts[1]) } } return detectCPUByEnv() } func detectCPUByEnv() string { cmd := exec.Command("cmd", "/c", "echo %PROCESSOR_IDENTIFIER%") cmd.SysProcAttr = hideWindow out, err := cmd.CombinedOutput() if err != nil { return "CPU" } name := trim(string(out)) if name == "" { return "CPU" } return name } // --- Orchestrator --- func detectGPUNames() []gpuName { methods := []struct { name string fn func() []gpuName }{ {"DXGI", dxgiGPUList}, {"Registry", registryGPUList}, {"PowerShell", psGPUList}, } for _, m := range methods { gpus := m.fn() if len(gpus) > 0 { log.Printf("[gpu] %s detected %d GPU(s)", m.name, len(gpus)) return gpus } log.Printf("[gpu] %s returned empty", m.name) } return nil } func classifyByVID(vid uint32) string { switch vid { case 0x10DE: return "nvidia" case 0x8086, 0x1414: return "intel" case 0x1002: return "amd" default: return "unknown" } } func classifyByName(name string) string { l := toLower(name) switch { case strContains(l, "nvidia"): return "nvidia" case strContains(l, "intel") || strContains(l, "uhd") || strContains(l, "iris") || strContains(l, "hd graphics") || strContains(l, "arc"): return "intel" case strContains(l, "amd") || strContains(l, "radeon") || strContains(l, "ati") || strContains(l, "firepro"): return "amd" default: return "unknown" } } func isVirtualGPU(name string) bool { l := toLower(name) for _, v := range []string{ "virtual", "remote", "rdp", "citrix", "vmware", "hyper-v", "mirror", "indirect", "parsec", "splashtop", "mirage", "vnc", "displayonly", "basicdisplay", "microsoft basic", "microsoft remote", } { if strContains(l, v) { return true } } return false } func toLower(s string) string { b := make([]byte, len(s)) for i := 0; i < len(s); i++ { c := s[i] if c >= 'A' && c <= 'Z' { c += 32 } b[i] = c } return string(b) } func strContains(s, sub string) bool { return len(sub) <= len(s) && indexOf(s, sub) >= 0 } func indexOf(s, sub string) int { for i := 0; i <= len(s)-len(sub); i++ { if s[i:i+len(sub)] == sub { return i } } return -1 } func splitLines(s string) []string { var lines []string for { i := indexOf(s, "\n") if i < 0 { if s != "" { lines = append(lines, s) } break } if i > 0 && s[i-1] == '\r' { lines = append(lines, s[:i-1]) } else { lines = append(lines, s[:i]) } s = s[i+1:] } return lines } func trim(s string) string { for len(s) > 0 && (s[0] == ' ' || s[0] == '\t' || s[0] == '\r' || s[0] == '\n') { s = s[1:] } for len(s) > 0 && (s[len(s)-1] == ' ' || s[len(s)-1] == '\t' || s[len(s)-1] == '\r' || s[len(s)-1] == '\n') { s = s[:len(s)-1] } return s } func startsWith(s, prefix string) bool { return len(s) >= len(prefix) && s[:len(prefix)] == prefix } func splitN(s, sep string, n int) []string { var r []string for n > 1 { i := indexOf(s, sep) if i < 0 { break } r = append(r, s[:i]) s = s[i+len(sep):] n-- } r = append(r, s) return r }