v0.11.0 dc50343

detect.go RawHistoryFolder

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}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close