Files
silo-server/internal/playback/gpudetect.go
T

127 lines
3.3 KiB
Go

package playback
import (
"log/slog"
"os"
"path/filepath"
"runtime"
"sort"
"strings"
)
const defaultDRIDir = "/dev/dri"
// HWAccelInfo describes the detected hardware acceleration capability.
type HWAccelInfo struct {
Resolved string `json:"resolved"`
RenderDevices []string `json:"render_devices"`
IntelDetected bool `json:"intel_detected"`
Source string `json:"source"`
NodeURL string `json:"node_url,omitempty"`
}
// DetectHWAccel probes this host's GPU hardware and returns structured info.
func DetectHWAccel() HWAccelInfo {
devices := listRenderDevices(defaultDRIDir)
intel := false
for _, d := range devices {
if isIntelDevice(d) {
intel = true
break
}
}
return HWAccelInfo{
Resolved: ResolveHWAccel("auto"),
RenderDevices: devices,
IntelDetected: intel,
Source: "local",
}
}
// PickRenderDevice returns the GPU render device path to use.
// If explicit is non-empty, it is returned as-is.
// Otherwise, it attempts to discover a render device under /dev/dri/.
// Returns empty string if no device is found (caller should fall back to CPU).
func PickRenderDevice(explicit string) string {
if explicit != "" {
return explicit
}
dev := detectRenderDevice(defaultDRIDir)
if dev != "" {
slog.Info("auto-detected GPU render device", "device", dev)
}
return dev
}
// ResolveHWAccel resolves "auto" into a concrete acceleration method by
// probing the system. Preference order: qsv > vaapi > none.
// Non-"auto" values are returned unchanged.
func ResolveHWAccel(hwAccel string) string {
if hwAccel != "auto" {
return hwAccel
}
if runtime.GOOS != "linux" {
return "none"
}
devices := listRenderDevices(defaultDRIDir)
if len(devices) == 0 {
slog.Info("hw_accel=auto: no render devices found, using software encoding")
return "none"
}
// Check for Intel GPU — enables QSV (preferred).
for _, dev := range devices {
if isIntelDevice(dev) {
slog.Info("hw_accel=auto: Intel GPU detected, using QSV", "device", dev)
return "qsv"
}
}
// Any accessible render device supports VAAPI.
slog.Info("hw_accel=auto: non-Intel GPU detected, using VAAPI", "device", devices[0])
return "vaapi"
}
// listRenderDevices returns all accessible /dev/dri/renderD* paths, sorted.
func listRenderDevices(driDir string) []string {
pattern := filepath.Join(driDir, "renderD*")
matches, err := filepath.Glob(pattern)
if err != nil || len(matches) == 0 {
return nil
}
sort.Strings(matches)
var accessible []string
for _, dev := range matches {
if f, err := os.Open(dev); err == nil {
f.Close()
accessible = append(accessible, dev)
}
}
return accessible
}
// isIntelDevice checks whether a render device belongs to an Intel GPU by
// reading the PCI vendor ID from sysfs. Intel vendor ID is 0x8086.
func isIntelDevice(renderDevPath string) bool {
// /dev/dri/renderD128 → card name "renderD128"
name := filepath.Base(renderDevPath)
vendorPath := filepath.Join("/sys/class/drm", name, "device", "vendor")
data, err := os.ReadFile(vendorPath)
if err != nil {
return false
}
return strings.TrimSpace(string(data)) == "0x8086"
}
// detectRenderDevice enumerates /dev/dri/renderD* and returns the first
// available device, or empty string if none found.
func detectRenderDevice(driDir string) string {
devices := listRenderDevices(driDir)
if len(devices) > 0 {
return devices[0]
}
return ""
}