1package cef
2
3import (
4 "context"
5 "os"
6 "strings"
7
8 "github.com/bnema/dumber/internal/application/port"
9)
10
11const (
12 dumberRenderStackEnvVar = "DUMBER_RENDER_STACK"
13 dumberRenderStackAllowSplitEnvVar = "DUMBER_RENDER_STACK_ALLOW_SPLIT"
14
15 cefEngineType = "cef"
16 renderStackAuto = "auto"
17 renderStackVulkanDMABUF = "vulkan-dmabuf"
18 renderStackLegacyGL = "legacy-gl"
19 cef2gtkBackendEnvVar = "PUREGO_CEF2GTK_BACKEND"
20 cef2gtkAngleBackendVar = "PUREGO_CEF2GTK_ANGLE_BACKEND"
21 cef2gtkOSRBackingScaleEnvVar = "PUREGO_CEF2GTK_OSR_BACKING_SCALE"
22 gskRendererEnvVar = "GSK_RENDERER"
23 cef2gtkBackendGDKDMABUF = "gdk-dmabuf"
24 cef2gtkBackendGLArea = "glarea"
25 cef2gtkAngleVulkan = "vulkan"
26 cef2gtkAngleGLEGL = "gl-egl"
27 gskRendererVulkan = "vulkan"
28 gskRendererOpenGL = "opengl"
29)
30
31// ApplyDefaultRenderStackEnvironment configures the process environment for
32// Dumber's default CEF presentation stack before GTK/libadwaita initialize.
33//
34// DUMBER_RENDER_STACK accepts:
35// - empty/default or vulkan-dmabuf: use GDK DMABUF presentation with GSK Vulkan and CEF ANGLE Vulkan
36// - auto: alias for vulkan-dmabuf
37// - legacy-gl: force the older GtkGLArea/OpenGL bridge for diagnostics
38//
39// Low-level env vars are treated as library-development escape hatches only.
40// Dumber keeps the selected stack coherent by default and overwrites conflicts;
41// set DUMBER_RENDER_STACK_ALLOW_SPLIT=1 to preserve explicit split-stack values.
42func ApplyDefaultRenderStackEnvironment(logger port.Logger) string {
43 return applyDefaultRenderStackEnvironment(logger)
44}
45
46func applyDefaultRenderStackEnvironment(logger port.Logger) string {
47 stack := normalizeRenderStack(logger, os.Getenv(dumberRenderStackEnvVar))
48 switch stack {
49 case renderStackLegacyGL:
50 setRenderStackEnv(logger, gskRendererEnvVar, gskRendererOpenGL)
51 setRenderStackEnv(logger, cef2gtkBackendEnvVar, cef2gtkBackendGLArea)
52 setRenderStackEnv(logger, cef2gtkAngleBackendVar, cef2gtkAngleGLEGL)
53 default:
54 stack = renderStackVulkanDMABUF
55 setRenderStackEnv(logger, gskRendererEnvVar, gskRendererVulkan)
56 setRenderStackEnv(logger, cef2gtkBackendEnvVar, cef2gtkBackendGDKDMABUF)
57 setRenderStackEnv(logger, cef2gtkAngleBackendVar, cef2gtkAngleVulkan)
58 setEnvDefault(logger, cef2gtkOSRBackingScaleEnvVar, "auto")
59 }
60 logRenderStackEnvironment(logger, stack)
61 return stack
62}
63
64func logRenderStackEnvironment(logger port.Logger, stack string) {
65 logInfo(logger, "cef: render stack environment configured",
66 port.Field("render_stack", stack),
67 port.Field("gsk_renderer", os.Getenv(gskRendererEnvVar)),
68 port.Field("cef2gtk_backend", os.Getenv(cef2gtkBackendEnvVar)),
69 port.Field("cef2gtk_angle_backend", os.Getenv(cef2gtkAngleBackendVar)),
70 port.Field("cef2gtk_osr_backing_scale", os.Getenv(cef2gtkOSRBackingScaleEnvVar)),
71 port.Field("split_stack_allowed", envBoolEnabled(dumberRenderStackAllowSplitEnvVar)),
72 )
73}
74
75// HardwareDecodeEnvironmentOptions carries config-derived CEF environment
76// decisions from the composition root without making the CEF adapter import
77// sibling configuration or environment adapters directly.
78type HardwareDecodeEnvironmentOptions struct {
79 EngineType string
80 HardwareDecodingDisabled bool
81 RenderingEnvManager port.RenderingEnvManager
82 Logger port.Logger
83}
84
85// ApplyDefaultHardwareDecodeEnvironment maps Dumber's media config to CEF's
86// developer-facing VAAPI switch env var before CEF command-line callbacks run.
87// Existing explicit env values are preserved as low-level escape hatches.
88func ApplyDefaultHardwareDecodeEnvironment(ctx context.Context, opts HardwareDecodeEnvironmentOptions) {
89 if opts.EngineType != cefEngineType {
90 return
91 }
92 if opts.HardwareDecodingDisabled {
93 setEnvDefault(opts.Logger, cefEnableVAAPIEnvVar, "0")
94 return
95 }
96 setEnvDefault(opts.Logger, cefEnableVAAPIEnvVar, "1")
97 applyDefaultLIBVADriverEnvironment(ctx, opts.RenderingEnvManager, opts.Logger)
98}
99
100func applyDefaultLIBVADriverEnvironment(ctx context.Context, manager port.RenderingEnvManager, logger port.Logger) {
101 if strings.TrimSpace(os.Getenv("LIBVA_DRIVER_NAME")) != "" || manager == nil {
102 return
103 }
104 switch manager.DetectGPUVendor(ctx) {
105 case port.GPUVendorAMD:
106 setEnvDefault(logger, "LIBVA_DRIVER_NAME", "radeonsi")
107 case port.GPUVendorIntel:
108 setEnvDefault(logger, "LIBVA_DRIVER_NAME", "iHD")
109 case port.GPUVendorNVIDIA:
110 logWarn(logger, "cef: not defaulting LIBVA_DRIVER_NAME for NVIDIA; Chromium/CEF VAAPI support is driver-dependent")
111 }
112}
113
114func normalizeRenderStack(logger port.Logger, value string) string {
115 normalized := strings.ToLower(strings.TrimSpace(value))
116 switch normalized {
117 case "", renderStackAuto, renderStackVulkanDMABUF:
118 return renderStackVulkanDMABUF
119 case renderStackLegacyGL:
120 return renderStackLegacyGL
121 default:
122 logWarn(logger, "cef: unknown render stack, falling back to default",
123 port.Field("render_stack", value),
124 port.Field("fallback", renderStackVulkanDMABUF),
125 )
126 return renderStackVulkanDMABUF
127 }
128}
129
130func setRenderStackEnv(logger port.Logger, key, value string) {
131 if existing := strings.TrimSpace(os.Getenv(key)); existing != "" {
132 fields := []port.LogField{
133 port.Field("key", key),
134 port.Field("value", existing),
135 port.Field("stack_value", value),
136 }
137 if envBoolEnabled(dumberRenderStackAllowSplitEnvVar) {
138 logWarn(logger, "cef: preserving explicit split render stack environment override", fields...)
139 return
140 }
141 logWarn(logger, "cef: overriding low-level render stack environment to keep Dumber stack coherent", fields...)
142 }
143 _ = os.Setenv(key, value)
144}
145
146func setEnvDefault(logger port.Logger, key, value string) {
147 if existing := strings.TrimSpace(os.Getenv(key)); existing != "" {
148 logDebug(logger, "cef: preserving explicit environment override",
149 port.Field("key", key),
150 port.Field("value", existing),
151 )
152 return
153 }
154 _ = os.Setenv(key, value)
155}
156
157func logDebug(logger port.Logger, msg string, fields ...port.LogField) {
158 if logger != nil {
159 logger.Debug(msg, fields...)
160 }
161}
162
163func logInfo(logger port.Logger, msg string, fields ...port.LogField) {
164 if logger != nil {
165 logger.Info(msg, fields...)
166 }
167}
168
169func logWarn(logger port.Logger, msg string, fields ...port.LogField) {
170 if logger != nil {
171 logger.Warn(msg, fields...)
172 }
173}