v0.20.0 c741a39

manager.go RawHistoryFolder

1// Package env provides environment variable management for rendering subsystems.
2package env
3
4import (
5	"context"
6	"fmt"
7	"os"
8	"path/filepath"
9	"strings"
10
11	"github.com/bnema/dumber/internal/application/port"
12	"github.com/bnema/dumber/internal/logging"
13)
14
15// Manager implements port.RenderingEnvManager for configuring
16// environment variables for GStreamer, WebKit, and GTK/GSK.
17type Manager struct {
18	appliedVars map[string]string
19	gpuVendor   port.GPUVendor
20}
21
22// NewManager creates a new environment manager.
23func NewManager() *Manager {
24	return &Manager{
25		appliedVars: make(map[string]string),
26		gpuVendor:   port.GPUVendorUnknown,
27	}
28}
29
30// DetectGPUVendor identifies the primary GPU vendor from system info.
31func (m *Manager) DetectGPUVendor(ctx context.Context) port.GPUVendor {
32	log := logging.FromContext(ctx)
33
34	vendor := m.detectFromDRM()
35	if vendor != port.GPUVendorUnknown {
36		m.gpuVendor = vendor
37		log.Debug().Str("vendor", string(vendor)).Msg("detected GPU vendor from DRM")
38		return vendor
39	}
40
41	log.Debug().Msg("could not detect GPU vendor, using unknown")
42	m.gpuVendor = port.GPUVendorUnknown
43	return port.GPUVendorUnknown
44}
45
46// ApplyEnvironment sets all rendering environment variables.
47// Must be called before GTK/WebKit initialization.
48func (m *Manager) ApplyEnvironment(ctx context.Context, settings port.RenderingEnvSettings) error {
49	log := logging.FromContext(ctx)
50	m.appliedVars = make(map[string]string)
51
52	m.applyGStreamerEnv(settings)
53	m.applyGPUSpecificEnv()
54	m.applyWebKitEnv(settings)
55	m.applyGTKEnv(settings)
56	m.applyDebugEnv(settings)
57
58	log.Debug().
59		Interface("vars", m.appliedVars).
60		Str("gpu", string(m.gpuVendor)).
61		Msg("applied rendering environment")
62
63	return nil
64}
65
66func (m *Manager) applyGStreamerEnv(settings port.RenderingEnvSettings) {
67	// VSync
68	if settings.ForceVSync {
69		m.setEnv("__GL_SYNC_TO_VBLANK", "1")
70		m.setEnv("vblank_mode", "3")
71	}
72
73	// GL API
74	switch strings.ToLower(settings.GLRenderingMode) {
75	case "gles2":
76		m.setEnv("GST_GL_API", "gles2")
77	case "gl3":
78		m.setEnv("GST_GL_API", "opengl3")
79	case "none":
80		m.setEnv("GST_GL_API", "none")
81	case "auto", "":
82		// Don't set - let GStreamer decide
83	}
84
85	// GStreamer debug level
86	if settings.GStreamerDebugLevel > 0 {
87		level := settings.GStreamerDebugLevel
88		if level > 5 {
89			level = 5
90		}
91		m.setEnv("GST_DEBUG", fmt.Sprintf("%d", level))
92	}
93
94	// Video buffer size
95	if settings.VideoBufferSizeMB > 0 {
96		bufferBytes := settings.VideoBufferSizeMB * 1024 * 1024
97		m.setEnv("GST_BUFFER_SIZE", fmt.Sprintf("%d", bufferBytes))
98		m.setEnv("GST_QUEUE2_MAX_SIZE_BYTES", fmt.Sprintf("%d", bufferBytes))
99	}
100
101	// Queue buffer time
102	if settings.QueueBufferTimeSec > 0 {
103		bufferTimeNs := settings.QueueBufferTimeSec * 1_000_000_000
104		m.setEnv("GST_QUEUE2_MAX_SIZE_TIME", fmt.Sprintf("%d", bufferTimeNs))
105	}
106}
107
108func (m *Manager) applyGPUSpecificEnv() {
109	if os.Getenv("LIBVA_DRIVER_NAME") != "" {
110		return
111	}
112
113	switch m.gpuVendor {
114	case port.GPUVendorAMD:
115		m.setEnv("LIBVA_DRIVER_NAME", "radeonsi")
116	case port.GPUVendorIntel:
117		m.setEnv("LIBVA_DRIVER_NAME", "iHD")
118	case port.GPUVendorNVIDIA:
119		m.setEnv("LIBVA_DRIVER_NAME", "nvidia")
120		if os.Getenv("GST_GL_PLATFORM") == "" {
121			m.setEnv("GST_GL_PLATFORM", "egl")
122		}
123	}
124}
125
126func (m *Manager) applyWebKitEnv(settings port.RenderingEnvSettings) {
127	if settings.DisableDMABufRenderer {
128		m.setEnv("WEBKIT_DISABLE_DMABUF_RENDERER", "1")
129	}
130	if settings.ForceCompositingMode {
131		m.setEnv("WEBKIT_FORCE_COMPOSITING_MODE", "1")
132	}
133	if settings.DisableCompositingMode {
134		m.setEnv("WEBKIT_DISABLE_COMPOSITING_MODE", "1")
135	}
136}
137
138func (m *Manager) applyGTKEnv(settings port.RenderingEnvSettings) {
139	renderer := strings.ToLower(strings.TrimSpace(settings.GSKRenderer))
140	if renderer != "" && renderer != "auto" {
141		m.setEnv("GSK_RENDERER", renderer)
142	}
143
144	var gskDisable []string
145	if settings.DisableMipmaps {
146		gskDisable = append(gskDisable, "mipmap")
147	}
148	if len(gskDisable) > 0 {
149		m.setEnv("GSK_GPU_DISABLE", strings.Join(gskDisable, ","))
150	}
151
152	var gdkDebug []string
153	if settings.PreferGL {
154		gdkDebug = append(gdkDebug, "gl-prefer-gl")
155	}
156	if settings.DebugFrames {
157		gdkDebug = append(gdkDebug, "frames")
158	}
159	if len(gdkDebug) > 0 {
160		m.setEnv("GDK_DEBUG", strings.Join(gdkDebug, ","))
161	}
162}
163
164func (m *Manager) applyDebugEnv(settings port.RenderingEnvSettings) {
165	if settings.ShowFPS {
166		m.setEnv("WEBKIT_SHOW_FPS", "1")
167	}
168	if settings.SampleMemory {
169		m.setEnv("WEBKIT_SAMPLE_MEMORY", "1")
170	}
171}
172
173func (m *Manager) setEnv(key, value string) {
174	_ = os.Setenv(key, value)
175	m.appliedVars[key] = value
176}
177
178// GetAppliedVars returns a copy of applied environment variables.
179func (m *Manager) GetAppliedVars() map[string]string {
180	result := make(map[string]string, len(m.appliedVars))
181	for k, v := range m.appliedVars {
182		result[k] = v
183	}
184	return result
185}
186
187func (*Manager) detectFromDRM() port.GPUVendor {
188	const drmBase = "/sys/class/drm"
189	for _, card := range []string{"card0", "card1"} {
190		vendorPath := filepath.Join(drmBase, card, "device", "vendor")
191		data, err := os.ReadFile(vendorPath)
192		if err != nil {
193			continue
194		}
195
196		vendorID := strings.TrimSpace(string(data))
197		switch vendorID {
198		case "0x1002":
199			return port.GPUVendorAMD
200		case "0x8086":
201			return port.GPUVendorIntel
202		case "0x10de":
203			return port.GPUVendorNVIDIA
204		}
205	}
206	return port.GPUVendorUnknown
207}
208
209var _ port.RenderingEnvManager = (*Manager)(nil)

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