Merge pull request #256 from bnema/feature/cef-render-stack-config
feat(cef): add render stack config
25 files · +220 -27
M README.md +1 -1
| @@ -101,7 +101,7 @@ CEF_DIR=/custom/path dumber browse | |||
| 101 | 101 | ||
| 102 | 102 | Then set `engine.type = "cef"` in your config. | |
| 103 | 103 | ||
| 104 | - | CEF uses Dumber's GPU-first Wayland render stack by default: GDK DMABUF presentation with ANGLE/GSK Vulkan. You normally do not need render environment variables or helper scripts. For diagnostics only, set `DUMBER_RENDER_STACK=legacy-gl` to use the older GtkGLArea/OpenGL bridge, or `DUMBER_RENDER_STACK=vulkan-dmabuf` to force the default explicitly. | |
| 104 | + | CEF uses Dumber's GPU-first Wayland render stack by default: GDK DMABUF presentation with ANGLE/GSK Vulkan. For driver compatibility, switch to the EGL/OpenGL stack with `engine.cef.render_stack = "egl"`; the default is `"vulkan"`. | |
| 105 | 105 | ||
| 106 | 106 | ## Keyboard-Driven | |
| 107 | 107 | ||
| @@ -101,7 +101,7 @@ CEF_DIR=/custom/path dumber browse | |||||
| 101 | 101 | ||||
| 102 | Then set `engine.type = "cef"` in your config. | 102 | Then set `engine.type = "cef"` in your config. | ||
| 103 | 103 | ||||
| 104 | - | CEF uses Dumber's GPU-first Wayland render stack by default: GDK DMABUF presentation with ANGLE/GSK Vulkan. You normally do not need render environment variables or helper scripts. For diagnostics only, set `DUMBER_RENDER_STACK=legacy-gl` to use the older GtkGLArea/OpenGL bridge, or `DUMBER_RENDER_STACK=vulkan-dmabuf` to force the default explicitly. | 104 | + | CEF uses Dumber's GPU-first Wayland render stack by default: GDK DMABUF presentation with ANGLE/GSK Vulkan. For driver compatibility, switch to the EGL/OpenGL stack with `engine.cef.render_stack = "egl"`; the default is `"vulkan"`. |
| 105 | 105 | ||||
| 106 | ## Keyboard-Driven | 106 | ## Keyboard-Driven | ||
| 107 | 107 | ||||
M cmd/dumber/main.go +1 -1
| @@ -328,7 +328,7 @@ func applyCEFRenderStackDefault(ctx context.Context, cfg *config.Config) { | |||
| 328 | 328 | return | |
| 329 | 329 | } | |
| 330 | 330 | logger := logging.NewPortLogger(ctx) | |
| 331 | - | infracef.ApplyDefaultRenderStackEnvironment(logger) | |
| 331 | + | infracef.ApplyConfiguredRenderStackEnvironment(string(cfg.Engine.CEF.CEFRenderStack()), logger) | |
| 332 | 332 | infracef.ApplyDefaultHardwareDecodeEnvironment(ctx, infracef.HardwareDecodeEnvironmentOptions{ | |
| 333 | 333 | EngineType: cfg.Engine.ResolveEngineType(), | |
| 334 | 334 | HardwareDecodingDisabled: cfg.Media.HardwareDecodingMode == config.HardwareDecodingDisable, | |
| @@ -328,7 +328,7 @@ func applyCEFRenderStackDefault(ctx context.Context, cfg *config.Config) { | |||||
| 328 | return | 328 | return | ||
| 329 | } | 329 | } | ||
| 330 | logger := logging.NewPortLogger(ctx) | 330 | logger := logging.NewPortLogger(ctx) | ||
| 331 | - | infracef.ApplyDefaultRenderStackEnvironment(logger) | 331 | + | infracef.ApplyConfiguredRenderStackEnvironment(string(cfg.Engine.CEF.CEFRenderStack()), logger) |
| 332 | infracef.ApplyDefaultHardwareDecodeEnvironment(ctx, infracef.HardwareDecodeEnvironmentOptions{ | 332 | infracef.ApplyDefaultHardwareDecodeEnvironment(ctx, infracef.HardwareDecodeEnvironmentOptions{ | ||
| 333 | EngineType: cfg.Engine.ResolveEngineType(), | 333 | EngineType: cfg.Engine.ResolveEngineType(), | ||
| 334 | HardwareDecodingDisabled: cfg.Media.HardwareDecodingMode == config.HardwareDecodingDisable, | 334 | HardwareDecodingDisabled: cfg.Media.HardwareDecodingMode == config.HardwareDecodingDisable, | ||
M docs/config/index.md +3 -2
| @@ -189,6 +189,7 @@ border = "#363636" | |||
| 189 | 189 | | `rendering.show_fps` | bool | `false` | - | Show WebKit FPS counter (`WEBKIT_SHOW_FPS`) | | |
| 190 | 190 | | `rendering.sample_memory` | bool | `false` | - | Enable WebKit memory sampling (`WEBKIT_SAMPLE_MEMORY`) | | |
| 191 | 191 | | `rendering.debug_frames` | bool | `false` | - | Enable GTK frame timing debug (`GDK_DEBUG=frames`) | | |
| 192 | + | | `engine.cef.render_stack` | string | `"vulkan"` | `vulkan`, `egl` | CEF GPU render stack | | |
| 192 | 193 | | `default_ui_scale` | float | `1.0` | > 0 | GTK widget UI scale (1.0=100%, 2.0=200%) | | |
| 193 | 194 | | `default_webpage_zoom` | float | `1.2` | > 0 | Default page zoom (1.0=100%, 1.2=120%) | | |
| 194 | 195 | ||
| @@ -691,8 +692,8 @@ DUMBER_DATABASE_PATH=/custom/path/db.sqlite | |||
| 691 | 692 | ||
| 692 | 693 | # Rendering (examples) | |
| 693 | 694 | # CEF defaults to the GPU-first Vulkan DMABUF stack. | |
| 694 | - | # Use legacy-gl only as a diagnostic fallback. | |
| 695 | - | # DUMBER_RENDER_STACK=legacy-gl | |
| 695 | + | # Use EGL/OpenGL for driver compatibility: | |
| 696 | + | # DUMBER_ENGINE_CEF_RENDER_STACK=egl | |
| 696 | 697 | DUMBER_RENDERING_MODE=cpu | |
| 697 | 698 | # WebKit-specific rendering overrides: | |
| 698 | 699 | # DUMBER_RENDERING_DISABLE_DMABUF_RENDERER=true | |
| @@ -189,6 +189,7 @@ border = "#363636" | |||||
| 189 | | `rendering.show_fps` | bool | `false` | - | Show WebKit FPS counter (`WEBKIT_SHOW_FPS`) | | 189 | | `rendering.show_fps` | bool | `false` | - | Show WebKit FPS counter (`WEBKIT_SHOW_FPS`) | | ||
| 190 | | `rendering.sample_memory` | bool | `false` | - | Enable WebKit memory sampling (`WEBKIT_SAMPLE_MEMORY`) | | 190 | | `rendering.sample_memory` | bool | `false` | - | Enable WebKit memory sampling (`WEBKIT_SAMPLE_MEMORY`) | | ||
| 191 | | `rendering.debug_frames` | bool | `false` | - | Enable GTK frame timing debug (`GDK_DEBUG=frames`) | | 191 | | `rendering.debug_frames` | bool | `false` | - | Enable GTK frame timing debug (`GDK_DEBUG=frames`) | | ||
| 192 | + | | `engine.cef.render_stack` | string | `"vulkan"` | `vulkan`, `egl` | CEF GPU render stack | | |||
| 192 | | `default_ui_scale` | float | `1.0` | > 0 | GTK widget UI scale (1.0=100%, 2.0=200%) | | 193 | | `default_ui_scale` | float | `1.0` | > 0 | GTK widget UI scale (1.0=100%, 2.0=200%) | | ||
| 193 | | `default_webpage_zoom` | float | `1.2` | > 0 | Default page zoom (1.0=100%, 1.2=120%) | | 194 | | `default_webpage_zoom` | float | `1.2` | > 0 | Default page zoom (1.0=100%, 1.2=120%) | | ||
| 194 | 195 | ||||
| @@ -691,8 +692,8 @@ DUMBER_DATABASE_PATH=/custom/path/db.sqlite | |||||
| 691 | 692 | ||||
| 692 | # Rendering (examples) | 693 | # Rendering (examples) | ||
| 693 | # CEF defaults to the GPU-first Vulkan DMABUF stack. | 694 | # CEF defaults to the GPU-first Vulkan DMABUF stack. | ||
| 694 | - | # Use legacy-gl only as a diagnostic fallback. | 695 | + | # Use EGL/OpenGL for driver compatibility: |
| 695 | - | # DUMBER_RENDER_STACK=legacy-gl | 696 | + | # DUMBER_ENGINE_CEF_RENDER_STACK=egl |
| 696 | DUMBER_RENDERING_MODE=cpu | 697 | DUMBER_RENDERING_MODE=cpu | ||
| 697 | # WebKit-specific rendering overrides: | 698 | # WebKit-specific rendering overrides: | ||
| 698 | # DUMBER_RENDERING_DISABLE_DMABUF_RENDERER=true | 699 | # DUMBER_RENDERING_DISABLE_DMABUF_RENDERER=true | ||
M docs/reference/configuration.md +1 -1
| @@ -59,7 +59,7 @@ | |||
| 59 | 59 | | `rendering.show_fps` | bool | `false` | | | |
| 60 | 60 | | `rendering.sample_memory` | bool | `false` | | | |
| 61 | 61 | | `rendering.debug_frames` | bool | `false` | | | |
| 62 | - | | `DUMBER_RENDER_STACK` env | string | `vulkan-dmabuf` | `vulkan-dmabuf`, `auto`, `legacy-gl` | | |
| 62 | + | | `engine.cef.render_stack` | string | `vulkan` | `vulkan`, `egl` | | |
| 63 | 63 | | `default_ui_scale` | float | `1.0` | > 0 | | |
| 64 | 64 | | `default_webpage_zoom` | float | `1.2` | > 0 | | |
| 65 | 65 | | `workspace.new_pane_url` | string | `about:blank` | | | |
| @@ -59,7 +59,7 @@ | |||||
| 59 | | `rendering.show_fps` | bool | `false` | | | 59 | | `rendering.show_fps` | bool | `false` | | | ||
| 60 | | `rendering.sample_memory` | bool | `false` | | | 60 | | `rendering.sample_memory` | bool | `false` | | | ||
| 61 | | `rendering.debug_frames` | bool | `false` | | | 61 | | `rendering.debug_frames` | bool | `false` | | | ||
| 62 | - | | `DUMBER_RENDER_STACK` env | string | `vulkan-dmabuf` | `vulkan-dmabuf`, `auto`, `legacy-gl` | | 62 | + | | `engine.cef.render_stack` | string | `vulkan` | `vulkan`, `egl` | |
| 63 | | `default_ui_scale` | float | `1.0` | > 0 | | 63 | | `default_ui_scale` | float | `1.0` | > 0 | | ||
| 64 | | `default_webpage_zoom` | float | `1.2` | > 0 | | 64 | | `default_webpage_zoom` | float | `1.2` | > 0 | | ||
| 65 | | `workspace.new_pane_url` | string | `about:blank` | | | 65 | | `workspace.new_pane_url` | string | `about:blank` | | | ||
M docs/reference/dependencies.md +1 -1
| @@ -73,7 +73,7 @@ sudo pacman -S vulkan-tools # for vulkaninfo | |||
| 73 | 73 | ||
| 74 | 74 | ### CEF Wayland rendering | |
| 75 | 75 | ||
| 76 | - | When `engine.type = "cef"`, Dumber defaults to the GPU-first Wayland stack: GDK DMABUF presentation with ANGLE/GSK Vulkan. No launch wrapper or render environment variables are required for normal use. Set `DUMBER_RENDER_STACK=legacy-gl` only when diagnosing the older GtkGLArea/OpenGL fallback path. | |
| 76 | + | When `engine.type = "cef"`, Dumber defaults to the GPU-first Wayland stack: GDK DMABUF presentation with ANGLE/GSK Vulkan. No launch wrapper or render environment variables are required for normal use. For driver compatibility, set `engine.cef.render_stack = "egl"` (or `DUMBER_ENGINE_CEF_RENDER_STACK=egl`) to use the GtkGLArea/EGL/OpenGL stack. | |
| 77 | 77 | ||
| 78 | 78 | ## Debian/Ubuntu | |
| 79 | 79 | ||
| @@ -73,7 +73,7 @@ sudo pacman -S vulkan-tools # for vulkaninfo | |||||
| 73 | 73 | ||||
| 74 | ### CEF Wayland rendering | 74 | ### CEF Wayland rendering | ||
| 75 | 75 | ||||
| 76 | - | When `engine.type = "cef"`, Dumber defaults to the GPU-first Wayland stack: GDK DMABUF presentation with ANGLE/GSK Vulkan. No launch wrapper or render environment variables are required for normal use. Set `DUMBER_RENDER_STACK=legacy-gl` only when diagnosing the older GtkGLArea/OpenGL fallback path. | 76 | + | When `engine.type = "cef"`, Dumber defaults to the GPU-first Wayland stack: GDK DMABUF presentation with ANGLE/GSK Vulkan. No launch wrapper or render environment variables are required for normal use. For driver compatibility, set `engine.cef.render_stack = "egl"` (or `DUMBER_ENGINE_CEF_RENDER_STACK=egl`) to use the GtkGLArea/EGL/OpenGL stack. |
| 77 | 77 | ||||
| 78 | ## Debian/Ubuntu | 78 | ## Debian/Ubuntu | ||
| 79 | 79 | ||||
M go.mod +1 -1
| @@ -7,7 +7,7 @@ require ( | |||
| 7 | 7 | github.com/andybalholm/brotli v1.2.1 | |
| 8 | 8 | github.com/bnema/purego v0.11.0-bnema.2 | |
| 9 | 9 | github.com/bnema/purego-cef v0.13.0 | |
| 10 | - | github.com/bnema/purego-cef2gtk v0.2.0 | |
| 10 | + | github.com/bnema/purego-cef2gtk v0.3.0 | |
| 11 | 11 | github.com/bnema/purego-pipewire v0.1.3 | |
| 12 | 12 | github.com/bnema/purego-sqlite v0.1.2 | |
| 13 | 13 | github.com/bnema/puregotk v0.5.2 | |
| @@ -7,7 +7,7 @@ require ( | |||||
| 7 | github.com/andybalholm/brotli v1.2.1 | 7 | github.com/andybalholm/brotli v1.2.1 | ||
| 8 | github.com/bnema/purego v0.11.0-bnema.2 | 8 | github.com/bnema/purego v0.11.0-bnema.2 | ||
| 9 | github.com/bnema/purego-cef v0.13.0 | 9 | github.com/bnema/purego-cef v0.13.0 | ||
| 10 | - | github.com/bnema/purego-cef2gtk v0.2.0 | 10 | + | github.com/bnema/purego-cef2gtk v0.3.0 |
| 11 | github.com/bnema/purego-pipewire v0.1.3 | 11 | github.com/bnema/purego-pipewire v0.1.3 | ||
| 12 | github.com/bnema/purego-sqlite v0.1.2 | 12 | github.com/bnema/purego-sqlite v0.1.2 | ||
| 13 | github.com/bnema/puregotk v0.5.2 | 13 | github.com/bnema/puregotk v0.5.2 | ||
M go.sum +2 -2 generated file: collapsed
M internal/bootstrap/engine.go +1 -0
| @@ -80,6 +80,7 @@ func BuildEngine(input EngineInput) (port.Engine, error) { | |||
| 80 | 80 | CEFDir: cfg.Engine.CEF.CEFDir, | |
| 81 | 81 | LogFile: cfg.Engine.CEF.LogFile, | |
| 82 | 82 | LogSeverity: cfg.Engine.CEF.LogSeverity, | |
| 83 | + | RenderStack: string(cfg.Engine.CEF.CEFRenderStack()), | |
| 83 | 84 | WindowlessFrameRate: cfg.Engine.CEF.WindowlessFrameRate, | |
| 84 | 85 | EnableAudioHandler: cfg.Engine.CEF.EnableAudioHandler, | |
| 85 | 86 | TraceHandlers: cfg.Engine.CEF.TraceHandlers, | |
| @@ -80,6 +80,7 @@ func BuildEngine(input EngineInput) (port.Engine, error) { | |||||
| 80 | CEFDir: cfg.Engine.CEF.CEFDir, | 80 | CEFDir: cfg.Engine.CEF.CEFDir, | ||
| 81 | LogFile: cfg.Engine.CEF.LogFile, | 81 | LogFile: cfg.Engine.CEF.LogFile, | ||
| 82 | LogSeverity: cfg.Engine.CEF.LogSeverity, | 82 | LogSeverity: cfg.Engine.CEF.LogSeverity, | ||
| 83 | + | RenderStack: string(cfg.Engine.CEF.CEFRenderStack()), | |||
| 83 | WindowlessFrameRate: cfg.Engine.CEF.WindowlessFrameRate, | 84 | WindowlessFrameRate: cfg.Engine.CEF.WindowlessFrameRate, | ||
| 84 | EnableAudioHandler: cfg.Engine.CEF.EnableAudioHandler, | 85 | EnableAudioHandler: cfg.Engine.CEF.EnableAudioHandler, | ||
| 85 | TraceHandlers: cfg.Engine.CEF.TraceHandlers, | 86 | TraceHandlers: cfg.Engine.CEF.TraceHandlers, | ||
M internal/infrastructure/cef/app.go +7 -3
| @@ -41,6 +41,10 @@ func registerDumbScheme(registrar purecef.SchemeRegistrar) { | |||
| 41 | 41 | } | |
| 42 | 42 | ||
| 43 | 43 | func configureCommandLine(commandLine purecef.CommandLine) { | |
| 44 | + | configureCommandLineWithRenderStack(commandLine, cef2gtk.RenderStackPlan{}) | |
| 45 | + | } | |
| 46 | + | ||
| 47 | + | func configureCommandLineWithRenderStack(commandLine purecef.CommandLine, renderStackPlan cef2gtk.RenderStackPlan) { | |
| 44 | 48 | if commandLine == nil { | |
| 45 | 49 | return | |
| 46 | 50 | } | |
| @@ -48,7 +52,7 @@ func configureCommandLine(commandLine purecef.CommandLine) { | |||
| 48 | 52 | // Delegate Wayland accelerated rendering setup to the GTK bridge. | |
| 49 | 53 | // Preserves any pre-existing ozone-platform value (e.g. set via | |
| 50 | 54 | // CEF command-line args) — the bridge is a no-op when already present. | |
| 51 | - | cef2gtk.ConfigureCommandLine(commandLine, cef2gtk.CommandLineOptions{}) | |
| 55 | + | cef2gtk.ConfigureCommandLine(commandLine, cef2gtk.CommandLineOptions{RenderStackPlan: renderStackPlan}) | |
| 52 | 56 | ||
| 53 | 57 | // Enable Chromium's built-in smooth scrolling animation — without this, | |
| 54 | 58 | // mouse wheel scroll jumps in discrete steps with no momentum/easing. | |
| @@ -292,7 +296,7 @@ const maxCmdLineLogLen = 200 | |||
| 292 | 296 | func (a *dumberApp) OnBeforeCommandLineProcessing(processType string, commandLine purecef.CommandLine) { | |
| 293 | 297 | log := logging.FromContext(a.engine.ctx) | |
| 294 | 298 | if commandLine != nil { | |
| 295 | - | configureCommandLine(commandLine) | |
| 299 | + | configureCommandLineWithRenderStack(commandLine, a.engine.renderStackPlan) | |
| 296 | 300 | ||
| 297 | 301 | if processType == "" { | |
| 298 | 302 | if cefWebAuthnUnsafeEnabled() { | |
| @@ -360,7 +364,7 @@ func (h *dumberBPH) OnBeforeChildProcessLaunch(commandLine purecef.CommandLine) | |||
| 360 | 364 | ozonePlatform := "" | |
| 361 | 365 | renderNodeOverride := "" | |
| 362 | 366 | if commandLine != nil { | |
| 363 | - | configureCommandLine(commandLine) | |
| 367 | + | configureCommandLineWithRenderStack(commandLine, h.engine.renderStackPlan) | |
| 364 | 368 | appendSwitchIfMissing(commandLine, "no-zygote") | |
| 365 | 369 | processType = commandLine.GetSwitchValue("type") | |
| 366 | 370 | commandLineString = commandLine.GetCommandLineString() | |
| @@ -41,6 +41,10 @@ func registerDumbScheme(registrar purecef.SchemeRegistrar) { | |||||
| 41 | } | 41 | } | ||
| 42 | 42 | ||||
| 43 | func configureCommandLine(commandLine purecef.CommandLine) { | 43 | func configureCommandLine(commandLine purecef.CommandLine) { | ||
| 44 | + | configureCommandLineWithRenderStack(commandLine, cef2gtk.RenderStackPlan{}) | |||
| 45 | + | } | |||
| 46 | + | ||||
| 47 | + | func configureCommandLineWithRenderStack(commandLine purecef.CommandLine, renderStackPlan cef2gtk.RenderStackPlan) { | |||
| 44 | if commandLine == nil { | 48 | if commandLine == nil { | ||
| 45 | return | 49 | return | ||
| 46 | } | 50 | } | ||
| @@ -48,7 +52,7 @@ func configureCommandLine(commandLine purecef.CommandLine) { | |||||
| 48 | // Delegate Wayland accelerated rendering setup to the GTK bridge. | 52 | // Delegate Wayland accelerated rendering setup to the GTK bridge. | ||
| 49 | // Preserves any pre-existing ozone-platform value (e.g. set via | 53 | // Preserves any pre-existing ozone-platform value (e.g. set via | ||
| 50 | // CEF command-line args) — the bridge is a no-op when already present. | 54 | // CEF command-line args) — the bridge is a no-op when already present. | ||
| 51 | - | cef2gtk.ConfigureCommandLine(commandLine, cef2gtk.CommandLineOptions{}) | 55 | + | cef2gtk.ConfigureCommandLine(commandLine, cef2gtk.CommandLineOptions{RenderStackPlan: renderStackPlan}) |
| 52 | 56 | ||||
| 53 | // Enable Chromium's built-in smooth scrolling animation — without this, | 57 | // Enable Chromium's built-in smooth scrolling animation — without this, | ||
| 54 | // mouse wheel scroll jumps in discrete steps with no momentum/easing. | 58 | // mouse wheel scroll jumps in discrete steps with no momentum/easing. | ||
| @@ -292,7 +296,7 @@ const maxCmdLineLogLen = 200 | |||||
| 292 | func (a *dumberApp) OnBeforeCommandLineProcessing(processType string, commandLine purecef.CommandLine) { | 296 | func (a *dumberApp) OnBeforeCommandLineProcessing(processType string, commandLine purecef.CommandLine) { | ||
| 293 | log := logging.FromContext(a.engine.ctx) | 297 | log := logging.FromContext(a.engine.ctx) | ||
| 294 | if commandLine != nil { | 298 | if commandLine != nil { | ||
| 295 | - | configureCommandLine(commandLine) | 299 | + | configureCommandLineWithRenderStack(commandLine, a.engine.renderStackPlan) |
| 296 | 300 | ||||
| 297 | if processType == "" { | 301 | if processType == "" { | ||
| 298 | if cefWebAuthnUnsafeEnabled() { | 302 | if cefWebAuthnUnsafeEnabled() { | ||
| @@ -360,7 +364,7 @@ func (h *dumberBPH) OnBeforeChildProcessLaunch(commandLine purecef.CommandLine) | |||||
| 360 | ozonePlatform := "" | 364 | ozonePlatform := "" | ||
| 361 | renderNodeOverride := "" | 365 | renderNodeOverride := "" | ||
| 362 | if commandLine != nil { | 366 | if commandLine != nil { | ||
| 363 | - | configureCommandLine(commandLine) | 367 | + | configureCommandLineWithRenderStack(commandLine, h.engine.renderStackPlan) |
| 364 | appendSwitchIfMissing(commandLine, "no-zygote") | 368 | appendSwitchIfMissing(commandLine, "no-zygote") | ||
| 365 | processType = commandLine.GetSwitchValue("type") | 369 | processType = commandLine.GetSwitchValue("type") | ||
| 366 | commandLineString = commandLine.GetCommandLineString() | 370 | commandLineString = commandLine.GetCommandLineString() | ||
M internal/infrastructure/cef/app_test.go +13 -2
| @@ -297,6 +297,17 @@ func TestConfigureCommandLine_PreservesExistingOzonePlatform(t *testing.T) { | |||
| 297 | 297 | } | |
| 298 | 298 | } | |
| 299 | 299 | ||
| 300 | + | func TestConfigureCommandLine_PreservesInheritedANGLEBackend(t *testing.T) { | |
| 301 | + | commandLine := newMutableCommandLineStub() | |
| 302 | + | commandLine.AppendSwitchWithValue("use-angle", "gl-egl") | |
| 303 | + | ||
| 304 | + | configureCommandLine(commandLine) | |
| 305 | + | ||
| 306 | + | if got := commandLine.GetSwitchValue("use-angle"); got != "gl-egl" { | |
| 307 | + | t.Fatalf("use-angle = %q, want inherited gl-egl", got) | |
| 308 | + | } | |
| 309 | + | } | |
| 310 | + | ||
| 300 | 311 | func TestConfigureCommandLine_EnableVAAPIEnvAppendsHardwareDecodeFlags(t *testing.T) { | |
| 301 | 312 | t.Setenv(cefEnableVAAPIEnvVar, "1") | |
| 302 | 313 | ||
| @@ -337,8 +348,8 @@ func TestConfigureCommandLine_ChromiumFlagsEnvAppendsSwitches(t *testing.T) { | |||
| 337 | 348 | ||
| 338 | 349 | configureCommandLine(commandLine) | |
| 339 | 350 | ||
| 340 | - | if got := commandLine.GetSwitchValue(chromiumEnableFeaturesSwitch); got != "ExistingFeature,VaapiVideoDecoder,VaapiIgnoreDriverChecks" { | |
| 341 | - | t.Fatalf("enable-features = %q, want env features appended", got) | |
| 351 | + | if got := commandLine.GetSwitchValue(chromiumEnableFeaturesSwitch); got != "ExistingFeature,Vulkan,DefaultANGLEVulkan,VulkanFromANGLE,VaapiVideoDecoder,VaapiIgnoreDriverChecks" { | |
| 352 | + | t.Fatalf("enable-features = %q, want render stack and env features appended", got) | |
| 342 | 353 | } | |
| 343 | 354 | if !commandLine.HasSwitch("ignore-gpu-blocklist") { | |
| 344 | 355 | t.Fatal("expected ignore-gpu-blocklist switch") | |
| @@ -297,6 +297,17 @@ func TestConfigureCommandLine_PreservesExistingOzonePlatform(t *testing.T) { | |||||
| 297 | } | 297 | } | ||
| 298 | } | 298 | } | ||
| 299 | 299 | ||||
| 300 | + | func TestConfigureCommandLine_PreservesInheritedANGLEBackend(t *testing.T) { | |||
| 301 | + | commandLine := newMutableCommandLineStub() | |||
| 302 | + | commandLine.AppendSwitchWithValue("use-angle", "gl-egl") | |||
| 303 | + | ||||
| 304 | + | configureCommandLine(commandLine) | |||
| 305 | + | ||||
| 306 | + | if got := commandLine.GetSwitchValue("use-angle"); got != "gl-egl" { | |||
| 307 | + | t.Fatalf("use-angle = %q, want inherited gl-egl", got) | |||
| 308 | + | } | |||
| 309 | + | } | |||
| 310 | + | ||||
| 300 | func TestConfigureCommandLine_EnableVAAPIEnvAppendsHardwareDecodeFlags(t *testing.T) { | 311 | func TestConfigureCommandLine_EnableVAAPIEnvAppendsHardwareDecodeFlags(t *testing.T) { | ||
| 301 | t.Setenv(cefEnableVAAPIEnvVar, "1") | 312 | t.Setenv(cefEnableVAAPIEnvVar, "1") | ||
| 302 | 313 | ||||
| @@ -337,8 +348,8 @@ func TestConfigureCommandLine_ChromiumFlagsEnvAppendsSwitches(t *testing.T) { | |||||
| 337 | 348 | ||||
| 338 | configureCommandLine(commandLine) | 349 | configureCommandLine(commandLine) | ||
| 339 | 350 | ||||
| 340 | - | if got := commandLine.GetSwitchValue(chromiumEnableFeaturesSwitch); got != "ExistingFeature,VaapiVideoDecoder,VaapiIgnoreDriverChecks" { | 351 | + | if got := commandLine.GetSwitchValue(chromiumEnableFeaturesSwitch); got != "ExistingFeature,Vulkan,DefaultANGLEVulkan,VulkanFromANGLE,VaapiVideoDecoder,VaapiIgnoreDriverChecks" { |
| 341 | - | t.Fatalf("enable-features = %q, want env features appended", got) | 352 | + | t.Fatalf("enable-features = %q, want render stack and env features appended", got) |
| 342 | } | 353 | } | ||
| 343 | if !commandLine.HasSwitch("ignore-gpu-blocklist") { | 354 | if !commandLine.HasSwitch("ignore-gpu-blocklist") { | ||
| 344 | t.Fatal("expected ignore-gpu-blocklist switch") | 355 | t.Fatal("expected ignore-gpu-blocklist switch") | ||
M internal/infrastructure/cef/cef2gtk_adapter.go +2 -2
| @@ -33,8 +33,8 @@ type Cef2gtkAdapter struct { | |||
| 33 | 33 | ||
| 34 | 34 | // NewCef2gtkAdapter creates an accelerated CEF view and wraps it in a thin | |
| 35 | 35 | // Dumber adapter. | |
| 36 | - | func NewCef2gtkAdapter() *Cef2gtkAdapter { | |
| 37 | - | v := cef2gtk.NewView() | |
| 36 | + | func NewCef2gtkAdapter(renderStackPlan cef2gtk.RenderStackPlan) *Cef2gtkAdapter { | |
| 37 | + | v := cef2gtk.NewViewWithOptions(cef2gtk.ViewOptions{RenderStackPlan: renderStackPlan}) | |
| 38 | 38 | if v == nil { | |
| 39 | 39 | return nil | |
| 40 | 40 | } | |
| @@ -33,8 +33,8 @@ type Cef2gtkAdapter struct { | |||||
| 33 | 33 | ||||
| 34 | // NewCef2gtkAdapter creates an accelerated CEF view and wraps it in a thin | 34 | // NewCef2gtkAdapter creates an accelerated CEF view and wraps it in a thin | ||
| 35 | // Dumber adapter. | 35 | // Dumber adapter. | ||
| 36 | - | func NewCef2gtkAdapter() *Cef2gtkAdapter { | 36 | + | func NewCef2gtkAdapter(renderStackPlan cef2gtk.RenderStackPlan) *Cef2gtkAdapter { |
| 37 | - | v := cef2gtk.NewView() | 37 | + | v := cef2gtk.NewViewWithOptions(cef2gtk.ViewOptions{RenderStackPlan: renderStackPlan}) |
| 38 | if v == nil { | 38 | if v == nil { | ||
| 39 | return nil | 39 | return nil | ||
| 40 | } | 40 | } | ||
M internal/infrastructure/cef/engine.go +7 -5
| @@ -11,6 +11,7 @@ import ( | |||
| 11 | 11 | "unicode/utf8" | |
| 12 | 12 | ||
| 13 | 13 | purecef "github.com/bnema/purego-cef/cef" | |
| 14 | + | cef2gtk "github.com/bnema/purego-cef2gtk" | |
| 14 | 15 | ||
| 15 | 16 | "github.com/bnema/dumber/internal/application/dto" | |
| 16 | 17 | "github.com/bnema/dumber/internal/application/port" | |
| @@ -24,11 +25,12 @@ var _ port.AlreadyRunningAppRelaunchHandlerSetter = (*Engine)(nil) | |||
| 24 | 25 | // Engine implements port.Engine for the CEF browser backend. | |
| 25 | 26 | // It manages the CEF lifecycle and provides access to all engine subsystems. | |
| 26 | 27 | type Engine struct { | |
| 27 | - | ctx context.Context | |
| 28 | - | factory *WebViewFactory | |
| 29 | - | pool *WebViewPool | |
| 30 | - | profileLogDir string | |
| 31 | - | runtimeCEFDir string | |
| 28 | + | ctx context.Context | |
| 29 | + | factory *WebViewFactory | |
| 30 | + | pool *WebViewPool | |
| 31 | + | profileLogDir string | |
| 32 | + | runtimeCEFDir string | |
| 33 | + | renderStackPlan cef2gtk.RenderStackPlan | |
| 32 | 34 | ||
| 33 | 35 | messageRouter *MessageRouter | |
| 34 | 36 | schemeHandler *dumbSchemeHandler | |
| @@ -11,6 +11,7 @@ import ( | |||||
| 11 | "unicode/utf8" | 11 | "unicode/utf8" | ||
| 12 | 12 | ||||
| 13 | purecef "github.com/bnema/purego-cef/cef" | 13 | purecef "github.com/bnema/purego-cef/cef" | ||
| 14 | + | cef2gtk "github.com/bnema/purego-cef2gtk" | |||
| 14 | 15 | ||||
| 15 | "github.com/bnema/dumber/internal/application/dto" | 16 | "github.com/bnema/dumber/internal/application/dto" | ||
| 16 | "github.com/bnema/dumber/internal/application/port" | 17 | "github.com/bnema/dumber/internal/application/port" | ||
| @@ -24,11 +25,12 @@ var _ port.AlreadyRunningAppRelaunchHandlerSetter = (*Engine)(nil) | |||||
| 24 | // Engine implements port.Engine for the CEF browser backend. | 25 | // Engine implements port.Engine for the CEF browser backend. | ||
| 25 | // It manages the CEF lifecycle and provides access to all engine subsystems. | 26 | // It manages the CEF lifecycle and provides access to all engine subsystems. | ||
| 26 | type Engine struct { | 27 | type Engine struct { | ||
| 27 | - | ctx context.Context | 28 | + | ctx context.Context |
| 28 | - | factory *WebViewFactory | 29 | + | factory *WebViewFactory |
| 29 | - | pool *WebViewPool | 30 | + | pool *WebViewPool |
| 30 | - | profileLogDir string | 31 | + | profileLogDir string |
| 31 | - | runtimeCEFDir string | 32 | + | runtimeCEFDir string |
| 33 | + | renderStackPlan cef2gtk.RenderStackPlan | |||
| 32 | 34 | ||||
| 33 | messageRouter *MessageRouter | 35 | messageRouter *MessageRouter | ||
| 34 | schemeHandler *dumbSchemeHandler | 36 | schemeHandler *dumbSchemeHandler | ||
M internal/infrastructure/cef/engine_init.go +11 -0
| @@ -11,6 +11,7 @@ import ( | |||
| 11 | 11 | "syscall" | |
| 12 | 12 | ||
| 13 | 13 | purecef "github.com/bnema/purego-cef/cef" | |
| 14 | + | cef2gtk "github.com/bnema/purego-cef2gtk" | |
| 14 | 15 | "github.com/bnema/puregotk/v4/gdk" | |
| 15 | 16 | "github.com/bnema/puregotk/v4/glib" | |
| 16 | 17 | "github.com/rs/zerolog" | |
| @@ -44,6 +45,11 @@ func NewEngine( | |||
| 44 | 45 | stateRoot := resolvedStateRoot(paths.StateRoot, opts) | |
| 45 | 46 | cleanStaleSingletonLocks(logger, stateRoot) | |
| 46 | 47 | windowlessFrameRate := normalizedWindowlessFrameRate(cfg.WindowlessFrameRate) | |
| 48 | + | renderStackPlan, err := resolveCEFRenderStackPlan(cfg.RenderStack) | |
| 49 | + | if err != nil { | |
| 50 | + | return nil, err | |
| 51 | + | } | |
| 52 | + | cef2gtk.ConfigureRenderStackEnvironment(renderStackPlan) | |
| 47 | 53 | ||
| 48 | 54 | settings, err := prepareCEFSettings(opts, paths, cfg, logger) | |
| 49 | 55 | if err != nil { | |
| @@ -59,6 +65,7 @@ func NewEngine( | |||
| 59 | 65 | ctx: ctx, | |
| 60 | 66 | profileLogDir: paths.ProfileLogDir, | |
| 61 | 67 | runtimeCEFDir: settings.CEFDir, | |
| 68 | + | renderStackPlan: renderStackPlan, | |
| 62 | 69 | registerHandlers: deps.RegisterHandlers, | |
| 63 | 70 | registerAccentHandlers: deps.RegisterAccentHandlers, | |
| 64 | 71 | currentConfigPayload: deps.CurrentConfigPayload, | |
| @@ -72,6 +79,10 @@ func NewEngine( | |||
| 72 | 79 | ||
| 73 | 80 | logger.Info(). | |
| 74 | 81 | Int32("windowless_frame_rate", windowlessFrameRate). | |
| 82 | + | Str("render_stack", string(renderStackPlan.Stack)). | |
| 83 | + | Str("render_backend", renderStackPlan.Backend.String()). | |
| 84 | + | Str("angle_backend", renderStackPlan.ANGLEBackend). | |
| 85 | + | Str("gsk_renderer", renderStackPlan.GSKRenderer). | |
| 75 | 86 | Bool("external_begin_frame", externalBeginFrameEnabled()). | |
| 76 | 87 | Bool("trace_handlers", cfg.TraceHandlers). | |
| 77 | 88 | Bool("enable_audio_handler", cfg.EnableAudioHandler). | |
| @@ -11,6 +11,7 @@ import ( | |||||
| 11 | "syscall" | 11 | "syscall" | ||
| 12 | 12 | ||||
| 13 | purecef "github.com/bnema/purego-cef/cef" | 13 | purecef "github.com/bnema/purego-cef/cef" | ||
| 14 | + | cef2gtk "github.com/bnema/purego-cef2gtk" | |||
| 14 | "github.com/bnema/puregotk/v4/gdk" | 15 | "github.com/bnema/puregotk/v4/gdk" | ||
| 15 | "github.com/bnema/puregotk/v4/glib" | 16 | "github.com/bnema/puregotk/v4/glib" | ||
| 16 | "github.com/rs/zerolog" | 17 | "github.com/rs/zerolog" | ||
| @@ -44,6 +45,11 @@ func NewEngine( | |||||
| 44 | stateRoot := resolvedStateRoot(paths.StateRoot, opts) | 45 | stateRoot := resolvedStateRoot(paths.StateRoot, opts) | ||
| 45 | cleanStaleSingletonLocks(logger, stateRoot) | 46 | cleanStaleSingletonLocks(logger, stateRoot) | ||
| 46 | windowlessFrameRate := normalizedWindowlessFrameRate(cfg.WindowlessFrameRate) | 47 | windowlessFrameRate := normalizedWindowlessFrameRate(cfg.WindowlessFrameRate) | ||
| 48 | + | renderStackPlan, err := resolveCEFRenderStackPlan(cfg.RenderStack) | |||
| 49 | + | if err != nil { | |||
| 50 | + | return nil, err | |||
| 51 | + | } | |||
| 52 | + | cef2gtk.ConfigureRenderStackEnvironment(renderStackPlan) | |||
| 47 | 53 | ||||
| 48 | settings, err := prepareCEFSettings(opts, paths, cfg, logger) | 54 | settings, err := prepareCEFSettings(opts, paths, cfg, logger) | ||
| 49 | if err != nil { | 55 | if err != nil { | ||
| @@ -59,6 +65,7 @@ func NewEngine( | |||||
| 59 | ctx: ctx, | 65 | ctx: ctx, | ||
| 60 | profileLogDir: paths.ProfileLogDir, | 66 | profileLogDir: paths.ProfileLogDir, | ||
| 61 | runtimeCEFDir: settings.CEFDir, | 67 | runtimeCEFDir: settings.CEFDir, | ||
| 68 | + | renderStackPlan: renderStackPlan, | |||
| 62 | registerHandlers: deps.RegisterHandlers, | 69 | registerHandlers: deps.RegisterHandlers, | ||
| 63 | registerAccentHandlers: deps.RegisterAccentHandlers, | 70 | registerAccentHandlers: deps.RegisterAccentHandlers, | ||
| 64 | currentConfigPayload: deps.CurrentConfigPayload, | 71 | currentConfigPayload: deps.CurrentConfigPayload, | ||
| @@ -72,6 +79,10 @@ func NewEngine( | |||||
| 72 | 79 | ||||
| 73 | logger.Info(). | 80 | logger.Info(). | ||
| 74 | Int32("windowless_frame_rate", windowlessFrameRate). | 81 | Int32("windowless_frame_rate", windowlessFrameRate). | ||
| 82 | + | Str("render_stack", string(renderStackPlan.Stack)). | |||
| 83 | + | Str("render_backend", renderStackPlan.Backend.String()). | |||
| 84 | + | Str("angle_backend", renderStackPlan.ANGLEBackend). | |||
| 85 | + | Str("gsk_renderer", renderStackPlan.GSKRenderer). | |||
| 75 | Bool("external_begin_frame", externalBeginFrameEnabled()). | 86 | Bool("external_begin_frame", externalBeginFrameEnabled()). | ||
| 76 | Bool("trace_handlers", cfg.TraceHandlers). | 87 | Bool("trace_handlers", cfg.TraceHandlers). | ||
| 77 | Bool("enable_audio_handler", cfg.EnableAudioHandler). | 88 | Bool("enable_audio_handler", cfg.EnableAudioHandler). | ||
M internal/infrastructure/cef/factory.go +3 -3
| @@ -81,7 +81,7 @@ func (f *WebViewFactory) Create(ctx context.Context) (port.WebView, error) { | |||
| 81 | 81 | ||
| 82 | 82 | // Configure BrowserSettings. | |
| 83 | 83 | settings := purecef.NewBrowserSettings() | |
| 84 | - | settings.WindowlessFrameRate = f.windowlessFrameRate | |
| 84 | + | cef2gtk.ConfigureBrowserSettings(&settings, cef2gtk.BrowserSettingsOptions{WindowlessFrameRate: f.windowlessFrameRate}) | |
| 85 | 85 | settings.LocalStorage = 1 // CEF_STATE_ENABLED | |
| 86 | 86 | if bg := f.bgColor.Load(); bg != 0 { | |
| 87 | 87 | settings.BackgroundColor = bg | |
| @@ -97,7 +97,7 @@ func (f *WebViewFactory) Create(ctx context.Context) (port.WebView, error) { | |||
| 97 | 97 | ||
| 98 | 98 | func (f *WebViewFactory) newWebView(ctx context.Context) (*WebView, error) { | |
| 99 | 99 | id := port.WebViewID(f.nextID.Add(1)) | |
| 100 | - | viewBridge := NewCef2gtkAdapter() | |
| 100 | + | viewBridge := NewCef2gtkAdapter(f.engine.renderStackPlan) | |
| 101 | 101 | if viewBridge == nil { | |
| 102 | 102 | return nil, fmt.Errorf("create cef2gtk view") | |
| 103 | 103 | } | |
| @@ -300,7 +300,7 @@ func (f *WebViewFactory) CreateRelated(ctx context.Context, parentID port.WebVie | |||
| 300 | 300 | } | |
| 301 | 301 | ||
| 302 | 302 | settings := purecef.NewBrowserSettings() | |
| 303 | - | settings.WindowlessFrameRate = f.windowlessFrameRate | |
| 303 | + | cef2gtk.ConfigureBrowserSettings(&settings, cef2gtk.BrowserSettingsOptions{WindowlessFrameRate: f.windowlessFrameRate}) | |
| 304 | 304 | settings.LocalStorage = 1 // CEF_STATE_ENABLED | |
| 305 | 305 | if bg := f.bgColor.Load(); bg != 0 { | |
| 306 | 306 | settings.BackgroundColor = bg | |
| @@ -81,7 +81,7 @@ func (f *WebViewFactory) Create(ctx context.Context) (port.WebView, error) { | |||||
| 81 | 81 | ||||
| 82 | // Configure BrowserSettings. | 82 | // Configure BrowserSettings. | ||
| 83 | settings := purecef.NewBrowserSettings() | 83 | settings := purecef.NewBrowserSettings() | ||
| 84 | - | settings.WindowlessFrameRate = f.windowlessFrameRate | 84 | + | cef2gtk.ConfigureBrowserSettings(&settings, cef2gtk.BrowserSettingsOptions{WindowlessFrameRate: f.windowlessFrameRate}) |
| 85 | settings.LocalStorage = 1 // CEF_STATE_ENABLED | 85 | settings.LocalStorage = 1 // CEF_STATE_ENABLED | ||
| 86 | if bg := f.bgColor.Load(); bg != 0 { | 86 | if bg := f.bgColor.Load(); bg != 0 { | ||
| 87 | settings.BackgroundColor = bg | 87 | settings.BackgroundColor = bg | ||
| @@ -97,7 +97,7 @@ func (f *WebViewFactory) Create(ctx context.Context) (port.WebView, error) { | |||||
| 97 | 97 | ||||
| 98 | func (f *WebViewFactory) newWebView(ctx context.Context) (*WebView, error) { | 98 | func (f *WebViewFactory) newWebView(ctx context.Context) (*WebView, error) { | ||
| 99 | id := port.WebViewID(f.nextID.Add(1)) | 99 | id := port.WebViewID(f.nextID.Add(1)) | ||
| 100 | - | viewBridge := NewCef2gtkAdapter() | 100 | + | viewBridge := NewCef2gtkAdapter(f.engine.renderStackPlan) |
| 101 | if viewBridge == nil { | 101 | if viewBridge == nil { | ||
| 102 | return nil, fmt.Errorf("create cef2gtk view") | 102 | return nil, fmt.Errorf("create cef2gtk view") | ||
| 103 | } | 103 | } | ||
| @@ -300,7 +300,7 @@ func (f *WebViewFactory) CreateRelated(ctx context.Context, parentID port.WebVie | |||||
| 300 | } | 300 | } | ||
| 301 | 301 | ||||
| 302 | settings := purecef.NewBrowserSettings() | 302 | settings := purecef.NewBrowserSettings() | ||
| 303 | - | settings.WindowlessFrameRate = f.windowlessFrameRate | 303 | + | cef2gtk.ConfigureBrowserSettings(&settings, cef2gtk.BrowserSettingsOptions{WindowlessFrameRate: f.windowlessFrameRate}) |
| 304 | settings.LocalStorage = 1 // CEF_STATE_ENABLED | 304 | settings.LocalStorage = 1 // CEF_STATE_ENABLED | ||
| 305 | if bg := f.bgColor.Load(); bg != 0 { | 305 | if bg := f.bgColor.Load(); bg != 0 { | ||
| 306 | settings.BackgroundColor = bg | 306 | settings.BackgroundColor = bg | ||
A internal/infrastructure/cef/render_stack.go +36 -0
| @@ -0,0 +1,36 @@ | |||
| 1 | + | package cef | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "fmt" | |
| 5 | + | ||
| 6 | + | cef2gtk "github.com/bnema/purego-cef2gtk" | |
| 7 | + | ||
| 8 | + | "github.com/bnema/dumber/internal/application/port" | |
| 9 | + | ) | |
| 10 | + | ||
| 11 | + | func resolveCEFRenderStackPlan(stack string) (cef2gtk.RenderStackPlan, error) { | |
| 12 | + | plan, err := cef2gtk.ResolveRenderStack(cef2gtk.RenderStack(stack)) | |
| 13 | + | if err != nil { | |
| 14 | + | return cef2gtk.RenderStackPlan{}, fmt.Errorf("resolve CEF render stack: %w", err) | |
| 15 | + | } | |
| 16 | + | return plan, nil | |
| 17 | + | } | |
| 18 | + | ||
| 19 | + | // ApplyConfiguredRenderStackEnvironment applies Dumber's CEF render-stack | |
| 20 | + | // process environment before GTK/libadwaita initialize. | |
| 21 | + | func ApplyConfiguredRenderStackEnvironment(stack string, logger port.Logger) cef2gtk.RenderStackPlan { | |
| 22 | + | plan, err := resolveCEFRenderStackPlan(stack) | |
| 23 | + | if err != nil { | |
| 24 | + | logWarn(logger, "cef: invalid render stack, falling back to Vulkan", port.Field("error", err.Error())) | |
| 25 | + | plan, _ = cef2gtk.ResolveRenderStack(cef2gtk.RenderStackVulkan) | |
| 26 | + | } | |
| 27 | + | cef2gtk.ConfigureRenderStackEnvironment(plan) | |
| 28 | + | logInfo(logger, "cef: render stack environment configured", | |
| 29 | + | port.Field("render_stack", string(plan.Stack)), | |
| 30 | + | port.Field("render_backend", plan.Backend.String()), | |
| 31 | + | port.Field("angle_backend", plan.ANGLEBackend), | |
| 32 | + | port.Field("gsk_renderer", plan.GSKRenderer), | |
| 33 | + | port.Field("osr_backing_scale", plan.OSRBackingScale), | |
| 34 | + | ) | |
| 35 | + | return plan | |
| 36 | + | } | |
| @@ -0,0 +1,36 @@ | |||||
| 1 | + | package cef | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "fmt" | |||
| 5 | + | ||||
| 6 | + | cef2gtk "github.com/bnema/purego-cef2gtk" | |||
| 7 | + | ||||
| 8 | + | "github.com/bnema/dumber/internal/application/port" | |||
| 9 | + | ) | |||
| 10 | + | ||||
| 11 | + | func resolveCEFRenderStackPlan(stack string) (cef2gtk.RenderStackPlan, error) { | |||
| 12 | + | plan, err := cef2gtk.ResolveRenderStack(cef2gtk.RenderStack(stack)) | |||
| 13 | + | if err != nil { | |||
| 14 | + | return cef2gtk.RenderStackPlan{}, fmt.Errorf("resolve CEF render stack: %w", err) | |||
| 15 | + | } | |||
| 16 | + | return plan, nil | |||
| 17 | + | } | |||
| 18 | + | ||||
| 19 | + | // ApplyConfiguredRenderStackEnvironment applies Dumber's CEF render-stack | |||
| 20 | + | // process environment before GTK/libadwaita initialize. | |||
| 21 | + | func ApplyConfiguredRenderStackEnvironment(stack string, logger port.Logger) cef2gtk.RenderStackPlan { | |||
| 22 | + | plan, err := resolveCEFRenderStackPlan(stack) | |||
| 23 | + | if err != nil { | |||
| 24 | + | logWarn(logger, "cef: invalid render stack, falling back to Vulkan", port.Field("error", err.Error())) | |||
| 25 | + | plan, _ = cef2gtk.ResolveRenderStack(cef2gtk.RenderStackVulkan) | |||
| 26 | + | } | |||
| 27 | + | cef2gtk.ConfigureRenderStackEnvironment(plan) | |||
| 28 | + | logInfo(logger, "cef: render stack environment configured", | |||
| 29 | + | port.Field("render_stack", string(plan.Stack)), | |||
| 30 | + | port.Field("render_backend", plan.Backend.String()), | |||
| 31 | + | port.Field("angle_backend", plan.ANGLEBackend), | |||
| 32 | + | port.Field("gsk_renderer", plan.GSKRenderer), | |||
| 33 | + | port.Field("osr_backing_scale", plan.OSRBackingScale), | |||
| 34 | + | ) | |||
| 35 | + | return plan | |||
| 36 | + | } | |||
A internal/infrastructure/cef/render_stack_plan_test.go +48 -0
| @@ -0,0 +1,48 @@ | |||
| 1 | + | package cef | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "testing" | |
| 5 | + | ||
| 6 | + | cef2gtk "github.com/bnema/purego-cef2gtk" | |
| 7 | + | ) | |
| 8 | + | ||
| 9 | + | func TestResolveCEFRenderStackPlan(t *testing.T) { | |
| 10 | + | tests := []struct { | |
| 11 | + | name string | |
| 12 | + | stack string | |
| 13 | + | wantStack cef2gtk.RenderStack | |
| 14 | + | wantAngle string | |
| 15 | + | }{ | |
| 16 | + | {name: "vulkan", stack: "vulkan", wantStack: cef2gtk.RenderStackVulkan, wantAngle: "vulkan"}, | |
| 17 | + | {name: "egl", stack: "egl", wantStack: cef2gtk.RenderStackEGL, wantAngle: "gl-egl"}, | |
| 18 | + | } | |
| 19 | + | ||
| 20 | + | for _, tt := range tests { | |
| 21 | + | t.Run(tt.name, func(t *testing.T) { | |
| 22 | + | plan, err := resolveCEFRenderStackPlan(tt.stack) | |
| 23 | + | if err != nil { | |
| 24 | + | t.Fatalf("resolveCEFRenderStackPlan(%q) error = %v", tt.stack, err) | |
| 25 | + | } | |
| 26 | + | if plan.Stack != tt.wantStack { | |
| 27 | + | t.Fatalf("Stack = %q, want %q", plan.Stack, tt.wantStack) | |
| 28 | + | } | |
| 29 | + | if plan.ANGLEBackend != tt.wantAngle { | |
| 30 | + | t.Fatalf("ANGLEBackend = %q, want %q", plan.ANGLEBackend, tt.wantAngle) | |
| 31 | + | } | |
| 32 | + | }) | |
| 33 | + | } | |
| 34 | + | } | |
| 35 | + | ||
| 36 | + | func TestConfigureCommandLineWithRenderStackUsesPlan(t *testing.T) { | |
| 37 | + | plan, err := resolveCEFRenderStackPlan("egl") | |
| 38 | + | if err != nil { | |
| 39 | + | t.Fatalf("resolveCEFRenderStackPlan(egl) error = %v", err) | |
| 40 | + | } | |
| 41 | + | commandLine := newMutableCommandLineStub() | |
| 42 | + | ||
| 43 | + | configureCommandLineWithRenderStack(commandLine, plan) | |
| 44 | + | ||
| 45 | + | if got := commandLine.GetSwitchValue("use-angle"); got != "gl-egl" { | |
| 46 | + | t.Fatalf("use-angle = %q, want gl-egl", got) | |
| 47 | + | } | |
| 48 | + | } | |
| @@ -0,0 +1,48 @@ | |||||
| 1 | + | package cef | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "testing" | |||
| 5 | + | ||||
| 6 | + | cef2gtk "github.com/bnema/purego-cef2gtk" | |||
| 7 | + | ) | |||
| 8 | + | ||||
| 9 | + | func TestResolveCEFRenderStackPlan(t *testing.T) { | |||
| 10 | + | tests := []struct { | |||
| 11 | + | name string | |||
| 12 | + | stack string | |||
| 13 | + | wantStack cef2gtk.RenderStack | |||
| 14 | + | wantAngle string | |||
| 15 | + | }{ | |||
| 16 | + | {name: "vulkan", stack: "vulkan", wantStack: cef2gtk.RenderStackVulkan, wantAngle: "vulkan"}, | |||
| 17 | + | {name: "egl", stack: "egl", wantStack: cef2gtk.RenderStackEGL, wantAngle: "gl-egl"}, | |||
| 18 | + | } | |||
| 19 | + | ||||
| 20 | + | for _, tt := range tests { | |||
| 21 | + | t.Run(tt.name, func(t *testing.T) { | |||
| 22 | + | plan, err := resolveCEFRenderStackPlan(tt.stack) | |||
| 23 | + | if err != nil { | |||
| 24 | + | t.Fatalf("resolveCEFRenderStackPlan(%q) error = %v", tt.stack, err) | |||
| 25 | + | } | |||
| 26 | + | if plan.Stack != tt.wantStack { | |||
| 27 | + | t.Fatalf("Stack = %q, want %q", plan.Stack, tt.wantStack) | |||
| 28 | + | } | |||
| 29 | + | if plan.ANGLEBackend != tt.wantAngle { | |||
| 30 | + | t.Fatalf("ANGLEBackend = %q, want %q", plan.ANGLEBackend, tt.wantAngle) | |||
| 31 | + | } | |||
| 32 | + | }) | |||
| 33 | + | } | |||
| 34 | + | } | |||
| 35 | + | ||||
| 36 | + | func TestConfigureCommandLineWithRenderStackUsesPlan(t *testing.T) { | |||
| 37 | + | plan, err := resolveCEFRenderStackPlan("egl") | |||
| 38 | + | if err != nil { | |||
| 39 | + | t.Fatalf("resolveCEFRenderStackPlan(egl) error = %v", err) | |||
| 40 | + | } | |||
| 41 | + | commandLine := newMutableCommandLineStub() | |||
| 42 | + | ||||
| 43 | + | configureCommandLineWithRenderStack(commandLine, plan) | |||
| 44 | + | ||||
| 45 | + | if got := commandLine.GetSwitchValue("use-angle"); got != "gl-egl" { | |||
| 46 | + | t.Fatalf("use-angle = %q, want gl-egl", got) | |||
| 47 | + | } | |||
| 48 | + | } | |||
M internal/infrastructure/cef/runtime_config.go +1 -0
| @@ -18,6 +18,7 @@ type RuntimeConfig struct { | |||
| 18 | 18 | CEFDir string | |
| 19 | 19 | LogFile string | |
| 20 | 20 | LogSeverity int32 | |
| 21 | + | RenderStack string | |
| 21 | 22 | WindowlessFrameRate int32 | |
| 22 | 23 | EnableAudioHandler bool | |
| 23 | 24 | TraceHandlers bool | |
| @@ -18,6 +18,7 @@ type RuntimeConfig struct { | |||||
| 18 | CEFDir string | 18 | CEFDir string | ||
| 19 | LogFile string | 19 | LogFile string | ||
| 20 | LogSeverity int32 | 20 | LogSeverity int32 | ||
| 21 | + | RenderStack string | |||
| 21 | WindowlessFrameRate int32 | 22 | WindowlessFrameRate int32 | ||
| 22 | EnableAudioHandler bool | 23 | EnableAudioHandler bool | ||
| 23 | TraceHandlers bool | 24 | TraceHandlers bool | ||
M internal/infrastructure/cef/webview.go +1 -3
| @@ -965,9 +965,7 @@ func configureNativePopupWindow( | |||
| 965 | 965 | if settings == nil { | |
| 966 | 966 | return | |
| 967 | 967 | } | |
| 968 | - | if frameRate > 0 { | |
| 969 | - | settings.WindowlessFrameRate = frameRate | |
| 970 | - | } | |
| 968 | + | cef2gtk.ConfigureBrowserSettings(settings, cef2gtk.BrowserSettingsOptions{WindowlessFrameRate: frameRate}) | |
| 971 | 969 | settings.LocalStorage = 1 | |
| 972 | 970 | if backgroundColor != 0 { | |
| 973 | 971 | settings.BackgroundColor = backgroundColor | |
| @@ -965,9 +965,7 @@ func configureNativePopupWindow( | |||||
| 965 | if settings == nil { | 965 | if settings == nil { | ||
| 966 | return | 966 | return | ||
| 967 | } | 967 | } | ||
| 968 | - | if frameRate > 0 { | 968 | + | cef2gtk.ConfigureBrowserSettings(settings, cef2gtk.BrowserSettingsOptions{WindowlessFrameRate: frameRate}) |
| 969 | - | settings.WindowlessFrameRate = frameRate | |||
| 970 | - | } | |||
| 971 | settings.LocalStorage = 1 | 969 | settings.LocalStorage = 1 | ||
| 972 | if backgroundColor != 0 { | 970 | if backgroundColor != 0 { | ||
| 973 | settings.BackgroundColor = backgroundColor | 971 | settings.BackgroundColor = backgroundColor | ||
A internal/infrastructure/config/cef_render_stack_test.go +42 -0
| @@ -0,0 +1,42 @@ | |||
| 1 | + | package config | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "testing" | |
| 5 | + | ||
| 6 | + | "github.com/stretchr/testify/assert" | |
| 7 | + | "github.com/stretchr/testify/require" | |
| 8 | + | ) | |
| 9 | + | ||
| 10 | + | func TestCEFRenderStackDefaultIsVulkan(t *testing.T) { | |
| 11 | + | cfg := DefaultConfig() | |
| 12 | + | ||
| 13 | + | assert.Equal(t, CEFRenderStackVulkan, cfg.Engine.CEF.CEFRenderStack()) | |
| 14 | + | } | |
| 15 | + | ||
| 16 | + | func TestValidateConfig_CEFRenderStack(t *testing.T) { | |
| 17 | + | tests := []struct { | |
| 18 | + | name string | |
| 19 | + | stack CEFRenderStack | |
| 20 | + | wantErr bool | |
| 21 | + | }{ | |
| 22 | + | {name: "vulkan", stack: CEFRenderStackVulkan}, | |
| 23 | + | {name: "egl", stack: CEFRenderStackEGL}, | |
| 24 | + | {name: "empty uses default", stack: ""}, | |
| 25 | + | {name: "invalid", stack: CEFRenderStack("auto"), wantErr: true}, | |
| 26 | + | } | |
| 27 | + | ||
| 28 | + | for _, tt := range tests { | |
| 29 | + | t.Run(tt.name, func(t *testing.T) { | |
| 30 | + | cfg := DefaultConfig() | |
| 31 | + | cfg.Engine.CEF.RenderStack = tt.stack | |
| 32 | + | ||
| 33 | + | err := validateConfig(cfg) | |
| 34 | + | if tt.wantErr { | |
| 35 | + | require.Error(t, err) | |
| 36 | + | assert.Contains(t, err.Error(), "engine.cef.render_stack") | |
| 37 | + | return | |
| 38 | + | } | |
| 39 | + | require.NoError(t, err) | |
| 40 | + | }) | |
| 41 | + | } | |
| 42 | + | } | |
| @@ -0,0 +1,42 @@ | |||||
| 1 | + | package config | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "testing" | |||
| 5 | + | ||||
| 6 | + | "github.com/stretchr/testify/assert" | |||
| 7 | + | "github.com/stretchr/testify/require" | |||
| 8 | + | ) | |||
| 9 | + | ||||
| 10 | + | func TestCEFRenderStackDefaultIsVulkan(t *testing.T) { | |||
| 11 | + | cfg := DefaultConfig() | |||
| 12 | + | ||||
| 13 | + | assert.Equal(t, CEFRenderStackVulkan, cfg.Engine.CEF.CEFRenderStack()) | |||
| 14 | + | } | |||
| 15 | + | ||||
| 16 | + | func TestValidateConfig_CEFRenderStack(t *testing.T) { | |||
| 17 | + | tests := []struct { | |||
| 18 | + | name string | |||
| 19 | + | stack CEFRenderStack | |||
| 20 | + | wantErr bool | |||
| 21 | + | }{ | |||
| 22 | + | {name: "vulkan", stack: CEFRenderStackVulkan}, | |||
| 23 | + | {name: "egl", stack: CEFRenderStackEGL}, | |||
| 24 | + | {name: "empty uses default", stack: ""}, | |||
| 25 | + | {name: "invalid", stack: CEFRenderStack("auto"), wantErr: true}, | |||
| 26 | + | } | |||
| 27 | + | ||||
| 28 | + | for _, tt := range tests { | |||
| 29 | + | t.Run(tt.name, func(t *testing.T) { | |||
| 30 | + | cfg := DefaultConfig() | |||
| 31 | + | cfg.Engine.CEF.RenderStack = tt.stack | |||
| 32 | + | ||||
| 33 | + | err := validateConfig(cfg) | |||
| 34 | + | if tt.wantErr { | |||
| 35 | + | require.Error(t, err) | |||
| 36 | + | assert.Contains(t, err.Error(), "engine.cef.render_stack") | |||
| 37 | + | return | |||
| 38 | + | } | |||
| 39 | + | require.NoError(t, err) | |||
| 40 | + | }) | |||
| 41 | + | } | |||
| 42 | + | } | |||
M internal/infrastructure/config/defaults.go +1 -0
| @@ -171,6 +171,7 @@ func DefaultConfig() *Config { | |||
| 171 | 171 | // matches Epiphany's model and avoids a misleading setting. | |
| 172 | 172 | CookiePolicy: CookiePolicyAlways, | |
| 173 | 173 | CEF: CEFEngineConfig{ | |
| 174 | + | RenderStack: CEFRenderStackVulkan, | |
| 174 | 175 | WindowlessFrameRate: defaultCEFWindowlessFrameRate, | |
| 175 | 176 | LogFile: "", | |
| 176 | 177 | EnableAudioHandler: true, | |
| @@ -171,6 +171,7 @@ func DefaultConfig() *Config { | |||||
| 171 | // matches Epiphany's model and avoids a misleading setting. | 171 | // matches Epiphany's model and avoids a misleading setting. | ||
| 172 | CookiePolicy: CookiePolicyAlways, | 172 | CookiePolicy: CookiePolicyAlways, | ||
| 173 | CEF: CEFEngineConfig{ | 173 | CEF: CEFEngineConfig{ | ||
| 174 | + | RenderStack: CEFRenderStackVulkan, | |||
| 174 | WindowlessFrameRate: defaultCEFWindowlessFrameRate, | 175 | WindowlessFrameRate: defaultCEFWindowlessFrameRate, | ||
| 175 | LogFile: "", | 176 | LogFile: "", | ||
| 176 | EnableAudioHandler: true, | 177 | EnableAudioHandler: true, | ||
M internal/infrastructure/config/defaults_test.go +1 -0
| @@ -18,6 +18,7 @@ func TestDefaultConfig_CoreDefaults(t *testing.T) { | |||
| 18 | 18 | assert.Equal(t, "webkit", cfg.Engine.Type) | |
| 19 | 19 | assert.Equal(t, ProfileDefault, cfg.Engine.Profile) | |
| 20 | 20 | assert.Equal(t, CookiePolicyAlways, cfg.Engine.CookiePolicy) | |
| 21 | + | assert.Equal(t, CEFRenderStackVulkan, cfg.Engine.CEF.CEFRenderStack()) | |
| 21 | 22 | assert.Equal(t, int32(defaultCEFWindowlessFrameRate), cfg.Engine.CEF.CEFWindowlessFrameRate()) | |
| 22 | 23 | assert.Empty(t, cfg.Engine.CEF.LogFile) | |
| 23 | 24 | assert.True(t, cfg.Engine.CEF.EnableAudioHandler) | |
| @@ -18,6 +18,7 @@ func TestDefaultConfig_CoreDefaults(t *testing.T) { | |||||
| 18 | assert.Equal(t, "webkit", cfg.Engine.Type) | 18 | assert.Equal(t, "webkit", cfg.Engine.Type) | ||
| 19 | assert.Equal(t, ProfileDefault, cfg.Engine.Profile) | 19 | assert.Equal(t, ProfileDefault, cfg.Engine.Profile) | ||
| 20 | assert.Equal(t, CookiePolicyAlways, cfg.Engine.CookiePolicy) | 20 | assert.Equal(t, CookiePolicyAlways, cfg.Engine.CookiePolicy) | ||
| 21 | + | assert.Equal(t, CEFRenderStackVulkan, cfg.Engine.CEF.CEFRenderStack()) | |||
| 21 | assert.Equal(t, int32(defaultCEFWindowlessFrameRate), cfg.Engine.CEF.CEFWindowlessFrameRate()) | 22 | assert.Equal(t, int32(defaultCEFWindowlessFrameRate), cfg.Engine.CEF.CEFWindowlessFrameRate()) | ||
| 22 | assert.Empty(t, cfg.Engine.CEF.LogFile) | 23 | assert.Empty(t, cfg.Engine.CEF.LogFile) | ||
| 23 | assert.True(t, cfg.Engine.CEF.EnableAudioHandler) | 24 | assert.True(t, cfg.Engine.CEF.EnableAudioHandler) | ||
M internal/infrastructure/config/engine.go +18 -0
| @@ -7,6 +7,14 @@ const ( | |||
| 7 | 7 | EngineTypeCEF = "cef" | |
| 8 | 8 | ) | |
| 9 | 9 | ||
| 10 | + | // CEFRenderStack selects Dumber's CEF GPU render stack. | |
| 11 | + | type CEFRenderStack string | |
| 12 | + | ||
| 13 | + | const ( | |
| 14 | + | CEFRenderStackVulkan CEFRenderStack = "vulkan" | |
| 15 | + | CEFRenderStackEGL CEFRenderStack = "egl" | |
| 16 | + | ) | |
| 17 | + | ||
| 10 | 18 | // EngineConfig holds engine selection and universal engine options. | |
| 11 | 19 | type EngineConfig struct { | |
| 12 | 20 | Type string `mapstructure:"type" toml:"type" yaml:"type"` | |
| @@ -90,6 +98,8 @@ type CEFEngineConfig struct { | |||
| 90 | 98 | // LogSeverity controls CEF's internal log verbosity. | |
| 91 | 99 | // 0 = default, 1 = verbose, 2 = info, 3 = warning, 4 = error, 99 = disable. | |
| 92 | 100 | LogSeverity int32 `mapstructure:"log_severity" toml:"log_severity" yaml:"log_severity"` | |
| 101 | + | // RenderStack selects the CEF GPU render stack. Empty falls back to vulkan. | |
| 102 | + | RenderStack CEFRenderStack `mapstructure:"render_stack" toml:"render_stack" yaml:"render_stack"` | |
| 93 | 103 | // WindowlessFrameRate is the maximum frame rate for off-screen rendering. | |
| 94 | 104 | // Default: 60. Higher values increase CPU usage. | |
| 95 | 105 | WindowlessFrameRate int32 `mapstructure:"windowless_frame_rate" toml:"windowless_frame_rate" yaml:"windowless_frame_rate"` | |
| @@ -99,6 +109,14 @@ type CEFEngineConfig struct { | |||
| 99 | 109 | TraceHandlers bool `mapstructure:"trace_handlers" toml:"trace_handlers" yaml:"trace_handlers"` | |
| 100 | 110 | } | |
| 101 | 111 | ||
| 112 | + | // CEFRenderStack returns the effective CEF GPU render stack. | |
| 113 | + | func (c CEFEngineConfig) CEFRenderStack() CEFRenderStack { | |
| 114 | + | if c.RenderStack != "" { | |
| 115 | + | return c.RenderStack | |
| 116 | + | } | |
| 117 | + | return CEFRenderStackVulkan | |
| 118 | + | } | |
| 119 | + | ||
| 102 | 120 | // CEFWindowlessFrameRate returns the effective OSR frame rate. | |
| 103 | 121 | // A non-positive value falls back to the built-in default to avoid accidentally | |
| 104 | 122 | // matching high-refresh monitors and burning CPU while idling. | |
| @@ -7,6 +7,14 @@ const ( | |||||
| 7 | EngineTypeCEF = "cef" | 7 | EngineTypeCEF = "cef" | ||
| 8 | ) | 8 | ) | ||
| 9 | 9 | ||||
| 10 | + | // CEFRenderStack selects Dumber's CEF GPU render stack. | |||
| 11 | + | type CEFRenderStack string | |||
| 12 | + | ||||
| 13 | + | const ( | |||
| 14 | + | CEFRenderStackVulkan CEFRenderStack = "vulkan" | |||
| 15 | + | CEFRenderStackEGL CEFRenderStack = "egl" | |||
| 16 | + | ) | |||
| 17 | + | ||||
| 10 | // EngineConfig holds engine selection and universal engine options. | 18 | // EngineConfig holds engine selection and universal engine options. | ||
| 11 | type EngineConfig struct { | 19 | type EngineConfig struct { | ||
| 12 | Type string `mapstructure:"type" toml:"type" yaml:"type"` | 20 | Type string `mapstructure:"type" toml:"type" yaml:"type"` | ||
| @@ -90,6 +98,8 @@ type CEFEngineConfig struct { | |||||
| 90 | // LogSeverity controls CEF's internal log verbosity. | 98 | // LogSeverity controls CEF's internal log verbosity. | ||
| 91 | // 0 = default, 1 = verbose, 2 = info, 3 = warning, 4 = error, 99 = disable. | 99 | // 0 = default, 1 = verbose, 2 = info, 3 = warning, 4 = error, 99 = disable. | ||
| 92 | LogSeverity int32 `mapstructure:"log_severity" toml:"log_severity" yaml:"log_severity"` | 100 | LogSeverity int32 `mapstructure:"log_severity" toml:"log_severity" yaml:"log_severity"` | ||
| 101 | + | // RenderStack selects the CEF GPU render stack. Empty falls back to vulkan. | |||
| 102 | + | RenderStack CEFRenderStack `mapstructure:"render_stack" toml:"render_stack" yaml:"render_stack"` | |||
| 93 | // WindowlessFrameRate is the maximum frame rate for off-screen rendering. | 103 | // WindowlessFrameRate is the maximum frame rate for off-screen rendering. | ||
| 94 | // Default: 60. Higher values increase CPU usage. | 104 | // Default: 60. Higher values increase CPU usage. | ||
| 95 | WindowlessFrameRate int32 `mapstructure:"windowless_frame_rate" toml:"windowless_frame_rate" yaml:"windowless_frame_rate"` | 105 | WindowlessFrameRate int32 `mapstructure:"windowless_frame_rate" toml:"windowless_frame_rate" yaml:"windowless_frame_rate"` | ||
| @@ -99,6 +109,14 @@ type CEFEngineConfig struct { | |||||
| 99 | TraceHandlers bool `mapstructure:"trace_handlers" toml:"trace_handlers" yaml:"trace_handlers"` | 109 | TraceHandlers bool `mapstructure:"trace_handlers" toml:"trace_handlers" yaml:"trace_handlers"` | ||
| 100 | } | 110 | } | ||
| 101 | 111 | ||||
| 112 | + | // CEFRenderStack returns the effective CEF GPU render stack. | |||
| 113 | + | func (c CEFEngineConfig) CEFRenderStack() CEFRenderStack { | |||
| 114 | + | if c.RenderStack != "" { | |||
| 115 | + | return c.RenderStack | |||
| 116 | + | } | |||
| 117 | + | return CEFRenderStackVulkan | |||
| 118 | + | } | |||
| 119 | + | ||||
| 102 | // CEFWindowlessFrameRate returns the effective OSR frame rate. | 120 | // CEFWindowlessFrameRate returns the effective OSR frame rate. | ||
| 103 | // A non-positive value falls back to the built-in default to avoid accidentally | 121 | // A non-positive value falls back to the built-in default to avoid accidentally | ||
| 104 | // matching high-refresh monitors and burning CPU while idling. | 122 | // matching high-refresh monitors and burning CPU while idling. | ||
M internal/infrastructure/config/loader.go +1 -0
| @@ -691,6 +691,7 @@ func (m *Manager) setEngineDefaults(defaults *Config) { | |||
| 691 | 691 | ||
| 692 | 692 | ce := e.CEF | |
| 693 | 693 | m.viper.SetDefault("engine.cef.cef_dir", ce.CEFDir) | |
| 694 | + | m.viper.SetDefault("engine.cef.render_stack", string(ce.CEFRenderStack())) | |
| 694 | 695 | m.viper.SetDefault("engine.cef.log_file", ce.LogFile) | |
| 695 | 696 | m.viper.SetDefault("engine.cef.log_severity", ce.LogSeverity) | |
| 696 | 697 | m.viper.SetDefault("engine.cef.windowless_frame_rate", ce.CEFWindowlessFrameRate()) | |
| @@ -691,6 +691,7 @@ func (m *Manager) setEngineDefaults(defaults *Config) { | |||||
| 691 | 691 | ||||
| 692 | ce := e.CEF | 692 | ce := e.CEF | ||
| 693 | m.viper.SetDefault("engine.cef.cef_dir", ce.CEFDir) | 693 | m.viper.SetDefault("engine.cef.cef_dir", ce.CEFDir) | ||
| 694 | + | m.viper.SetDefault("engine.cef.render_stack", string(ce.CEFRenderStack())) | |||
| 694 | m.viper.SetDefault("engine.cef.log_file", ce.LogFile) | 695 | m.viper.SetDefault("engine.cef.log_file", ce.LogFile) | ||
| 695 | m.viper.SetDefault("engine.cef.log_severity", ce.LogSeverity) | 696 | m.viper.SetDefault("engine.cef.log_severity", ce.LogSeverity) | ||
| 696 | m.viper.SetDefault("engine.cef.windowless_frame_rate", ce.CEFWindowlessFrameRate()) | 697 | m.viper.SetDefault("engine.cef.windowless_frame_rate", ce.CEFWindowlessFrameRate()) | ||
M internal/infrastructure/config/schema_provider.go +8 -0
| @@ -931,6 +931,14 @@ func (*SchemaProvider) getPerformanceKeys(defaults *Config) []entity.ConfigKeyIn | |||
| 931 | 931 | Range: ">=0", | |
| 932 | 932 | Section: SectionPerformance, | |
| 933 | 933 | }, | |
| 934 | + | { | |
| 935 | + | Key: "engine.cef.render_stack", | |
| 936 | + | Type: "string", | |
| 937 | + | Default: string(defaults.Engine.CEF.CEFRenderStack()), | |
| 938 | + | Description: "CEF GPU render stack", | |
| 939 | + | Values: []string{"vulkan", "egl"}, | |
| 940 | + | Section: SectionPerformance, | |
| 941 | + | }, | |
| 934 | 942 | { | |
| 935 | 943 | Key: "engine.cef.windowless_frame_rate", | |
| 936 | 944 | Type: "int32", | |
| @@ -931,6 +931,14 @@ func (*SchemaProvider) getPerformanceKeys(defaults *Config) []entity.ConfigKeyIn | |||||
| 931 | Range: ">=0", | 931 | Range: ">=0", | ||
| 932 | Section: SectionPerformance, | 932 | Section: SectionPerformance, | ||
| 933 | }, | 933 | }, | ||
| 934 | + | { | |||
| 935 | + | Key: "engine.cef.render_stack", | |||
| 936 | + | Type: "string", | |||
| 937 | + | Default: string(defaults.Engine.CEF.CEFRenderStack()), | |||
| 938 | + | Description: "CEF GPU render stack", | |||
| 939 | + | Values: []string{"vulkan", "egl"}, | |||
| 940 | + | Section: SectionPerformance, | |||
| 941 | + | }, | |||
| 934 | { | 942 | { | ||
| 935 | Key: "engine.cef.windowless_frame_rate", | 943 | Key: "engine.cef.windowless_frame_rate", | ||
| 936 | Type: "int32", | 944 | Type: "int32", | ||
M internal/infrastructure/config/validation.go +9 -0
| @@ -336,6 +336,15 @@ func validateLogging(config *Config) []string { | |||
| 336 | 336 | func validateCEF(config *Config) []string { | |
| 337 | 337 | var validationErrors []string | |
| 338 | 338 | ||
| 339 | + | switch config.Engine.CEF.CEFRenderStack() { | |
| 340 | + | case CEFRenderStackVulkan, CEFRenderStackEGL: | |
| 341 | + | default: | |
| 342 | + | validationErrors = append(validationErrors, fmt.Sprintf( | |
| 343 | + | "engine.cef.render_stack must be one of: vulkan, egl (got: %s)", | |
| 344 | + | config.Engine.CEF.RenderStack, | |
| 345 | + | )) | |
| 346 | + | } | |
| 347 | + | ||
| 339 | 348 | switch config.Engine.CEF.LogSeverity { | |
| 340 | 349 | case 0, 1, 2, 3, 4, cefLogSeverityDisabled: | |
| 341 | 350 | default: | |
| @@ -336,6 +336,15 @@ func validateLogging(config *Config) []string { | |||||
| 336 | func validateCEF(config *Config) []string { | 336 | func validateCEF(config *Config) []string { | ||
| 337 | var validationErrors []string | 337 | var validationErrors []string | ||
| 338 | 338 | ||||
| 339 | + | switch config.Engine.CEF.CEFRenderStack() { | |||
| 340 | + | case CEFRenderStackVulkan, CEFRenderStackEGL: | |||
| 341 | + | default: | |||
| 342 | + | validationErrors = append(validationErrors, fmt.Sprintf( | |||
| 343 | + | "engine.cef.render_stack must be one of: vulkan, egl (got: %s)", | |||
| 344 | + | config.Engine.CEF.RenderStack, | |||
| 345 | + | )) | |||
| 346 | + | } | |||
| 347 | + | ||||
| 339 | switch config.Engine.CEF.LogSeverity { | 348 | switch config.Engine.CEF.LogSeverity { | ||
| 340 | case 0, 1, 2, 3, 4, cefLogSeverityDisabled: | 349 | case 0, 1, 2, 3, 4, cefLogSeverityDisabled: | ||
| 341 | default: | 350 | default: | ||