detached f790427

feat(video): add comprehensive VA-API/VDPAU hardware video acceleration with auto GPU detection

commit
f790427e3bf5770977990b8b7316b96a2af144fa
author
bamen · · 2025-09-12 05:23 UTC
parent
866ded8
signature
unsigned
6 files · +354 -2
M internal/config/config.go +50 -1
@@ -11,6 +11,7 @@ import (
1111 "sync"
1212 "time"
1313
14+ "github.com/bnema/dumber/pkg/gpu"
1415 "github.com/fsnotify/fsnotify"
1516 "github.com/spf13/viper"
1617 )
@@ -28,7 +29,8 @@ type Config struct {
2829 SearchShortcuts map[string]SearchShortcut `mapstructure:"search_shortcuts" yaml:"search_shortcuts"`
2930 Dmenu DmenuConfig `mapstructure:"dmenu" yaml:"dmenu"`
3031 Logging LoggingConfig `mapstructure:"logging" yaml:"logging"`
31- Appearance AppearanceConfig `mapstructure:"appearance" yaml:"appearance"`
32+ Appearance AppearanceConfig `mapstructure:"appearance" yaml:"appearance"`
33+ VideoAcceleration VideoAccelerationConfig `mapstructure:"video_acceleration" yaml:"video_acceleration"`
3234 // RenderingMode controls GPU/CPU rendering selection for WebKit
3335 RenderingMode RenderingMode `mapstructure:"rendering_mode" yaml:"rendering_mode"`
3436 }
@@ -109,6 +111,15 @@ type AppearanceConfig struct {
109111 DefaultFontSize int `mapstructure:"default_font_size" yaml:"default_font_size"`
110112 }
111113
114+// VideoAccelerationConfig holds video hardware acceleration preferences.
115+type VideoAccelerationConfig struct {
116+ EnableVAAPI bool `mapstructure:"enable_vaapi" yaml:"enable_vaapi"`
117+ AutoDetectGPU bool `mapstructure:"auto_detect_gpu" yaml:"auto_detect_gpu"`
118+ VAAPIDriverName string `mapstructure:"vaapi_driver_name" yaml:"vaapi_driver_name"`
119+ EnableAllDrivers bool `mapstructure:"enable_all_drivers" yaml:"enable_all_drivers"`
120+ LegacyVAAPI bool `mapstructure:"legacy_vaapi" yaml:"legacy_vaapi"`
121+}
122+
112123 // Manager handles configuration loading, watching, and reloading.
113124 type Manager struct {
114125 config *Config
@@ -178,6 +189,21 @@ func NewManager() (*Manager, error) {
178189 return nil, fmt.Errorf("failed to bind DUMBER_RENDERING_MODE: %w", err)
179190 }
180191
192+ // Video acceleration environment variable bindings
193+ videoAccelEnvBindings := map[string]string{
194+ "video_acceleration.enable_vaapi": "DUMBER_VIDEO_ACCELERATION_ENABLE",
195+ "video_acceleration.auto_detect_gpu": "DUMBER_VIDEO_AUTO_DETECT",
196+ "video_acceleration.vaapi_driver_name": "LIBVA_DRIVER_NAME",
197+ "video_acceleration.enable_all_drivers": "GST_VAAPI_ALL_DRIVERS",
198+ "video_acceleration.legacy_vaapi": "WEBKIT_GST_ENABLE_LEGACY_VAAPI",
199+ }
200+
201+ for key, env := range videoAccelEnvBindings {
202+ if err := v.BindEnv(key, env); err != nil {
203+ return nil, fmt.Errorf("failed to bind environment variable %s: %w", env, err)
204+ }
205+ }
206+
181207 return &Manager{
182208 viper: v,
183209 callbacks: make([]func(*Config), 0),
@@ -237,6 +263,14 @@ func (m *Manager) Load() error {
237263 config.RenderingMode = RenderingModeAuto
238264 }
239265
266+ // Auto-detect GPU if enabled and driver name is not set
267+ if config.VideoAcceleration.AutoDetectGPU && config.VideoAcceleration.VAAPIDriverName == "" {
268+ gpuInfo := gpu.DetectGPU()
269+ if gpuInfo.SupportsVAAPI() {
270+ config.VideoAcceleration.VAAPIDriverName = gpuInfo.GetVAAPIDriverName()
271+ }
272+ }
273+
240274 m.config = config
241275 return nil
242276 }
@@ -324,6 +358,14 @@ func (m *Manager) reload() error {
324358 config.RenderingMode = RenderingModeAuto
325359 }
326360
361+ // Auto-detect GPU if enabled and driver name is not set
362+ if config.VideoAcceleration.AutoDetectGPU && config.VideoAcceleration.VAAPIDriverName == "" {
363+ gpuInfo := gpu.DetectGPU()
364+ if gpuInfo.SupportsVAAPI() {
365+ config.VideoAcceleration.VAAPIDriverName = gpuInfo.GetVAAPIDriverName()
366+ }
367+ }
368+
327369 m.config = config
328370 return nil
329371 }
@@ -379,6 +421,13 @@ func (m *Manager) setDefaults() {
379421 m.viper.SetDefault("appearance.monospace_font", defaults.Appearance.MonospaceFont)
380422 m.viper.SetDefault("appearance.default_font_size", defaults.Appearance.DefaultFontSize)
381423
424+ // Video acceleration defaults
425+ m.viper.SetDefault("video_acceleration.enable_vaapi", defaults.VideoAcceleration.EnableVAAPI)
426+ m.viper.SetDefault("video_acceleration.auto_detect_gpu", defaults.VideoAcceleration.AutoDetectGPU)
427+ m.viper.SetDefault("video_acceleration.vaapi_driver_name", defaults.VideoAcceleration.VAAPIDriverName)
428+ m.viper.SetDefault("video_acceleration.enable_all_drivers", defaults.VideoAcceleration.EnableAllDrivers)
429+ m.viper.SetDefault("video_acceleration.legacy_vaapi", defaults.VideoAcceleration.LegacyVAAPI)
430+
382431 // Rendering defaults
383432 m.viper.SetDefault("rendering_mode", string(RenderingModeAuto))
384433 }
@@ -11,6 +11,7 @@ import (
11 "sync"11 "sync"
12 "time"12 "time"
13 13
14+ "github.com/bnema/dumber/pkg/gpu"
14 "github.com/fsnotify/fsnotify"15 "github.com/fsnotify/fsnotify"
15 "github.com/spf13/viper"16 "github.com/spf13/viper"
16 )17 )
@@ -28,7 +29,8 @@ type Config struct {
28 SearchShortcuts map[string]SearchShortcut `mapstructure:"search_shortcuts" yaml:"search_shortcuts"`29 SearchShortcuts map[string]SearchShortcut `mapstructure:"search_shortcuts" yaml:"search_shortcuts"`
29 Dmenu DmenuConfig `mapstructure:"dmenu" yaml:"dmenu"`30 Dmenu DmenuConfig `mapstructure:"dmenu" yaml:"dmenu"`
30 Logging LoggingConfig `mapstructure:"logging" yaml:"logging"`31 Logging LoggingConfig `mapstructure:"logging" yaml:"logging"`
31- Appearance AppearanceConfig `mapstructure:"appearance" yaml:"appearance"`32+ Appearance AppearanceConfig `mapstructure:"appearance" yaml:"appearance"`
33+ VideoAcceleration VideoAccelerationConfig `mapstructure:"video_acceleration" yaml:"video_acceleration"`
32 // RenderingMode controls GPU/CPU rendering selection for WebKit34 // RenderingMode controls GPU/CPU rendering selection for WebKit
33 RenderingMode RenderingMode `mapstructure:"rendering_mode" yaml:"rendering_mode"`35 RenderingMode RenderingMode `mapstructure:"rendering_mode" yaml:"rendering_mode"`
34 }36 }
@@ -109,6 +111,15 @@ type AppearanceConfig struct {
109 DefaultFontSize int `mapstructure:"default_font_size" yaml:"default_font_size"`111 DefaultFontSize int `mapstructure:"default_font_size" yaml:"default_font_size"`
110 }112 }
111 113
114+// VideoAccelerationConfig holds video hardware acceleration preferences.
115+type VideoAccelerationConfig struct {
116+ EnableVAAPI bool `mapstructure:"enable_vaapi" yaml:"enable_vaapi"`
117+ AutoDetectGPU bool `mapstructure:"auto_detect_gpu" yaml:"auto_detect_gpu"`
118+ VAAPIDriverName string `mapstructure:"vaapi_driver_name" yaml:"vaapi_driver_name"`
119+ EnableAllDrivers bool `mapstructure:"enable_all_drivers" yaml:"enable_all_drivers"`
120+ LegacyVAAPI bool `mapstructure:"legacy_vaapi" yaml:"legacy_vaapi"`
121+}
122+
112 // Manager handles configuration loading, watching, and reloading.123 // Manager handles configuration loading, watching, and reloading.
113 type Manager struct {124 type Manager struct {
114 config *Config125 config *Config
@@ -178,6 +189,21 @@ func NewManager() (*Manager, error) {
178 return nil, fmt.Errorf("failed to bind DUMBER_RENDERING_MODE: %w", err)189 return nil, fmt.Errorf("failed to bind DUMBER_RENDERING_MODE: %w", err)
179 }190 }
180 191
192+ // Video acceleration environment variable bindings
193+ videoAccelEnvBindings := map[string]string{
194+ "video_acceleration.enable_vaapi": "DUMBER_VIDEO_ACCELERATION_ENABLE",
195+ "video_acceleration.auto_detect_gpu": "DUMBER_VIDEO_AUTO_DETECT",
196+ "video_acceleration.vaapi_driver_name": "LIBVA_DRIVER_NAME",
197+ "video_acceleration.enable_all_drivers": "GST_VAAPI_ALL_DRIVERS",
198+ "video_acceleration.legacy_vaapi": "WEBKIT_GST_ENABLE_LEGACY_VAAPI",
199+ }
200+
201+ for key, env := range videoAccelEnvBindings {
202+ if err := v.BindEnv(key, env); err != nil {
203+ return nil, fmt.Errorf("failed to bind environment variable %s: %w", env, err)
204+ }
205+ }
206+
181 return &Manager{207 return &Manager{
182 viper: v,208 viper: v,
183 callbacks: make([]func(*Config), 0),209 callbacks: make([]func(*Config), 0),
@@ -237,6 +263,14 @@ func (m *Manager) Load() error {
237 config.RenderingMode = RenderingModeAuto263 config.RenderingMode = RenderingModeAuto
238 }264 }
239 265
266+ // Auto-detect GPU if enabled and driver name is not set
267+ if config.VideoAcceleration.AutoDetectGPU && config.VideoAcceleration.VAAPIDriverName == "" {
268+ gpuInfo := gpu.DetectGPU()
269+ if gpuInfo.SupportsVAAPI() {
270+ config.VideoAcceleration.VAAPIDriverName = gpuInfo.GetVAAPIDriverName()
271+ }
272+ }
273+
240 m.config = config274 m.config = config
241 return nil275 return nil
242 }276 }
@@ -324,6 +358,14 @@ func (m *Manager) reload() error {
324 config.RenderingMode = RenderingModeAuto358 config.RenderingMode = RenderingModeAuto
325 }359 }
326 360
361+ // Auto-detect GPU if enabled and driver name is not set
362+ if config.VideoAcceleration.AutoDetectGPU && config.VideoAcceleration.VAAPIDriverName == "" {
363+ gpuInfo := gpu.DetectGPU()
364+ if gpuInfo.SupportsVAAPI() {
365+ config.VideoAcceleration.VAAPIDriverName = gpuInfo.GetVAAPIDriverName()
366+ }
367+ }
368+
327 m.config = config369 m.config = config
328 return nil370 return nil
329 }371 }
@@ -379,6 +421,13 @@ func (m *Manager) setDefaults() {
379 m.viper.SetDefault("appearance.monospace_font", defaults.Appearance.MonospaceFont)421 m.viper.SetDefault("appearance.monospace_font", defaults.Appearance.MonospaceFont)
380 m.viper.SetDefault("appearance.default_font_size", defaults.Appearance.DefaultFontSize)422 m.viper.SetDefault("appearance.default_font_size", defaults.Appearance.DefaultFontSize)
381 423
424+ // Video acceleration defaults
425+ m.viper.SetDefault("video_acceleration.enable_vaapi", defaults.VideoAcceleration.EnableVAAPI)
426+ m.viper.SetDefault("video_acceleration.auto_detect_gpu", defaults.VideoAcceleration.AutoDetectGPU)
427+ m.viper.SetDefault("video_acceleration.vaapi_driver_name", defaults.VideoAcceleration.VAAPIDriverName)
428+ m.viper.SetDefault("video_acceleration.enable_all_drivers", defaults.VideoAcceleration.EnableAllDrivers)
429+ m.viper.SetDefault("video_acceleration.legacy_vaapi", defaults.VideoAcceleration.LegacyVAAPI)
430+
382 // Rendering defaults431 // Rendering defaults
383 m.viper.SetDefault("rendering_mode", string(RenderingModeAuto))432 m.viper.SetDefault("rendering_mode", string(RenderingModeAuto))
384 }433 }
M internal/config/defaults.go +7 -0
@@ -99,6 +99,13 @@ func DefaultConfig() *Config {
9999 MonospaceFont: "Fira Code",
100100 DefaultFontSize: defaultFontSize,
101101 },
102+ VideoAcceleration: VideoAccelerationConfig{
103+ EnableVAAPI: true,
104+ AutoDetectGPU: true,
105+ VAAPIDriverName: "", // Will be auto-detected
106+ EnableAllDrivers: true,
107+ LegacyVAAPI: false,
108+ },
102109 RenderingMode: RenderingModeAuto,
103110 }
104111 }
@@ -99,6 +99,13 @@ func DefaultConfig() *Config {
99 MonospaceFont: "Fira Code",99 MonospaceFont: "Fira Code",
100 DefaultFontSize: defaultFontSize,100 DefaultFontSize: defaultFontSize,
101 },101 },
102+ VideoAcceleration: VideoAccelerationConfig{
103+ EnableVAAPI: true,
104+ AutoDetectGPU: true,
105+ VAAPIDriverName: "", // Will be auto-detected
106+ EnableAllDrivers: true,
107+ LegacyVAAPI: false,
108+ },
102 RenderingMode: RenderingModeAuto,109 RenderingMode: RenderingModeAuto,
103 }110 }
104 }111 }
M main.go +8 -1
@@ -437,7 +437,14 @@ func runBrowser() {
437437 DefaultMonospaceFont: cfg.Appearance.MonospaceFont,
438438 DefaultFontSize: cfg.Appearance.DefaultFontSize,
439439 Rendering: webkit.RenderingConfig{Mode: string(cfg.RenderingMode)},
440- Memory: readMemoryConfigFromEnv(),
440+ VideoAcceleration: webkit.VideoAccelerationConfig{
441+ EnableVAAPI: cfg.VideoAcceleration.EnableVAAPI,
442+ AutoDetectGPU: cfg.VideoAcceleration.AutoDetectGPU,
443+ VAAPIDriverName: cfg.VideoAcceleration.VAAPIDriverName,
444+ EnableAllDrivers: cfg.VideoAcceleration.EnableAllDrivers,
445+ LegacyVAAPI: cfg.VideoAcceleration.LegacyVAAPI,
446+ },
447+ Memory: readMemoryConfigFromEnv(),
441448 })
442449 if err != nil {
443450 log.Printf("Warning: failed to create WebView: %v", err)
@@ -437,7 +437,14 @@ func runBrowser() {
437 DefaultMonospaceFont: cfg.Appearance.MonospaceFont,437 DefaultMonospaceFont: cfg.Appearance.MonospaceFont,
438 DefaultFontSize: cfg.Appearance.DefaultFontSize,438 DefaultFontSize: cfg.Appearance.DefaultFontSize,
439 Rendering: webkit.RenderingConfig{Mode: string(cfg.RenderingMode)},439 Rendering: webkit.RenderingConfig{Mode: string(cfg.RenderingMode)},
440- Memory: readMemoryConfigFromEnv(),440+ VideoAcceleration: webkit.VideoAccelerationConfig{
441+ EnableVAAPI: cfg.VideoAcceleration.EnableVAAPI,
442+ AutoDetectGPU: cfg.VideoAcceleration.AutoDetectGPU,
443+ VAAPIDriverName: cfg.VideoAcceleration.VAAPIDriverName,
444+ EnableAllDrivers: cfg.VideoAcceleration.EnableAllDrivers,
445+ LegacyVAAPI: cfg.VideoAcceleration.LegacyVAAPI,
446+ },
447+ Memory: readMemoryConfigFromEnv(),
441 })448 })
442 if err != nil {449 if err != nil {
443 log.Printf("Warning: failed to create WebView: %v", err)450 log.Printf("Warning: failed to create WebView: %v", err)
A pkg/gpu/detect.go +233 -0
@@ -0,0 +1,233 @@
1+package gpu
2+
3+import (
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
14+type GPUVendor string
15+
16+const (
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
24+type 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
31+func (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
39+func 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
62+func 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
108+func 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
147+func 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
184+func 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
195+func 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
213+func (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
231+func (g GPUInfo) SupportsVAAPI() bool {
232+ return g.Vendor == GPUVendorAMD || g.Vendor == GPUVendorIntel || g.Vendor == GPUVendorNVIDIA
233+}⏎̸
@@ -0,0 +1,233 @@
1+package gpu
2+
3+import (
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
14+type GPUVendor string
15+
16+const (
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
24+type 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
31+func (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
39+func 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
62+func 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
108+func 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
147+func 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
184+func 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
195+func 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
213+func (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
231+func (g GPUInfo) SupportsVAAPI() bool {
232+ return g.Vendor == GPUVendorAMD || g.Vendor == GPUVendorIntel || g.Vendor == GPUVendorNVIDIA
233+}⏎̸
M pkg/webkit/settings.go +12 -0
@@ -29,6 +29,9 @@ type Config struct {
2929 // Rendering controls GPU/CPU selection and debug options
3030 Rendering RenderingConfig
3131
32+ // VideoAcceleration controls video hardware acceleration settings
33+ VideoAcceleration VideoAccelerationConfig
34+
3235 // Memory controls memory optimization settings
3336 Memory MemoryConfig
3437 }
@@ -41,6 +44,15 @@ type RenderingConfig struct {
4144 DebugGPU bool
4245 }
4346
47+// VideoAccelerationConfig controls video hardware acceleration settings.
48+type VideoAccelerationConfig struct {
49+ EnableVAAPI bool
50+ AutoDetectGPU bool
51+ VAAPIDriverName string
52+ EnableAllDrivers bool
53+ LegacyVAAPI bool
54+}
55+
4456 // CacheModel represents WebKit cache model options for memory optimization.
4557 type CacheModel string
4658
@@ -29,6 +29,9 @@ type Config struct {
29 // Rendering controls GPU/CPU selection and debug options29 // Rendering controls GPU/CPU selection and debug options
30 Rendering RenderingConfig30 Rendering RenderingConfig
31 31
32+ // VideoAcceleration controls video hardware acceleration settings
33+ VideoAcceleration VideoAccelerationConfig
34+
32 // Memory controls memory optimization settings35 // Memory controls memory optimization settings
33 Memory MemoryConfig36 Memory MemoryConfig
34 }37 }
@@ -41,6 +44,15 @@ type RenderingConfig struct {
41 DebugGPU bool44 DebugGPU bool
42 }45 }
43 46
47+// VideoAccelerationConfig controls video hardware acceleration settings.
48+type VideoAccelerationConfig struct {
49+ EnableVAAPI bool
50+ AutoDetectGPU bool
51+ VAAPIDriverName string
52+ EnableAllDrivers bool
53+ LegacyVAAPI bool
54+}
55+
44 // CacheModel represents WebKit cache model options for memory optimization.56 // CacheModel represents WebKit cache model options for memory optimization.
45 type CacheModel string57 type CacheModel string
46 58
M pkg/webkit/webview_cgo.go +44 -0
@@ -50,6 +50,35 @@ static void on_tls_dialog_response(GtkDialog *dialog, gint response_id, gpointer
5050 static gboolean show_tls_warning_dialog_sync(GtkWindow *parent, const char* hostname, const char* error_msg);
5151 static char* extract_certificate_info(GTlsCertificate* certificate);
5252
53+// Setup video acceleration environment variables
54+static void setup_video_acceleration(const char* driver_name, int enable_all, int legacy) {
55+ if (legacy) {
56+ // Legacy mode: force old VA-API plugins, disable modern ones
57+ setenv("WEBKIT_GST_ENABLE_LEGACY_VAAPI", "1", 1);
58+ // Don't set other env vars in legacy mode to avoid conflicts
59+ return;
60+ }
61+
62+ // Modern mode (default): use improved VA-API handling
63+ if (driver_name && strlen(driver_name) > 0) {
64+ if (strcmp(driver_name, "nvidia") == 0) {
65+ // NVIDIA uses different env vars for hardware video acceleration
66+ setenv("VDPAU_DRIVER", "nvidia", 1);
67+ setenv("LIBVA_DRIVER_NAME", "vdpau", 1);
68+ } else if (strcmp(driver_name, "vdpau") == 0) {
69+ // Explicit VDPAU backend (for NVIDIA via VA-API)
70+ setenv("LIBVA_DRIVER_NAME", "vdpau", 1);
71+ } else {
72+ // AMD/Intel use VA-API directly
73+ setenv("LIBVA_DRIVER_NAME", driver_name, 1);
74+ }
75+ }
76+
77+ if (enable_all) {
78+ setenv("GST_VAAPI_ALL_DRIVERS", "1", 1);
79+ }
80+}
81+
5382 // Construct a WebKitWebView via g_object_new with a fresh UserContentManager.
5483 static GtkWidget* new_webview_with_ucm_and_session(const char* data_dir, const char* cache_dir, const char* cookie_path, WebKitUserContentManager** out_ucm, WebKitMemoryPressureSettings* pressure_settings) {
5584 WebKitUserContentManager* u = webkit_user_content_manager_new();
@@ -601,6 +630,21 @@ func NewWebView(cfg *Config) (*WebView, error) {
601630 cfg = &Config{}
602631 }
603632
633+ // Setup video acceleration environment variables before creating WebView
634+ if cfg.VideoAcceleration.EnableVAAPI {
635+ driverName := C.CString(cfg.VideoAcceleration.VAAPIDriverName)
636+ enableAll := 0
637+ if cfg.VideoAcceleration.EnableAllDrivers {
638+ enableAll = 1
639+ }
640+ legacy := 0
641+ if cfg.VideoAcceleration.LegacyVAAPI {
642+ legacy = 1
643+ }
644+ C.setup_video_acceleration(driverName, C.int(enableAll), C.int(legacy))
645+ C.free(unsafe.Pointer(driverName))
646+ }
647+
604648 // Prepare persistent website data/caches
605649 dataDir := cfg.DataDir
606650 cacheDir := cfg.CacheDir
@@ -50,6 +50,35 @@ static void on_tls_dialog_response(GtkDialog *dialog, gint response_id, gpointer
50 static gboolean show_tls_warning_dialog_sync(GtkWindow *parent, const char* hostname, const char* error_msg);50 static gboolean show_tls_warning_dialog_sync(GtkWindow *parent, const char* hostname, const char* error_msg);
51 static char* extract_certificate_info(GTlsCertificate* certificate);51 static char* extract_certificate_info(GTlsCertificate* certificate);
52 52
53+// Setup video acceleration environment variables
54+static void setup_video_acceleration(const char* driver_name, int enable_all, int legacy) {
55+ if (legacy) {
56+ // Legacy mode: force old VA-API plugins, disable modern ones
57+ setenv("WEBKIT_GST_ENABLE_LEGACY_VAAPI", "1", 1);
58+ // Don't set other env vars in legacy mode to avoid conflicts
59+ return;
60+ }
61+
62+ // Modern mode (default): use improved VA-API handling
63+ if (driver_name && strlen(driver_name) > 0) {
64+ if (strcmp(driver_name, "nvidia") == 0) {
65+ // NVIDIA uses different env vars for hardware video acceleration
66+ setenv("VDPAU_DRIVER", "nvidia", 1);
67+ setenv("LIBVA_DRIVER_NAME", "vdpau", 1);
68+ } else if (strcmp(driver_name, "vdpau") == 0) {
69+ // Explicit VDPAU backend (for NVIDIA via VA-API)
70+ setenv("LIBVA_DRIVER_NAME", "vdpau", 1);
71+ } else {
72+ // AMD/Intel use VA-API directly
73+ setenv("LIBVA_DRIVER_NAME", driver_name, 1);
74+ }
75+ }
76+
77+ if (enable_all) {
78+ setenv("GST_VAAPI_ALL_DRIVERS", "1", 1);
79+ }
80+}
81+
53 // Construct a WebKitWebView via g_object_new with a fresh UserContentManager.82 // Construct a WebKitWebView via g_object_new with a fresh UserContentManager.
54 static GtkWidget* new_webview_with_ucm_and_session(const char* data_dir, const char* cache_dir, const char* cookie_path, WebKitUserContentManager** out_ucm, WebKitMemoryPressureSettings* pressure_settings) {83 static GtkWidget* new_webview_with_ucm_and_session(const char* data_dir, const char* cache_dir, const char* cookie_path, WebKitUserContentManager** out_ucm, WebKitMemoryPressureSettings* pressure_settings) {
55 WebKitUserContentManager* u = webkit_user_content_manager_new();84 WebKitUserContentManager* u = webkit_user_content_manager_new();
@@ -601,6 +630,21 @@ func NewWebView(cfg *Config) (*WebView, error) {
601 cfg = &Config{}630 cfg = &Config{}
602 }631 }
603 632
633+ // Setup video acceleration environment variables before creating WebView
634+ if cfg.VideoAcceleration.EnableVAAPI {
635+ driverName := C.CString(cfg.VideoAcceleration.VAAPIDriverName)
636+ enableAll := 0
637+ if cfg.VideoAcceleration.EnableAllDrivers {
638+ enableAll = 1
639+ }
640+ legacy := 0
641+ if cfg.VideoAcceleration.LegacyVAAPI {
642+ legacy = 1
643+ }
644+ C.setup_video_acceleration(driverName, C.int(enableAll), C.int(legacy))
645+ C.free(unsafe.Pointer(driverName))
646+ }
647+
604 // Prepare persistent website data/caches648 // Prepare persistent website data/caches
605 dataDir := cfg.DataDir649 dataDir := cfg.DataDir
606 cacheDir := cfg.CacheDir650 cacheDir := cfg.CacheDir

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