1package gpu
2
3import (
4 "os"
5 "os/exec"
6 "path/filepath"
7 "regexp"
8 "strings"
9
10 "github.com/bnema/dumber/internal/logging"
11)
12
13// GPUVendor represents GPU vendor types
14type GPUVendor string
15
16const (
17 GPUVendorAMD GPUVendor = "amd"
18 GPUVendorNVIDIA GPUVendor = "nvidia"
19 GPUVendorIntel GPUVendor = "intel"
20 GPUVendorUnknown GPUVendor = "unknown"
21)
22
23// GPUInfo contains information about the detected GPU
24type GPUInfo struct {
25 Vendor GPUVendor
26 Name string
27 Driver string // radeonsi, nouveau, i965, etc.
28}
29
30// String returns a string representation of the GPU info
31func (g GPUInfo) String() string {
32 if g.Name != "" {
33 return string(g.Vendor) + " (" + g.Name + ")"
34 }
35 return string(g.Vendor)
36}
37
38// DetectGPU attempts to detect the GPU vendor and driver using multiple methods
39func DetectGPU() GPUInfo {
40 logging.Debug("Starting GPU detection")
41
42 // Try methods in order of reliability
43 methods := []func() GPUInfo{
44 detectViaGLXInfo,
45 detectViaLSPCI,
46 detectViaSysFS,
47 detectViaNVIDIAProc,
48 }
49
50 for _, method := range methods {
51 if info := method(); info.Vendor != GPUVendorUnknown {
52 logging.Info("GPU detected: " + info.String() + " (driver: " + info.GetVAAPIDriverName() + ")")
53 return info
54 }
55 }
56
57 logging.Warn("No GPU detected using any method")
58 return GPUInfo{Vendor: GPUVendorUnknown}
59}
60
61// detectViaGLXInfo uses glxinfo to detect GPU vendor and driver
62func detectViaGLXInfo() GPUInfo {
63 cmd := exec.Command("glxinfo")
64 output, err := cmd.Output()
65 if err != nil {
66 return GPUInfo{Vendor: GPUVendorUnknown}
67 }
68
69 lines := strings.Split(string(output), "\n")
70 var vendor, renderer string
71
72 for _, line := range lines {
73 line = strings.TrimSpace(line)
74 if strings.HasPrefix(line, "OpenGL vendor string:") {
75 vendor = strings.TrimSpace(strings.TrimPrefix(line, "OpenGL vendor string:"))
76 } else if strings.HasPrefix(line, "OpenGL renderer string:") {
77 renderer = strings.TrimSpace(strings.TrimPrefix(line, "OpenGL renderer string:"))
78 }
79 }
80
81 info := GPUInfo{Name: renderer}
82
83 // Parse vendor
84 vendor = strings.ToLower(vendor)
85 if strings.Contains(vendor, "amd") || strings.Contains(vendor, "ati") {
86 info.Vendor = GPUVendorAMD
87 // Extract driver from renderer (e.g., "radeonsi" from "AMD Radeon RX 9070 XT (radeonsi, ...)")
88 if strings.Contains(renderer, "radeonsi") {
89 info.Driver = "radeonsi"
90 }
91 } else if strings.Contains(vendor, "nvidia") {
92 info.Vendor = GPUVendorNVIDIA
93 info.Driver = "nvidia"
94 } else if strings.Contains(vendor, "intel") {
95 info.Vendor = GPUVendorIntel
96 // Intel can use iHD or i965 drivers
97 if strings.Contains(renderer, "iHD") {
98 info.Driver = "iHD"
99 } else {
100 info.Driver = "i965"
101 }
102 }
103
104 return info
105}
106
107// detectViaLSPCI uses lspci to detect GPU vendor
108func detectViaLSPCI() GPUInfo {
109 cmd := exec.Command("lspci")
110 output, err := cmd.Output()
111 if err != nil {
112 return GPUInfo{Vendor: GPUVendorUnknown}
113 }
114
115 lines := strings.Split(string(output), "\n")
116 vgaPattern := regexp.MustCompile(`(?i)VGA|3D|Display`)
117
118 for _, line := range lines {
119 if vgaPattern.MatchString(line) {
120 line = strings.ToLower(line)
121 if strings.Contains(line, "amd") || strings.Contains(line, "ati") {
122 return GPUInfo{
123 Vendor: GPUVendorAMD,
124 Name: extractDeviceName(line),
125 Driver: "radeonsi", // Default for modern AMD GPUs
126 }
127 } else if strings.Contains(line, "nvidia") {
128 return GPUInfo{
129 Vendor: GPUVendorNVIDIA,
130 Name: extractDeviceName(line),
131 Driver: "nvidia",
132 }
133 } else if strings.Contains(line, "intel") {
134 return GPUInfo{
135 Vendor: GPUVendorIntel,
136 Name: extractDeviceName(line),
137 Driver: "iHD", // Default to newer Intel driver
138 }
139 }
140 }
141 }
142
143 return GPUInfo{Vendor: GPUVendorUnknown}
144}
145
146// detectViaSysFS checks sysfs for GPU vendor IDs
147func detectViaSysFS() GPUInfo {
148 pattern := "/sys/class/drm/card*/device/vendor"
149 matches, err := filepath.Glob(pattern)
150 if err != nil {
151 return GPUInfo{Vendor: GPUVendorUnknown}
152 }
153
154 for _, vendorFile := range matches {
155 data, err := os.ReadFile(vendorFile)
156 if err != nil {
157 continue
158 }
159
160 vendorID := strings.TrimSpace(string(data))
161 switch vendorID {
162 case "0x1002": // AMD
163 return GPUInfo{
164 Vendor: GPUVendorAMD,
165 Driver: "radeonsi",
166 }
167 case "0x10de": // NVIDIA
168 return GPUInfo{
169 Vendor: GPUVendorNVIDIA,
170 Driver: "nvidia",
171 }
172 case "0x8086": // Intel
173 return GPUInfo{
174 Vendor: GPUVendorIntel,
175 Driver: "iHD",
176 }
177 }
178 }
179
180 return GPUInfo{Vendor: GPUVendorUnknown}
181}
182
183// detectViaNVIDIAProc checks for NVIDIA driver via /proc
184func detectViaNVIDIAProc() GPUInfo {
185 if _, err := os.Stat("/proc/driver/nvidia/version"); err == nil {
186 return GPUInfo{
187 Vendor: GPUVendorNVIDIA,
188 Driver: "nvidia",
189 }
190 }
191 return GPUInfo{Vendor: GPUVendorUnknown}
192}
193
194// extractDeviceName extracts a readable device name from lspci output
195func extractDeviceName(line string) string {
196 // Extract text after the colon but before any brackets
197 parts := strings.Split(line, ":")
198 if len(parts) < 2 {
199 return ""
200 }
201
202 name := strings.TrimSpace(parts[len(parts)-1])
203
204 // Remove revision info in parentheses
205 if idx := strings.Index(name, " (rev "); idx != -1 {
206 name = name[:idx]
207 }
208
209 return name
210}
211
212// GetVAAPIDriverName returns the appropriate VA-API driver name for the GPU vendor
213func (g GPUInfo) GetVAAPIDriverName() string {
214 if g.Driver != "" {
215 return g.Driver
216 }
217
218 switch g.Vendor {
219 case GPUVendorAMD:
220 return "radeonsi"
221 case GPUVendorNVIDIA:
222 return "vdpau" // NVIDIA uses VDPAU backend for VA-API
223 case GPUVendorIntel:
224 return "iHD" // Modern Intel GPUs
225 default:
226 return ""
227 }
228}
229
230// SupportsVAAPI returns true if the GPU vendor supports VA-API
231func (g GPUInfo) SupportsVAAPI() bool {
232 return g.Vendor == GPUVendorAMD || g.Vendor == GPUVendorIntel || g.Vendor == GPUVendorNVIDIA
233}
234
235// SupportsAV1Hardware returns true if GPU has hardware AV1 decoding support
236func (g GPUInfo) SupportsAV1Hardware() bool {
237 switch g.Vendor {
238 case GPUVendorAMD:
239 return g.supportsAMDAV1()
240 case GPUVendorNVIDIA:
241 return g.supportsNVIDIAAV1()
242 case GPUVendorIntel:
243 return g.supportsIntelAV1()
244 default:
245 return false
246 }
247}
248
249// supportsAMDAV1 checks for AMD AV1 hardware decode support based on official AMD specifications
250// RDNA2+ architectures support AV1, with RDNA3+ adding encode support and RDNA4 bringing full B-frame support
251func (g GPUInfo) supportsAMDAV1() bool {
252 name := strings.ToLower(g.Name)
253
254 // RDNA 2 architecture (RX 6000 series) - AV1 decode only
255 // Source: Official AMD RDNA2 specifications - hardware-accelerated AV1 decoding
256 rdna2Models := []string{
257 // Desktop RX 6000 series (all RDNA2 models support AV1 decode)
258 "rx 6400", "rx 6450", "rx 6500", "rx 6550", "rx 6600", "rx 6650",
259 "rx 6700", "rx 6700 xt", "rx 6750", "rx 6750 xt",
260 "rx 6800", "rx 6800 xt", "rx 6900", "rx 6900 xt", "rx 6950 xt",
261 }
262
263 // RDNA 3 architecture (RX 7000 series) - AV1 encode/decode with VCN 4.0
264 // Source: Official AMD specifications - first RDNA with dedicated media engine and AV1 encode
265 rdna3Models := []string{
266 "rx 7600", "rx 7600 xt", "rx 7700", "rx 7700 xt",
267 "rx 7800", "rx 7800 xt", "rx 7900", "rx 7900 xt", "rx 7900 gre", "rx 7900 xtx",
268 }
269
270 // RDNA 4 architecture (RX 9000 series) - Full AV1 support with B-frame encoding
271 // Source: Official AMD RX 9070 series announcement - enhanced AV1 with B-frame support
272 rdna4Models := []string{
273 "rx 9070", "rx 9070 xt", // Officially announced models
274 // Future RDNA4 models expected to follow similar naming
275 "rx 9060", "rx 9080", "rx 9090",
276 }
277
278 // Check against all supported models
279 allModels := append(rdna2Models, rdna3Models...)
280 allModels = append(allModels, rdna4Models...)
281
282 for _, model := range allModels {
283 if strings.Contains(name, model) {
284 return true
285 }
286 }
287
288 // APUs with RDNA2/3 integrated graphics
289 // Source: AMD APU specifications with RDNA2+ iGPUs
290 apuModels := []string{
291 // RDNA2-based APUs
292 "ryzen 6000", "radeon 680m", "radeon 660m",
293 // RDNA3-based APUs
294 "ryzen 7000", "radeon 780m", "radeon 760m",
295 // Future RDNA3+ APUs
296 "ryzen 8000", "ryzen 9000", "radeon 880m",
297 }
298
299 for _, apu := range apuModels {
300 if strings.Contains(name, apu) {
301 return true
302 }
303 }
304
305 return false
306}
307
308// supportsNVIDIAAV1 checks for NVIDIA AV1 hardware decode/encode support based on official NVIDIA specifications
309func (g GPUInfo) supportsNVIDIAAV1() bool {
310 name := strings.ToLower(g.Name)
311
312 // RTX 30 series (Ampere) - AV1 decode only, up to 8K HDR
313 // Source: Official NVIDIA RTX 30 series specifications - first NVIDIA GPUs with AV1 decode
314 rtx30Series := []string{
315 "rtx 3060", "rtx 3060 ti", "rtx 3070", "rtx 3070 ti",
316 "rtx 3080", "rtx 3080 ti", "rtx 3090", "rtx 3090 ti",
317 "rtx 3050", // Budget model with AV1 decode
318 }
319
320 // RTX 40 series (Ada Lovelace) - Full AV1 encode/decode with 8th gen NVENC
321 // Source: Official NVIDIA RTX 40 series specifications - real-time AV1 encoding support
322 rtx40Series := []string{
323 "rtx 4060", "rtx 4060 ti", "rtx 4070", "rtx 4070 ti", "rtx 4070 super",
324 "rtx 4080", "rtx 4080 super", "rtx 4090",
325 "rtx 4050", // Mobile/budget variants
326 }
327
328 // Mobile RTX 40 series (Ada Lovelace) - Full AV1 support
329 mobileRtx40 := []string{
330 "rtx 4050 mobile", "rtx 4060 mobile", "rtx 4070 mobile", "rtx 4080 mobile", "rtx 4090 mobile",
331 }
332
333 // Professional Ada Lovelace cards
334 professionalAda := []string{
335 "rtx a4000", "rtx a4500", "rtx a5000", "rtx a6000", // Ada Lovelace professional
336 "l4", "l40", "l40s", // Data center Ada Lovelace cards
337 }
338
339 // Check all series with AV1 support (decode-only for RTX 30, encode+decode for RTX 40+)
340 allCards := append(rtx30Series, rtx40Series...)
341 allCards = append(allCards, mobileRtx40...)
342 allCards = append(allCards, professionalAda...)
343
344 for _, card := range allCards {
345 if strings.Contains(name, card) {
346 return true
347 }
348 }
349
350 // Architecture-based detection
351 if strings.Contains(name, "ampere") || strings.Contains(name, "ada lovelace") {
352 return true
353 }
354
355 return false
356}
357
358// supportsIntelAV1 checks for Intel AV1 hardware decode/encode support based on official Intel specifications
359// Intel Arc GPUs are the first to offer full hardware AV1 acceleration (encode + decode)
360func (g GPUInfo) supportsIntelAV1() bool {
361 name := strings.ToLower(g.Name)
362
363 // Intel Arc A-Series discrete GPUs (Alchemist, Xe-HPG architecture)
364 // Source: Official Intel Arc specifications - industry-first full AV1 hardware acceleration
365 arcAlchemist := []string{
366 "arc a310", "arc a350", "arc a380", // Entry-level
367 "arc a580", "arc a750", "arc a770", // Performance models
368 }
369
370 // Intel Arc B-Series discrete GPUs (Battlemage, Xe2 architecture)
371 // Source: Official Intel Arc B-Series launch (December 2024) - B580 and B570 with enhanced AV1
372 arcBattlemage := []string{
373 "arc b570", "arc b580", // Officially announced December 2024
374 // Future Battlemage models expected
375 "arc b750", "arc b770",
376 }
377
378 // Intel Xe integrated graphics with AV1 support (12th gen+)
379 // Note: Integrated graphics may have limited AV1 encode capabilities compared to discrete
380 xeIntegrated := []string{
381 "xe graphics", "iris xe", "uhd graphics xe",
382 // Specific integrated GPU models
383 "xe graphics g7", "iris xe graphics", "uhd graphics xe g4",
384 }
385
386 // CPU generation indicators for AV1-capable integrated graphics
387 // Source: Intel specifications - AV1 support starting with Alder Lake (12th gen)
388 cpuGenerations := []string{
389 // Architecture codenames
390 "alder lake", "raptor lake", "meteor lake", "lunar lake", "arrow lake",
391 // Consumer generation naming
392 "12th gen", "13th gen", "14th gen", "15th gen",
393 // Core series with AV1 support
394 "core ultra", // New branding for newer processors
395 }
396
397 // Check discrete Arc GPUs (full AV1 encode/decode support)
398 allArcModels := append(arcAlchemist, arcBattlemage...)
399 for _, gpu := range allArcModels {
400 if strings.Contains(name, gpu) {
401 return true
402 }
403 }
404
405 // Check integrated Xe graphics with generation validation
406 for _, xe := range xeIntegrated {
407 if strings.Contains(name, xe) {
408 // Verify it's from a generation that supports AV1 (12th gen+)
409 for _, gen := range cpuGenerations {
410 if strings.Contains(name, gen) {
411 return true
412 }
413 }
414 // If we find Xe but can't determine generation, assume newer (likely supports AV1)
415 return true
416 }
417 }
418
419 // Check by CPU generation alone (may have Xe integrated graphics)
420 for _, gen := range cpuGenerations {
421 if strings.Contains(name, gen) {
422 return true
423 }
424 }
425
426 return false
427}
428
429// GetAV1HardwareCapabilities returns detailed AV1 capabilities for the GPU
430func (g GPUInfo) GetAV1HardwareCapabilities() map[string]bool {
431 capabilities := map[string]bool{
432 "decode": false,
433 "encode": false,
434 "10bit": false, // 10-bit AV1 support
435 "hdr": false, // HDR support
436 }
437
438 if !g.SupportsAV1Hardware() {
439 return capabilities
440 }
441
442 name := strings.ToLower(g.Name)
443
444 switch g.Vendor {
445 case GPUVendorAMD:
446 // All RDNA2+ GPUs support AV1 decode, RDNA3+ adds encode
447 capabilities["decode"] = true
448 capabilities["10bit"] = true
449
450 // RDNA3 (RX 7000) has VCN 4.0 with AV1 encode support
451 // RDNA4 (RX 9000) adds B-frame encoding for better compression
452 if strings.Contains(name, "rx 7") || strings.Contains(name, "rx 9") {
453 capabilities["encode"] = true
454 capabilities["hdr"] = true
455 }
456
457 case GPUVendorNVIDIA:
458 // RTX 30 series: AV1 decode only (8K HDR capable)
459 if strings.Contains(name, "rtx 3") {
460 capabilities["decode"] = true
461 capabilities["10bit"] = true
462 capabilities["hdr"] = true
463 }
464 // RTX 40 series: Full AV1 encode/decode with 8th gen NVENC
465 if strings.Contains(name, "rtx 4") {
466 capabilities["decode"] = true
467 capabilities["encode"] = true
468 capabilities["10bit"] = true
469 capabilities["hdr"] = true
470 }
471
472 case GPUVendorIntel:
473 // Intel Arc discrete GPUs: Full AV1 encode/decode (industry first)
474 // Xe integrated: AV1 decode, limited encode capabilities
475 capabilities["decode"] = true
476 capabilities["10bit"] = true
477
478 // Arc discrete GPUs have full AV1 encode support
479 if strings.Contains(name, "arc a") || strings.Contains(name, "arc b") {
480 capabilities["encode"] = true
481 capabilities["hdr"] = true
482 }
483 // Xe integrated may have basic encode on newer generations
484 if strings.Contains(name, "xe graphics") || strings.Contains(name, "iris xe") {
485 // Conservative: assume decode-only for integrated unless proven otherwise
486 capabilities["hdr"] = true
487 }
488 }
489
490 return capabilities
491}