// Package blender: host GPU backend detection for AMD/NVIDIA/Intel. package blender import ( "bufio" "os" "os/exec" "path/filepath" "strconv" "strings" ) // DetectGPUBackends detects whether AMD, NVIDIA, and/or Intel GPUs are available // using host-level hardware probing only. func DetectGPUBackends() (hasAMD, hasNVIDIA, hasIntel bool, ok bool) { return detectGPUBackendsFromHost() } func detectGPUBackendsFromHost() (hasAMD, hasNVIDIA, hasIntel bool, ok bool) { if amd, nvidia, intel, found := detectGPUBackendsFromDRM(); found { return amd, nvidia, intel, true } if amd, nvidia, intel, found := detectGPUBackendsFromLSPCI(); found { return amd, nvidia, intel, true } return false, false, false, false } func detectGPUBackendsFromDRM() (hasAMD, hasNVIDIA, hasIntel bool, ok bool) { entries, err := os.ReadDir("/sys/class/drm") if err != nil { return false, false, false, false } for _, entry := range entries { name := entry.Name() if !isDRMCardNode(name) { continue } vendorPath := filepath.Join("/sys/class/drm", name, "device", "vendor") vendorRaw, err := os.ReadFile(vendorPath) if err != nil { continue } vendor := strings.TrimSpace(strings.ToLower(string(vendorRaw))) switch vendor { case "0x1002": hasAMD = true ok = true case "0x10de": hasNVIDIA = true ok = true case "0x8086": hasIntel = true ok = true } } return hasAMD, hasNVIDIA, hasIntel, ok } func isDRMCardNode(name string) bool { if !strings.HasPrefix(name, "card") { return false } if strings.Contains(name, "-") { // Connector entries like card0-DP-1 are not GPU device nodes. return false } if len(name) <= len("card") { return false } _, err := strconv.Atoi(strings.TrimPrefix(name, "card")) return err == nil } func detectGPUBackendsFromLSPCI() (hasAMD, hasNVIDIA, hasIntel bool, ok bool) { if _, err := exec.LookPath("lspci"); err != nil { return false, false, false, false } out, err := exec.Command("lspci").CombinedOutput() if err != nil { return false, false, false, false } scanner := bufio.NewScanner(strings.NewReader(string(out))) for scanner.Scan() { line := strings.ToLower(strings.TrimSpace(scanner.Text())) if !isGPUControllerLine(line) { continue } if strings.Contains(line, "nvidia") { hasNVIDIA = true ok = true } if strings.Contains(line, "amd") || strings.Contains(line, "ati") || strings.Contains(line, "radeon") { hasAMD = true ok = true } if strings.Contains(line, "intel") { hasIntel = true ok = true } } return hasAMD, hasNVIDIA, hasIntel, ok } func isGPUControllerLine(line string) bool { return strings.Contains(line, "vga compatible controller") || strings.Contains(line, "3d controller") || strings.Contains(line, "display controller") }