cef: default to Vulkan dmabuf render stack
- commit
- 3a4bc2a2613b380fd7d16437b92d88e21a171fd2
- author
- brice · · 2026-05-01 13:41 UTC
- parent
- 7f761e3
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
11 files · +169 -25
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 | `auto` | `auto`, `vulkan-dmabuf`, `legacy-gl` | | |
| 62 | + | | `DUMBER_RENDER_STACK` env | string | `vulkan-dmabuf` | `vulkan-dmabuf`, `auto`, `legacy-gl` | | |
| 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 | `auto` | `auto`, `vulkan-dmabuf`, `legacy-gl` | | 62 | + | | `DUMBER_RENDER_STACK` env | string | `vulkan-dmabuf` | `vulkan-dmabuf`, `auto`, `legacy-gl` | |
| 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 go.mod +2 -4
| @@ -7,10 +7,10 @@ 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.12.0 | |
| 10 | - | github.com/bnema/purego-cef2gtk v0.0.0-00010101000000-000000000000 | |
| 10 | + | github.com/bnema/purego-cef2gtk v0.0.0-20260501133909-bb0f269889fd | |
| 11 | 11 | github.com/bnema/purego-pipewire v0.1.2 | |
| 12 | 12 | github.com/bnema/purego-sqlite v0.1.2 | |
| 13 | - | github.com/bnema/puregotk v0.5.1 | |
| 13 | + | github.com/bnema/puregotk v0.5.2-0.20260501133710-759d3a128c40 | |
| 14 | 14 | github.com/charmbracelet/bubbles v1.0.0 | |
| 15 | 15 | github.com/charmbracelet/bubbletea v1.3.10 | |
| 16 | 16 | github.com/charmbracelet/lipgloss v1.1.0 | |
| @@ -78,8 +78,6 @@ require ( | |||
| 78 | 78 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect | |
| 79 | 79 | ) | |
| 80 | 80 | ||
| 81 | - | replace github.com/bnema/purego-cef2gtk => ../purego-cef2gtk | |
| 82 | - | ||
| 83 | 81 | tool github.com/a-h/templ/cmd/templ | |
| 84 | 82 | ||
| 85 | 83 | replace github.com/bnema/purego-pipewire => ../purego-pipewire | |
| @@ -7,10 +7,10 @@ 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.12.0 | 9 | github.com/bnema/purego-cef v0.12.0 | ||
| 10 | - | github.com/bnema/purego-cef2gtk v0.0.0-00010101000000-000000000000 | 10 | + | github.com/bnema/purego-cef2gtk v0.0.0-20260501133909-bb0f269889fd |
| 11 | github.com/bnema/purego-pipewire v0.1.2 | 11 | github.com/bnema/purego-pipewire v0.1.2 | ||
| 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.1 | 13 | + | github.com/bnema/puregotk v0.5.2-0.20260501133710-759d3a128c40 |
| 14 | github.com/charmbracelet/bubbles v1.0.0 | 14 | github.com/charmbracelet/bubbles v1.0.0 | ||
| 15 | github.com/charmbracelet/bubbletea v1.3.10 | 15 | github.com/charmbracelet/bubbletea v1.3.10 | ||
| 16 | github.com/charmbracelet/lipgloss v1.1.0 | 16 | github.com/charmbracelet/lipgloss v1.1.0 | ||
| @@ -78,8 +78,6 @@ require ( | |||||
| 78 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect | 78 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect | ||
| 79 | ) | 79 | ) | ||
| 80 | 80 | ||||
| 81 | - | replace github.com/bnema/purego-cef2gtk => ../purego-cef2gtk | |||
| 82 | - | ||||
| 83 | tool github.com/a-h/templ/cmd/templ | 81 | tool github.com/a-h/templ/cmd/templ | ||
| 84 | 82 | ||||
| 85 | replace github.com/bnema/purego-pipewire => ../purego-pipewire | 83 | replace github.com/bnema/purego-pipewire => ../purego-pipewire | ||
M go.sum +4 -2
| @@ -14,10 +14,12 @@ github.com/bnema/purego v0.11.0-bnema.2 h1:7pzcdOJ8bGtLrCz6V582oyleP6css1ICQoGgj | |||
| 14 | 14 | github.com/bnema/purego v0.11.0-bnema.2/go.mod h1:5BnOIUj+0fvk/vwv45iottFoGfkDT7cnLXnpM2s6Hng= | |
| 15 | 15 | github.com/bnema/purego-cef v0.12.0 h1:zw45+1e5WUBJdRooNIXmf2u/Zc8XFR5Xb0qwG2krpiM= | |
| 16 | 16 | github.com/bnema/purego-cef v0.12.0/go.mod h1:AIW4lrXr4f2vesO4WnMDN49yAyfivYHV4x6hxpu5SmM= | |
| 17 | + | github.com/bnema/purego-cef2gtk v0.0.0-20260501133909-bb0f269889fd h1:mn1M1KOqPDlEraHbwpTIWvBW+vfva7baqkcJ3BSCBh8= | |
| 18 | + | github.com/bnema/purego-cef2gtk v0.0.0-20260501133909-bb0f269889fd/go.mod h1:LCWBipx/vA4SWJgLc46nsiryHugNMLVCn1gOD+CkJ5Q= | |
| 17 | 19 | github.com/bnema/purego-sqlite v0.1.2 h1:dhqfe3KfNRBh7tlrwxL7bf4FPZk2VCajdBESMbrvmoE= | |
| 18 | 20 | github.com/bnema/purego-sqlite v0.1.2/go.mod h1:fCvocfke59pjLrm5+y7aCQ3D5ZkiZbygLLJHkoezmRI= | |
| 19 | - | github.com/bnema/puregotk v0.5.1 h1:Qgk12PHu1P5bsVD3tZ0io2a9wn3+dN60qZvKnkXASY0= | |
| 20 | - | github.com/bnema/puregotk v0.5.1/go.mod h1:k9obstQtCopDsU4677xV9xJcMXMLbESRD5re7NAi7BM= | |
| 21 | + | github.com/bnema/puregotk v0.5.2-0.20260501133710-759d3a128c40 h1:I/EXDqR5yOeMkhuFo9JoEEhVqc0/OiodGSMYNCGPISE= | |
| 22 | + | github.com/bnema/puregotk v0.5.2-0.20260501133710-759d3a128c40/go.mod h1:k9obstQtCopDsU4677xV9xJcMXMLbESRD5re7NAi7BM= | |
| 21 | 23 | github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= | |
| 22 | 24 | github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= | |
| 23 | 25 | github.com/charmbracelet/bubbles v1.0.0 h1:12J8/ak/uCZEMQ6KU7pcfwceyjLlWsDLAxB5fXonfvc= | |
| @@ -14,10 +14,12 @@ github.com/bnema/purego v0.11.0-bnema.2 h1:7pzcdOJ8bGtLrCz6V582oyleP6css1ICQoGgj | |||||
| 14 | github.com/bnema/purego v0.11.0-bnema.2/go.mod h1:5BnOIUj+0fvk/vwv45iottFoGfkDT7cnLXnpM2s6Hng= | 14 | github.com/bnema/purego v0.11.0-bnema.2/go.mod h1:5BnOIUj+0fvk/vwv45iottFoGfkDT7cnLXnpM2s6Hng= | ||
| 15 | github.com/bnema/purego-cef v0.12.0 h1:zw45+1e5WUBJdRooNIXmf2u/Zc8XFR5Xb0qwG2krpiM= | 15 | github.com/bnema/purego-cef v0.12.0 h1:zw45+1e5WUBJdRooNIXmf2u/Zc8XFR5Xb0qwG2krpiM= | ||
| 16 | github.com/bnema/purego-cef v0.12.0/go.mod h1:AIW4lrXr4f2vesO4WnMDN49yAyfivYHV4x6hxpu5SmM= | 16 | github.com/bnema/purego-cef v0.12.0/go.mod h1:AIW4lrXr4f2vesO4WnMDN49yAyfivYHV4x6hxpu5SmM= | ||
| 17 | + | github.com/bnema/purego-cef2gtk v0.0.0-20260501133909-bb0f269889fd h1:mn1M1KOqPDlEraHbwpTIWvBW+vfva7baqkcJ3BSCBh8= | |||
| 18 | + | github.com/bnema/purego-cef2gtk v0.0.0-20260501133909-bb0f269889fd/go.mod h1:LCWBipx/vA4SWJgLc46nsiryHugNMLVCn1gOD+CkJ5Q= | |||
| 17 | github.com/bnema/purego-sqlite v0.1.2 h1:dhqfe3KfNRBh7tlrwxL7bf4FPZk2VCajdBESMbrvmoE= | 19 | github.com/bnema/purego-sqlite v0.1.2 h1:dhqfe3KfNRBh7tlrwxL7bf4FPZk2VCajdBESMbrvmoE= | ||
| 18 | github.com/bnema/purego-sqlite v0.1.2/go.mod h1:fCvocfke59pjLrm5+y7aCQ3D5ZkiZbygLLJHkoezmRI= | 20 | github.com/bnema/purego-sqlite v0.1.2/go.mod h1:fCvocfke59pjLrm5+y7aCQ3D5ZkiZbygLLJHkoezmRI= | ||
| 19 | - | github.com/bnema/puregotk v0.5.1 h1:Qgk12PHu1P5bsVD3tZ0io2a9wn3+dN60qZvKnkXASY0= | 21 | + | github.com/bnema/puregotk v0.5.2-0.20260501133710-759d3a128c40 h1:I/EXDqR5yOeMkhuFo9JoEEhVqc0/OiodGSMYNCGPISE= |
| 20 | - | github.com/bnema/puregotk v0.5.1/go.mod h1:k9obstQtCopDsU4677xV9xJcMXMLbESRD5re7NAi7BM= | 22 | + | github.com/bnema/puregotk v0.5.2-0.20260501133710-759d3a128c40/go.mod h1:k9obstQtCopDsU4677xV9xJcMXMLbESRD5re7NAi7BM= |
| 21 | github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= | 23 | github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= | ||
| 22 | github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= | 24 | github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= | ||
| 23 | github.com/charmbracelet/bubbles v1.0.0 h1:12J8/ak/uCZEMQ6KU7pcfwceyjLlWsDLAxB5fXonfvc= | 25 | github.com/charmbracelet/bubbles v1.0.0 h1:12J8/ak/uCZEMQ6KU7pcfwceyjLlWsDLAxB5fXonfvc= | ||
M internal/infrastructure/cef/app.go +24 -1
| @@ -15,6 +15,7 @@ const ( | |||
| 15 | 15 | chromiumEnableFeaturesSwitch = "enable-features" | |
| 16 | 16 | chromiumDisableFeaturesSwitch = "disable-features" | |
| 17 | 17 | chromiumDisableBlinkFeaturesSwitch = "disable-blink-features" | |
| 18 | + | chromiumRenderNodeOverrideSwitch = "render-node-override" | |
| 18 | 19 | ) | |
| 19 | 20 | ||
| 20 | 21 | // Chromium's core Blink runtime flag for the Web Authentication API is | |
| @@ -60,6 +61,7 @@ func configureCommandLine(commandLine purecef.CommandLine) { | |||
| 60 | 61 | ||
| 61 | 62 | configureHardwareVideoDecode(commandLine) | |
| 62 | 63 | configureEnvChromiumFlags(commandLine) | |
| 64 | + | configureRenderNodeOverride(commandLine) | |
| 63 | 65 | configureWebAuthnFeaturePolicy(commandLine) | |
| 64 | 66 | } | |
| 65 | 67 | ||
| @@ -90,6 +92,23 @@ func configureEnvChromiumFlags(commandLine purecef.CommandLine) { | |||
| 90 | 92 | } | |
| 91 | 93 | } | |
| 92 | 94 | ||
| 95 | + | func configureRenderNodeOverride(commandLine purecef.CommandLine) { | |
| 96 | + | if commandLine == nil { | |
| 97 | + | return | |
| 98 | + | } | |
| 99 | + | value := strings.TrimSpace(os.Getenv(cefRenderNodeEnvVar)) | |
| 100 | + | if value == "" { | |
| 101 | + | return | |
| 102 | + | } | |
| 103 | + | switch strings.ToLower(value) { | |
| 104 | + | case "auto", "default", "none", "off", "disable", "disabled": | |
| 105 | + | commandLine.RemoveSwitch(chromiumRenderNodeOverrideSwitch) | |
| 106 | + | return | |
| 107 | + | } | |
| 108 | + | commandLine.RemoveSwitch(chromiumRenderNodeOverrideSwitch) | |
| 109 | + | commandLine.AppendSwitchWithValue(chromiumRenderNodeOverrideSwitch, value) | |
| 110 | + | } | |
| 111 | + | ||
| 93 | 112 | func parseChromiumFlagsEnv(raw string) []string { | |
| 94 | 113 | // Reuse Dumber's command-line tokenizer so developer env flags can contain | |
| 95 | 114 | // quoted values without adding a second shell-like parser in the CEF adapter. | |
| @@ -288,6 +307,7 @@ func (a *dumberApp) OnBeforeCommandLineProcessing(processType string, commandLin | |||
| 288 | 307 | } | |
| 289 | 308 | } | |
| 290 | 309 | ||
| 310 | + | renderNodeOverride := commandLine.GetSwitchValue(chromiumRenderNodeOverrideSwitch) | |
| 291 | 311 | cmdline := commandLine.GetCommandLineString() | |
| 292 | 312 | if len(cmdline) > maxCmdLineLogLen { | |
| 293 | 313 | runes := []rune(cmdline) | |
| @@ -297,6 +317,7 @@ func (a *dumberApp) OnBeforeCommandLineProcessing(processType string, commandLin | |||
| 297 | 317 | } | |
| 298 | 318 | log.Debug(). | |
| 299 | 319 | Str("process_type", processType). | |
| 320 | + | Str("render_node_override", renderNodeOverride). | |
| 300 | 321 | Str("command_line", cmdline). | |
| 301 | 322 | Msg("cef: OnBeforeCommandLineProcessing") | |
| 302 | 323 | } | |
| @@ -337,6 +358,7 @@ func (h *dumberBPH) OnBeforeChildProcessLaunch(commandLine purecef.CommandLine) | |||
| 337 | 358 | commandLineString := "" | |
| 338 | 359 | useAngle := "" | |
| 339 | 360 | ozonePlatform := "" | |
| 361 | + | renderNodeOverride := "" | |
| 340 | 362 | if commandLine != nil { | |
| 341 | 363 | configureCommandLine(commandLine) | |
| 342 | 364 | appendSwitchIfMissing(commandLine, "no-zygote") | |
| @@ -344,8 +366,9 @@ func (h *dumberBPH) OnBeforeChildProcessLaunch(commandLine purecef.CommandLine) | |||
| 344 | 366 | commandLineString = commandLine.GetCommandLineString() | |
| 345 | 367 | useAngle = commandLine.GetSwitchValue("use-angle") | |
| 346 | 368 | ozonePlatform = commandLine.GetSwitchValue("ozone-platform") | |
| 369 | + | renderNodeOverride = commandLine.GetSwitchValue(chromiumRenderNodeOverrideSwitch) | |
| 347 | 370 | } | |
| 348 | - | h.engine.recordChildProcessLaunch(processType, useAngle, ozonePlatform, commandLineString) | |
| 371 | + | h.engine.recordChildProcessLaunch(processType, useAngle, ozonePlatform, renderNodeOverride, commandLineString) | |
| 349 | 372 | } | |
| 350 | 373 | ||
| 351 | 374 | func appendSwitchIfMissing(commandLine purecef.CommandLine, name string) { | |
| @@ -15,6 +15,7 @@ const ( | |||||
| 15 | chromiumEnableFeaturesSwitch = "enable-features" | 15 | chromiumEnableFeaturesSwitch = "enable-features" | ||
| 16 | chromiumDisableFeaturesSwitch = "disable-features" | 16 | chromiumDisableFeaturesSwitch = "disable-features" | ||
| 17 | chromiumDisableBlinkFeaturesSwitch = "disable-blink-features" | 17 | chromiumDisableBlinkFeaturesSwitch = "disable-blink-features" | ||
| 18 | + | chromiumRenderNodeOverrideSwitch = "render-node-override" | |||
| 18 | ) | 19 | ) | ||
| 19 | 20 | ||||
| 20 | // Chromium's core Blink runtime flag for the Web Authentication API is | 21 | // Chromium's core Blink runtime flag for the Web Authentication API is | ||
| @@ -60,6 +61,7 @@ func configureCommandLine(commandLine purecef.CommandLine) { | |||||
| 60 | 61 | ||||
| 61 | configureHardwareVideoDecode(commandLine) | 62 | configureHardwareVideoDecode(commandLine) | ||
| 62 | configureEnvChromiumFlags(commandLine) | 63 | configureEnvChromiumFlags(commandLine) | ||
| 64 | + | configureRenderNodeOverride(commandLine) | |||
| 63 | configureWebAuthnFeaturePolicy(commandLine) | 65 | configureWebAuthnFeaturePolicy(commandLine) | ||
| 64 | } | 66 | } | ||
| 65 | 67 | ||||
| @@ -90,6 +92,23 @@ func configureEnvChromiumFlags(commandLine purecef.CommandLine) { | |||||
| 90 | } | 92 | } | ||
| 91 | } | 93 | } | ||
| 92 | 94 | ||||
| 95 | + | func configureRenderNodeOverride(commandLine purecef.CommandLine) { | |||
| 96 | + | if commandLine == nil { | |||
| 97 | + | return | |||
| 98 | + | } | |||
| 99 | + | value := strings.TrimSpace(os.Getenv(cefRenderNodeEnvVar)) | |||
| 100 | + | if value == "" { | |||
| 101 | + | return | |||
| 102 | + | } | |||
| 103 | + | switch strings.ToLower(value) { | |||
| 104 | + | case "auto", "default", "none", "off", "disable", "disabled": | |||
| 105 | + | commandLine.RemoveSwitch(chromiumRenderNodeOverrideSwitch) | |||
| 106 | + | return | |||
| 107 | + | } | |||
| 108 | + | commandLine.RemoveSwitch(chromiumRenderNodeOverrideSwitch) | |||
| 109 | + | commandLine.AppendSwitchWithValue(chromiumRenderNodeOverrideSwitch, value) | |||
| 110 | + | } | |||
| 111 | + | ||||
| 93 | func parseChromiumFlagsEnv(raw string) []string { | 112 | func parseChromiumFlagsEnv(raw string) []string { | ||
| 94 | // Reuse Dumber's command-line tokenizer so developer env flags can contain | 113 | // Reuse Dumber's command-line tokenizer so developer env flags can contain | ||
| 95 | // quoted values without adding a second shell-like parser in the CEF adapter. | 114 | // quoted values without adding a second shell-like parser in the CEF adapter. | ||
| @@ -288,6 +307,7 @@ func (a *dumberApp) OnBeforeCommandLineProcessing(processType string, commandLin | |||||
| 288 | } | 307 | } | ||
| 289 | } | 308 | } | ||
| 290 | 309 | ||||
| 310 | + | renderNodeOverride := commandLine.GetSwitchValue(chromiumRenderNodeOverrideSwitch) | |||
| 291 | cmdline := commandLine.GetCommandLineString() | 311 | cmdline := commandLine.GetCommandLineString() | ||
| 292 | if len(cmdline) > maxCmdLineLogLen { | 312 | if len(cmdline) > maxCmdLineLogLen { | ||
| 293 | runes := []rune(cmdline) | 313 | runes := []rune(cmdline) | ||
| @@ -297,6 +317,7 @@ func (a *dumberApp) OnBeforeCommandLineProcessing(processType string, commandLin | |||||
| 297 | } | 317 | } | ||
| 298 | log.Debug(). | 318 | log.Debug(). | ||
| 299 | Str("process_type", processType). | 319 | Str("process_type", processType). | ||
| 320 | + | Str("render_node_override", renderNodeOverride). | |||
| 300 | Str("command_line", cmdline). | 321 | Str("command_line", cmdline). | ||
| 301 | Msg("cef: OnBeforeCommandLineProcessing") | 322 | Msg("cef: OnBeforeCommandLineProcessing") | ||
| 302 | } | 323 | } | ||
| @@ -337,6 +358,7 @@ func (h *dumberBPH) OnBeforeChildProcessLaunch(commandLine purecef.CommandLine) | |||||
| 337 | commandLineString := "" | 358 | commandLineString := "" | ||
| 338 | useAngle := "" | 359 | useAngle := "" | ||
| 339 | ozonePlatform := "" | 360 | ozonePlatform := "" | ||
| 361 | + | renderNodeOverride := "" | |||
| 340 | if commandLine != nil { | 362 | if commandLine != nil { | ||
| 341 | configureCommandLine(commandLine) | 363 | configureCommandLine(commandLine) | ||
| 342 | appendSwitchIfMissing(commandLine, "no-zygote") | 364 | appendSwitchIfMissing(commandLine, "no-zygote") | ||
| @@ -344,8 +366,9 @@ func (h *dumberBPH) OnBeforeChildProcessLaunch(commandLine purecef.CommandLine) | |||||
| 344 | commandLineString = commandLine.GetCommandLineString() | 366 | commandLineString = commandLine.GetCommandLineString() | ||
| 345 | useAngle = commandLine.GetSwitchValue("use-angle") | 367 | useAngle = commandLine.GetSwitchValue("use-angle") | ||
| 346 | ozonePlatform = commandLine.GetSwitchValue("ozone-platform") | 368 | ozonePlatform = commandLine.GetSwitchValue("ozone-platform") | ||
| 369 | + | renderNodeOverride = commandLine.GetSwitchValue(chromiumRenderNodeOverrideSwitch) | |||
| 347 | } | 370 | } | ||
| 348 | - | h.engine.recordChildProcessLaunch(processType, useAngle, ozonePlatform, commandLineString) | 371 | + | h.engine.recordChildProcessLaunch(processType, useAngle, ozonePlatform, renderNodeOverride, commandLineString) |
| 349 | } | 372 | } | ||
| 350 | 373 | ||||
| 351 | func appendSwitchIfMissing(commandLine purecef.CommandLine, name string) { | 374 | func appendSwitchIfMissing(commandLine purecef.CommandLine, name string) { | ||
M internal/infrastructure/cef/app_test.go +30 -0
| @@ -433,6 +433,36 @@ func TestDumberBPH_OnBeforeChildProcessLaunch_AppendsNoZygote(t *testing.T) { | |||
| 433 | 433 | } | |
| 434 | 434 | } | |
| 435 | 435 | ||
| 436 | + | func TestConfigureCommandLine_CEFRenderNodeEnvOverridesExistingRenderNode(t *testing.T) { | |
| 437 | + | t.Setenv(cefRenderNodeEnvVar, "/dev/dri/renderD129") | |
| 438 | + | ||
| 439 | + | commandLine := newMutableCommandLineStub() | |
| 440 | + | commandLine.AppendSwitchWithValue(chromiumRenderNodeOverrideSwitch, "/dev/dri/renderD128") | |
| 441 | + | ||
| 442 | + | configureCommandLine(commandLine) | |
| 443 | + | ||
| 444 | + | if got := commandLine.GetSwitchValue(chromiumRenderNodeOverrideSwitch); got != "/dev/dri/renderD129" { | |
| 445 | + | t.Fatalf("render node override = %q, want /dev/dri/renderD129", got) | |
| 446 | + | } | |
| 447 | + | } | |
| 448 | + | ||
| 449 | + | func TestConfigureCommandLine_CEFRenderNodeEnvSentinelsRemoveExistingRenderNode(t *testing.T) { | |
| 450 | + | for _, value := range []string{"auto", "default", "none", "off", "disable", "disabled"} { | |
| 451 | + | t.Run(value, func(t *testing.T) { | |
| 452 | + | t.Setenv(cefRenderNodeEnvVar, value) | |
| 453 | + | ||
| 454 | + | commandLine := newMutableCommandLineStub() | |
| 455 | + | commandLine.AppendSwitchWithValue(chromiumRenderNodeOverrideSwitch, "/dev/dri/renderD128") | |
| 456 | + | ||
| 457 | + | configureCommandLine(commandLine) | |
| 458 | + | ||
| 459 | + | if commandLine.HasSwitch(chromiumRenderNodeOverrideSwitch) { | |
| 460 | + | t.Fatalf("render node override still present: %q", commandLine.GetSwitchValue(chromiumRenderNodeOverrideSwitch)) | |
| 461 | + | } | |
| 462 | + | }) | |
| 463 | + | } | |
| 464 | + | } | |
| 465 | + | ||
| 436 | 466 | func TestDumberBPH_OnBeforeChildProcessLaunch_AppliesRenderingAndHardwareDecodeFlags(t *testing.T) { | |
| 437 | 467 | t.Setenv(cef2gtkAngleBackendVar, "vulkan") | |
| 438 | 468 | t.Setenv(cefEnableVAAPIEnvVar, "1") | |
| @@ -433,6 +433,36 @@ func TestDumberBPH_OnBeforeChildProcessLaunch_AppendsNoZygote(t *testing.T) { | |||||
| 433 | } | 433 | } | ||
| 434 | } | 434 | } | ||
| 435 | 435 | ||||
| 436 | + | func TestConfigureCommandLine_CEFRenderNodeEnvOverridesExistingRenderNode(t *testing.T) { | |||
| 437 | + | t.Setenv(cefRenderNodeEnvVar, "/dev/dri/renderD129") | |||
| 438 | + | ||||
| 439 | + | commandLine := newMutableCommandLineStub() | |||
| 440 | + | commandLine.AppendSwitchWithValue(chromiumRenderNodeOverrideSwitch, "/dev/dri/renderD128") | |||
| 441 | + | ||||
| 442 | + | configureCommandLine(commandLine) | |||
| 443 | + | ||||
| 444 | + | if got := commandLine.GetSwitchValue(chromiumRenderNodeOverrideSwitch); got != "/dev/dri/renderD129" { | |||
| 445 | + | t.Fatalf("render node override = %q, want /dev/dri/renderD129", got) | |||
| 446 | + | } | |||
| 447 | + | } | |||
| 448 | + | ||||
| 449 | + | func TestConfigureCommandLine_CEFRenderNodeEnvSentinelsRemoveExistingRenderNode(t *testing.T) { | |||
| 450 | + | for _, value := range []string{"auto", "default", "none", "off", "disable", "disabled"} { | |||
| 451 | + | t.Run(value, func(t *testing.T) { | |||
| 452 | + | t.Setenv(cefRenderNodeEnvVar, value) | |||
| 453 | + | ||||
| 454 | + | commandLine := newMutableCommandLineStub() | |||
| 455 | + | commandLine.AppendSwitchWithValue(chromiumRenderNodeOverrideSwitch, "/dev/dri/renderD128") | |||
| 456 | + | ||||
| 457 | + | configureCommandLine(commandLine) | |||
| 458 | + | ||||
| 459 | + | if commandLine.HasSwitch(chromiumRenderNodeOverrideSwitch) { | |||
| 460 | + | t.Fatalf("render node override still present: %q", commandLine.GetSwitchValue(chromiumRenderNodeOverrideSwitch)) | |||
| 461 | + | } | |||
| 462 | + | }) | |||
| 463 | + | } | |||
| 464 | + | } | |||
| 465 | + | ||||
| 436 | func TestDumberBPH_OnBeforeChildProcessLaunch_AppliesRenderingAndHardwareDecodeFlags(t *testing.T) { | 466 | func TestDumberBPH_OnBeforeChildProcessLaunch_AppliesRenderingAndHardwareDecodeFlags(t *testing.T) { | ||
| 437 | t.Setenv(cef2gtkAngleBackendVar, "vulkan") | 467 | t.Setenv(cef2gtkAngleBackendVar, "vulkan") | ||
| 438 | t.Setenv(cefEnableVAAPIEnvVar, "1") | 468 | t.Setenv(cefEnableVAAPIEnvVar, "1") | ||
M internal/infrastructure/cef/engine.go +2 -1
| @@ -409,7 +409,7 @@ func (e *Engine) recordContextInitialized() { | |||
| 409 | 409 | Msg("cef: OnContextInitialized") | |
| 410 | 410 | } | |
| 411 | 411 | ||
| 412 | - | func (e *Engine) recordChildProcessLaunch(processType, useAngle, ozonePlatform, commandLine string) { | |
| 412 | + | func (e *Engine) recordChildProcessLaunch(processType, useAngle, ozonePlatform, renderNodeOverride, commandLine string) { | |
| 413 | 413 | switch processType { | |
| 414 | 414 | case "renderer": | |
| 415 | 415 | e.childLaunchRenderer.Add(1) | |
| @@ -423,6 +423,7 @@ func (e *Engine) recordChildProcessLaunch(processType, useAngle, ozonePlatform, | |||
| 423 | 423 | Str("process_type", processType). | |
| 424 | 424 | Str("use_angle", useAngle). | |
| 425 | 425 | Str("ozone_platform", ozonePlatform). | |
| 426 | + | Str("render_node_override", renderNodeOverride). | |
| 426 | 427 | Str("command_line", commandLine). | |
| 427 | 428 | Uint64("renderer_launches", e.childLaunchRenderer.Load()). | |
| 428 | 429 | Uint64("gpu_launches", e.childLaunchGPU.Load()). | |
| @@ -409,7 +409,7 @@ func (e *Engine) recordContextInitialized() { | |||||
| 409 | Msg("cef: OnContextInitialized") | 409 | Msg("cef: OnContextInitialized") | ||
| 410 | } | 410 | } | ||
| 411 | 411 | ||||
| 412 | - | func (e *Engine) recordChildProcessLaunch(processType, useAngle, ozonePlatform, commandLine string) { | 412 | + | func (e *Engine) recordChildProcessLaunch(processType, useAngle, ozonePlatform, renderNodeOverride, commandLine string) { |
| 413 | switch processType { | 413 | switch processType { | ||
| 414 | case "renderer": | 414 | case "renderer": | ||
| 415 | e.childLaunchRenderer.Add(1) | 415 | e.childLaunchRenderer.Add(1) | ||
| @@ -423,6 +423,7 @@ func (e *Engine) recordChildProcessLaunch(processType, useAngle, ozonePlatform, | |||||
| 423 | Str("process_type", processType). | 423 | Str("process_type", processType). | ||
| 424 | Str("use_angle", useAngle). | 424 | Str("use_angle", useAngle). | ||
| 425 | Str("ozone_platform", ozonePlatform). | 425 | Str("ozone_platform", ozonePlatform). | ||
| 426 | + | Str("render_node_override", renderNodeOverride). | |||
| 426 | Str("command_line", commandLine). | 427 | Str("command_line", commandLine). | ||
| 427 | Uint64("renderer_launches", e.childLaunchRenderer.Load()). | 428 | Uint64("renderer_launches", e.childLaunchRenderer.Load()). | ||
| 428 | Uint64("gpu_launches", e.childLaunchGPU.Load()). | 429 | Uint64("gpu_launches", e.childLaunchGPU.Load()). | ||
M internal/infrastructure/cef/env.go +1 -0
| @@ -10,6 +10,7 @@ const ( | |||
| 10 | 10 | cefEnableWebAuthnUnsafeEnvVar = "DUMBER_CEF_ENABLE_WEBAUTHN_UNSAFE" | |
| 11 | 11 | cefChromiumFlagsEnvVar = "DUMBER_CEF_CHROMIUM_FLAGS" | |
| 12 | 12 | cefEnableVAAPIEnvVar = "DUMBER_CEF_ENABLE_VAAPI" | |
| 13 | + | cefRenderNodeEnvVar = "DUMBER_CEF_RENDER_NODE" | |
| 13 | 14 | cefRenderStallRecoveryEnvVar = "DUMBER_CEF_RENDER_STALL_RECOVERY" | |
| 14 | 15 | cefRenderStallBacktraceEnvVar = "DUMBER_CEF_RENDER_STALL_BACKTRACE" | |
| 15 | 16 | ) | |
| @@ -10,6 +10,7 @@ const ( | |||||
| 10 | cefEnableWebAuthnUnsafeEnvVar = "DUMBER_CEF_ENABLE_WEBAUTHN_UNSAFE" | 10 | cefEnableWebAuthnUnsafeEnvVar = "DUMBER_CEF_ENABLE_WEBAUTHN_UNSAFE" | ||
| 11 | cefChromiumFlagsEnvVar = "DUMBER_CEF_CHROMIUM_FLAGS" | 11 | cefChromiumFlagsEnvVar = "DUMBER_CEF_CHROMIUM_FLAGS" | ||
| 12 | cefEnableVAAPIEnvVar = "DUMBER_CEF_ENABLE_VAAPI" | 12 | cefEnableVAAPIEnvVar = "DUMBER_CEF_ENABLE_VAAPI" | ||
| 13 | + | cefRenderNodeEnvVar = "DUMBER_CEF_RENDER_NODE" | |||
| 13 | cefRenderStallRecoveryEnvVar = "DUMBER_CEF_RENDER_STALL_RECOVERY" | 14 | cefRenderStallRecoveryEnvVar = "DUMBER_CEF_RENDER_STALL_RECOVERY" | ||
| 14 | cefRenderStallBacktraceEnvVar = "DUMBER_CEF_RENDER_STALL_BACKTRACE" | 15 | cefRenderStallBacktraceEnvVar = "DUMBER_CEF_RENDER_STALL_BACKTRACE" | ||
| 15 | ) | 16 | ) | ||
M internal/infrastructure/cef/render_diagnostics.go +13 -0
| @@ -81,11 +81,24 @@ func (wv *WebView) logRenderDiagnosticSnapshot( | |||
| 81 | 81 | Uint64("audio_packets", wv.audioPacketCount.Load()). | |
| 82 | 82 | Uint64("audio_writes", wv.audioWriteCount.Load()). | |
| 83 | 83 | Int64("last_accelerated_paint_age_ms", durationMillis(lastPaintAge)). | |
| 84 | + | Str("render_backend", diag.Backend). | |
| 84 | 85 | Int("accelerated_paints", diag.AcceleratedPaints). | |
| 85 | 86 | Int("unsupported_paints", diag.UnsupportedPaints). | |
| 86 | 87 | Int("accelerated_paint_errors", diag.AcceleratedPaintErrors). | |
| 87 | 88 | Int("import_failures", diag.ImportFailures). | |
| 88 | 89 | Int("render_failures", diag.RenderFailures). | |
| 90 | + | Int("textures_built", diag.TexturesBuilt). | |
| 91 | + | Int("texture_build_failures", diag.TextureBuildFailures). | |
| 92 | + | Int("fd_dup_failures", diag.FDDupFailures). | |
| 93 | + | Int("unsupported_formats", diag.UnsupportedFormats). | |
| 94 | + | Int("paintable_swaps", diag.PaintableSwaps). | |
| 95 | + | Bool("pending_frame", diag.PendingFrame). | |
| 96 | + | Bool("pending_scheduled", diag.PendingScheduled). | |
| 97 | + | Int64("pending_age_ms", durationMillis(diag.PendingAge)). | |
| 98 | + | Uint("pending_source_id", diag.PendingSourceID). | |
| 99 | + | Int("pending_reschedules", diag.PendingReschedules). | |
| 100 | + | Int("pending_schedule_failures", diag.PendingScheduleFailures). | |
| 101 | + | Int("pending_idle_callbacks", diag.PendingIdleCallbacks). | |
| 89 | 102 | Int32("surface_width", width). | |
| 90 | 103 | Int32("surface_height", height). | |
| 91 | 104 | Float32("surface_scale", scale). | |
| @@ -81,11 +81,24 @@ func (wv *WebView) logRenderDiagnosticSnapshot( | |||||
| 81 | Uint64("audio_packets", wv.audioPacketCount.Load()). | 81 | Uint64("audio_packets", wv.audioPacketCount.Load()). | ||
| 82 | Uint64("audio_writes", wv.audioWriteCount.Load()). | 82 | Uint64("audio_writes", wv.audioWriteCount.Load()). | ||
| 83 | Int64("last_accelerated_paint_age_ms", durationMillis(lastPaintAge)). | 83 | Int64("last_accelerated_paint_age_ms", durationMillis(lastPaintAge)). | ||
| 84 | + | Str("render_backend", diag.Backend). | |||
| 84 | Int("accelerated_paints", diag.AcceleratedPaints). | 85 | Int("accelerated_paints", diag.AcceleratedPaints). | ||
| 85 | Int("unsupported_paints", diag.UnsupportedPaints). | 86 | Int("unsupported_paints", diag.UnsupportedPaints). | ||
| 86 | Int("accelerated_paint_errors", diag.AcceleratedPaintErrors). | 87 | Int("accelerated_paint_errors", diag.AcceleratedPaintErrors). | ||
| 87 | Int("import_failures", diag.ImportFailures). | 88 | Int("import_failures", diag.ImportFailures). | ||
| 88 | Int("render_failures", diag.RenderFailures). | 89 | Int("render_failures", diag.RenderFailures). | ||
| 90 | + | Int("textures_built", diag.TexturesBuilt). | |||
| 91 | + | Int("texture_build_failures", diag.TextureBuildFailures). | |||
| 92 | + | Int("fd_dup_failures", diag.FDDupFailures). | |||
| 93 | + | Int("unsupported_formats", diag.UnsupportedFormats). | |||
| 94 | + | Int("paintable_swaps", diag.PaintableSwaps). | |||
| 95 | + | Bool("pending_frame", diag.PendingFrame). | |||
| 96 | + | Bool("pending_scheduled", diag.PendingScheduled). | |||
| 97 | + | Int64("pending_age_ms", durationMillis(diag.PendingAge)). | |||
| 98 | + | Uint("pending_source_id", diag.PendingSourceID). | |||
| 99 | + | Int("pending_reschedules", diag.PendingReschedules). | |||
| 100 | + | Int("pending_schedule_failures", diag.PendingScheduleFailures). | |||
| 101 | + | Int("pending_idle_callbacks", diag.PendingIdleCallbacks). | |||
| 89 | Int32("surface_width", width). | 102 | Int32("surface_width", width). | ||
| 90 | Int32("surface_height", height). | 103 | Int32("surface_height", height). | ||
| 91 | Float32("surface_scale", scale). | 104 | Float32("surface_scale", scale). | ||
M internal/infrastructure/cef/render_stack_env.go +54 -15
| @@ -12,7 +12,8 @@ import ( | |||
| 12 | 12 | ) | |
| 13 | 13 | ||
| 14 | 14 | const ( | |
| 15 | - | dumberRenderStackEnvVar = "DUMBER_RENDER_STACK" | |
| 15 | + | dumberRenderStackEnvVar = "DUMBER_RENDER_STACK" | |
| 16 | + | dumberRenderStackAllowSplitEnvVar = "DUMBER_RENDER_STACK_ALLOW_SPLIT" | |
| 16 | 17 | ||
| 17 | 18 | renderStackAuto = "auto" | |
| 18 | 19 | renderStackVulkanDMABUF = "vulkan-dmabuf" | |
| @@ -32,29 +33,45 @@ const ( | |||
| 32 | 33 | // Dumber's default CEF presentation stack before GTK/libadwaita initialize. | |
| 33 | 34 | // | |
| 34 | 35 | // DUMBER_RENDER_STACK accepts: | |
| 35 | - | // - auto or empty: use the GPU-first GDK DMABUF stack | |
| 36 | - | // - vulkan-dmabuf: force GDK DMABUF presentation with GSK Vulkan and CEF ANGLE Vulkan | |
| 36 | + | // - empty/default or vulkan-dmabuf: use GDK DMABUF presentation with GSK Vulkan and CEF ANGLE Vulkan | |
| 37 | + | // - auto: alias for vulkan-dmabuf | |
| 37 | 38 | // - legacy-gl: force the older GtkGLArea/OpenGL bridge for diagnostics | |
| 38 | 39 | // | |
| 39 | - | // Low-level env vars remain diagnostic escape hatches: when explicitly set, | |
| 40 | - | // they are preserved instead of overwritten. | |
| 40 | + | // Low-level env vars are treated as library-development escape hatches only. | |
| 41 | + | // Dumber keeps the selected stack coherent by default and overwrites conflicts; | |
| 42 | + | // set DUMBER_RENDER_STACK_ALLOW_SPLIT=1 to preserve explicit split-stack values. | |
| 41 | 43 | func ApplyDefaultRenderStackEnvironment(ctx context.Context) string { | |
| 42 | 44 | return applyDefaultRenderStackEnvironment(ctx) | |
| 43 | 45 | } | |
| 44 | 46 | ||
| 45 | 47 | func applyDefaultRenderStackEnvironment(ctx context.Context) string { | |
| 46 | - | switch normalizeRenderStack(os.Getenv(dumberRenderStackEnvVar)) { | |
| 48 | + | stack := normalizeRenderStack(os.Getenv(dumberRenderStackEnvVar)) | |
| 49 | + | switch stack { | |
| 47 | 50 | case renderStackLegacyGL: | |
| 48 | - | setEnvDefault(ctx, gskRendererEnvVar, gskRendererOpenGL) | |
| 49 | - | setEnvDefault(ctx, cef2gtkBackendEnvVar, cef2gtkBackendGLArea) | |
| 50 | - | setEnvDefault(ctx, cef2gtkAngleBackendVar, cef2gtkAngleGLEGL) | |
| 51 | - | return renderStackLegacyGL | |
| 51 | + | setRenderStackEnv(ctx, gskRendererEnvVar, gskRendererOpenGL) | |
| 52 | + | setRenderStackEnv(ctx, cef2gtkBackendEnvVar, cef2gtkBackendGLArea) | |
| 53 | + | setRenderStackEnv(ctx, cef2gtkAngleBackendVar, cef2gtkAngleGLEGL) | |
| 52 | 54 | default: | |
| 53 | - | setEnvDefault(ctx, gskRendererEnvVar, gskRendererVulkan) | |
| 54 | - | setEnvDefault(ctx, cef2gtkBackendEnvVar, cef2gtkBackendGDKDMABUF) | |
| 55 | - | setEnvDefault(ctx, cef2gtkAngleBackendVar, cef2gtkAngleVulkan) | |
| 56 | - | return renderStackVulkanDMABUF | |
| 55 | + | stack = renderStackVulkanDMABUF | |
| 56 | + | setRenderStackEnv(ctx, gskRendererEnvVar, gskRendererVulkan) | |
| 57 | + | setRenderStackEnv(ctx, cef2gtkBackendEnvVar, cef2gtkBackendGDKDMABUF) | |
| 58 | + | setRenderStackEnv(ctx, cef2gtkAngleBackendVar, cef2gtkAngleVulkan) | |
| 59 | + | } | |
| 60 | + | logRenderStackEnvironment(ctx, stack) | |
| 61 | + | return stack | |
| 62 | + | } | |
| 63 | + | ||
| 64 | + | func logRenderStackEnvironment(ctx context.Context, stack string) { | |
| 65 | + | if ctx == nil { | |
| 66 | + | ctx = context.Background() | |
| 57 | 67 | } | |
| 68 | + | logging.FromContext(ctx).Info(). | |
| 69 | + | Str("render_stack", stack). | |
| 70 | + | Str("gsk_renderer", os.Getenv(gskRendererEnvVar)). | |
| 71 | + | Str("cef2gtk_backend", os.Getenv(cef2gtkBackendEnvVar)). | |
| 72 | + | Str("cef2gtk_angle_backend", os.Getenv(cef2gtkAngleBackendVar)). | |
| 73 | + | Bool("split_stack_allowed", envBoolEnabled(dumberRenderStackAllowSplitEnvVar)). | |
| 74 | + | Msg("cef: render stack environment configured") | |
| 58 | 75 | } | |
| 59 | 76 | ||
| 60 | 77 | // ApplyDefaultHardwareDecodeEnvironment maps Dumber's media config to CEF's | |
| @@ -102,6 +119,28 @@ func normalizeRenderStack(value string) string { | |||
| 102 | 119 | } | |
| 103 | 120 | } | |
| 104 | 121 | ||
| 122 | + | func setRenderStackEnv(ctx context.Context, key, value string) { | |
| 123 | + | if existing := strings.TrimSpace(os.Getenv(key)); existing != "" { | |
| 124 | + | if ctx == nil { | |
| 125 | + | ctx = context.Background() | |
| 126 | + | } | |
| 127 | + | if envBoolEnabled(dumberRenderStackAllowSplitEnvVar) { | |
| 128 | + | logging.FromContext(ctx).Warn(). | |
| 129 | + | Str("key", key). | |
| 130 | + | Str("value", existing). | |
| 131 | + | Str("stack_value", value). | |
| 132 | + | Msg("cef: preserving explicit split render stack environment override") | |
| 133 | + | return | |
| 134 | + | } | |
| 135 | + | logging.FromContext(ctx).Warn(). | |
| 136 | + | Str("key", key). | |
| 137 | + | Str("value", existing). | |
| 138 | + | Str("stack_value", value). | |
| 139 | + | Msg("cef: overriding low-level render stack environment to keep Dumber stack coherent") | |
| 140 | + | } | |
| 141 | + | _ = os.Setenv(key, value) | |
| 142 | + | } | |
| 143 | + | ||
| 105 | 144 | func setEnvDefault(ctx context.Context, key, value string) { | |
| 106 | 145 | if existing := strings.TrimSpace(os.Getenv(key)); existing != "" { | |
| 107 | 146 | if ctx == nil { | |
| @@ -110,7 +149,7 @@ func setEnvDefault(ctx context.Context, key, value string) { | |||
| 110 | 149 | logging.FromContext(ctx).Debug(). | |
| 111 | 150 | Str("key", key). | |
| 112 | 151 | Str("value", existing). | |
| 113 | - | Msg("cef: preserving explicit render stack environment override") | |
| 152 | + | Msg("cef: preserving explicit environment override") | |
| 114 | 153 | return | |
| 115 | 154 | } | |
| 116 | 155 | _ = os.Setenv(key, value) | |
| @@ -12,7 +12,8 @@ import ( | |||||
| 12 | ) | 12 | ) | ||
| 13 | 13 | ||||
| 14 | const ( | 14 | const ( | ||
| 15 | - | dumberRenderStackEnvVar = "DUMBER_RENDER_STACK" | 15 | + | dumberRenderStackEnvVar = "DUMBER_RENDER_STACK" |
| 16 | + | dumberRenderStackAllowSplitEnvVar = "DUMBER_RENDER_STACK_ALLOW_SPLIT" | |||
| 16 | 17 | ||||
| 17 | renderStackAuto = "auto" | 18 | renderStackAuto = "auto" | ||
| 18 | renderStackVulkanDMABUF = "vulkan-dmabuf" | 19 | renderStackVulkanDMABUF = "vulkan-dmabuf" | ||
| @@ -32,29 +33,45 @@ const ( | |||||
| 32 | // Dumber's default CEF presentation stack before GTK/libadwaita initialize. | 33 | // Dumber's default CEF presentation stack before GTK/libadwaita initialize. | ||
| 33 | // | 34 | // | ||
| 34 | // DUMBER_RENDER_STACK accepts: | 35 | // DUMBER_RENDER_STACK accepts: | ||
| 35 | - | // - auto or empty: use the GPU-first GDK DMABUF stack | 36 | + | // - empty/default or vulkan-dmabuf: use GDK DMABUF presentation with GSK Vulkan and CEF ANGLE Vulkan |
| 36 | - | // - vulkan-dmabuf: force GDK DMABUF presentation with GSK Vulkan and CEF ANGLE Vulkan | 37 | + | // - auto: alias for vulkan-dmabuf |
| 37 | // - legacy-gl: force the older GtkGLArea/OpenGL bridge for diagnostics | 38 | // - legacy-gl: force the older GtkGLArea/OpenGL bridge for diagnostics | ||
| 38 | // | 39 | // | ||
| 39 | - | // Low-level env vars remain diagnostic escape hatches: when explicitly set, | 40 | + | // Low-level env vars are treated as library-development escape hatches only. |
| 40 | - | // they are preserved instead of overwritten. | 41 | + | // Dumber keeps the selected stack coherent by default and overwrites conflicts; |
| 42 | + | // set DUMBER_RENDER_STACK_ALLOW_SPLIT=1 to preserve explicit split-stack values. | |||
| 41 | func ApplyDefaultRenderStackEnvironment(ctx context.Context) string { | 43 | func ApplyDefaultRenderStackEnvironment(ctx context.Context) string { | ||
| 42 | return applyDefaultRenderStackEnvironment(ctx) | 44 | return applyDefaultRenderStackEnvironment(ctx) | ||
| 43 | } | 45 | } | ||
| 44 | 46 | ||||
| 45 | func applyDefaultRenderStackEnvironment(ctx context.Context) string { | 47 | func applyDefaultRenderStackEnvironment(ctx context.Context) string { | ||
| 46 | - | switch normalizeRenderStack(os.Getenv(dumberRenderStackEnvVar)) { | 48 | + | stack := normalizeRenderStack(os.Getenv(dumberRenderStackEnvVar)) |
| 49 | + | switch stack { | |||
| 47 | case renderStackLegacyGL: | 50 | case renderStackLegacyGL: | ||
| 48 | - | setEnvDefault(ctx, gskRendererEnvVar, gskRendererOpenGL) | 51 | + | setRenderStackEnv(ctx, gskRendererEnvVar, gskRendererOpenGL) |
| 49 | - | setEnvDefault(ctx, cef2gtkBackendEnvVar, cef2gtkBackendGLArea) | 52 | + | setRenderStackEnv(ctx, cef2gtkBackendEnvVar, cef2gtkBackendGLArea) |
| 50 | - | setEnvDefault(ctx, cef2gtkAngleBackendVar, cef2gtkAngleGLEGL) | 53 | + | setRenderStackEnv(ctx, cef2gtkAngleBackendVar, cef2gtkAngleGLEGL) |
| 51 | - | return renderStackLegacyGL | |||
| 52 | default: | 54 | default: | ||
| 53 | - | setEnvDefault(ctx, gskRendererEnvVar, gskRendererVulkan) | 55 | + | stack = renderStackVulkanDMABUF |
| 54 | - | setEnvDefault(ctx, cef2gtkBackendEnvVar, cef2gtkBackendGDKDMABUF) | 56 | + | setRenderStackEnv(ctx, gskRendererEnvVar, gskRendererVulkan) |
| 55 | - | setEnvDefault(ctx, cef2gtkAngleBackendVar, cef2gtkAngleVulkan) | 57 | + | setRenderStackEnv(ctx, cef2gtkBackendEnvVar, cef2gtkBackendGDKDMABUF) |
| 56 | - | return renderStackVulkanDMABUF | 58 | + | setRenderStackEnv(ctx, cef2gtkAngleBackendVar, cef2gtkAngleVulkan) |
| 59 | + | } | |||
| 60 | + | logRenderStackEnvironment(ctx, stack) | |||
| 61 | + | return stack | |||
| 62 | + | } | |||
| 63 | + | ||||
| 64 | + | func logRenderStackEnvironment(ctx context.Context, stack string) { | |||
| 65 | + | if ctx == nil { | |||
| 66 | + | ctx = context.Background() | |||
| 57 | } | 67 | } | ||
| 68 | + | logging.FromContext(ctx).Info(). | |||
| 69 | + | Str("render_stack", stack). | |||
| 70 | + | Str("gsk_renderer", os.Getenv(gskRendererEnvVar)). | |||
| 71 | + | Str("cef2gtk_backend", os.Getenv(cef2gtkBackendEnvVar)). | |||
| 72 | + | Str("cef2gtk_angle_backend", os.Getenv(cef2gtkAngleBackendVar)). | |||
| 73 | + | Bool("split_stack_allowed", envBoolEnabled(dumberRenderStackAllowSplitEnvVar)). | |||
| 74 | + | Msg("cef: render stack environment configured") | |||
| 58 | } | 75 | } | ||
| 59 | 76 | ||||
| 60 | // ApplyDefaultHardwareDecodeEnvironment maps Dumber's media config to CEF's | 77 | // ApplyDefaultHardwareDecodeEnvironment maps Dumber's media config to CEF's | ||
| @@ -102,6 +119,28 @@ func normalizeRenderStack(value string) string { | |||||
| 102 | } | 119 | } | ||
| 103 | } | 120 | } | ||
| 104 | 121 | ||||
| 122 | + | func setRenderStackEnv(ctx context.Context, key, value string) { | |||
| 123 | + | if existing := strings.TrimSpace(os.Getenv(key)); existing != "" { | |||
| 124 | + | if ctx == nil { | |||
| 125 | + | ctx = context.Background() | |||
| 126 | + | } | |||
| 127 | + | if envBoolEnabled(dumberRenderStackAllowSplitEnvVar) { | |||
| 128 | + | logging.FromContext(ctx).Warn(). | |||
| 129 | + | Str("key", key). | |||
| 130 | + | Str("value", existing). | |||
| 131 | + | Str("stack_value", value). | |||
| 132 | + | Msg("cef: preserving explicit split render stack environment override") | |||
| 133 | + | return | |||
| 134 | + | } | |||
| 135 | + | logging.FromContext(ctx).Warn(). | |||
| 136 | + | Str("key", key). | |||
| 137 | + | Str("value", existing). | |||
| 138 | + | Str("stack_value", value). | |||
| 139 | + | Msg("cef: overriding low-level render stack environment to keep Dumber stack coherent") | |||
| 140 | + | } | |||
| 141 | + | _ = os.Setenv(key, value) | |||
| 142 | + | } | |||
| 143 | + | ||||
| 105 | func setEnvDefault(ctx context.Context, key, value string) { | 144 | func setEnvDefault(ctx context.Context, key, value string) { | ||
| 106 | if existing := strings.TrimSpace(os.Getenv(key)); existing != "" { | 145 | if existing := strings.TrimSpace(os.Getenv(key)); existing != "" { | ||
| 107 | if ctx == nil { | 146 | if ctx == nil { | ||
| @@ -110,7 +149,7 @@ func setEnvDefault(ctx context.Context, key, value string) { | |||||
| 110 | logging.FromContext(ctx).Debug(). | 149 | logging.FromContext(ctx).Debug(). | ||
| 111 | Str("key", key). | 150 | Str("key", key). | ||
| 112 | Str("value", existing). | 151 | Str("value", existing). | ||
| 113 | - | Msg("cef: preserving explicit render stack environment override") | 152 | + | Msg("cef: preserving explicit environment override") |
| 114 | return | 153 | return | ||
| 115 | } | 154 | } | ||
| 116 | _ = os.Setenv(key, value) | 155 | _ = os.Setenv(key, value) | ||
M internal/infrastructure/cef/render_stack_env_test.go +25 -1
| @@ -30,8 +30,32 @@ func TestApplyDefaultRenderStackEnvironment_DefaultsToGDKDMABUFWithANGLEVulkan(t | |||
| 30 | 30 | } | |
| 31 | 31 | } | |
| 32 | 32 | ||
| 33 | - | func TestApplyDefaultRenderStackEnvironment_PreservesExplicitLowLevelOverrides(t *testing.T) { | |
| 33 | + | func TestApplyDefaultRenderStackEnvironment_OverridesConflictingLowLevelDefaults(t *testing.T) { | |
| 34 | 34 | t.Setenv(dumberRenderStackEnvVar, "") | |
| 35 | + | t.Setenv(dumberRenderStackAllowSplitEnvVar, "") | |
| 36 | + | t.Setenv("GSK_RENDERER", "ngl") | |
| 37 | + | t.Setenv("PUREGO_CEF2GTK_BACKEND", "glarea") | |
| 38 | + | t.Setenv("PUREGO_CEF2GTK_ANGLE_BACKEND", "gl-egl") | |
| 39 | + | ||
| 40 | + | got := applyDefaultRenderStackEnvironment(context.Background()) | |
| 41 | + | ||
| 42 | + | if got != renderStackVulkanDMABUF { | |
| 43 | + | t.Fatalf("render stack = %q, want %q", got, renderStackVulkanDMABUF) | |
| 44 | + | } | |
| 45 | + | if got := os.Getenv("GSK_RENDERER"); got != "vulkan" { | |
| 46 | + | t.Fatalf("GSK_RENDERER = %q, want coherent vulkan default", got) | |
| 47 | + | } | |
| 48 | + | if got := os.Getenv("PUREGO_CEF2GTK_BACKEND"); got != "gdk-dmabuf" { | |
| 49 | + | t.Fatalf("PUREGO_CEF2GTK_BACKEND = %q, want coherent gdk-dmabuf default", got) | |
| 50 | + | } | |
| 51 | + | if got := os.Getenv("PUREGO_CEF2GTK_ANGLE_BACKEND"); got != "vulkan" { | |
| 52 | + | t.Fatalf("PUREGO_CEF2GTK_ANGLE_BACKEND = %q, want coherent vulkan default", got) | |
| 53 | + | } | |
| 54 | + | } | |
| 55 | + | ||
| 56 | + | func TestApplyDefaultRenderStackEnvironment_AllowSplitPreservesExplicitLowLevelOverrides(t *testing.T) { | |
| 57 | + | t.Setenv(dumberRenderStackEnvVar, "") | |
| 58 | + | t.Setenv(dumberRenderStackAllowSplitEnvVar, "1") | |
| 35 | 59 | t.Setenv("GSK_RENDERER", "ngl") | |
| 36 | 60 | t.Setenv("PUREGO_CEF2GTK_BACKEND", "glarea") | |
| 37 | 61 | t.Setenv("PUREGO_CEF2GTK_ANGLE_BACKEND", "gl-egl") | |
| @@ -30,8 +30,32 @@ func TestApplyDefaultRenderStackEnvironment_DefaultsToGDKDMABUFWithANGLEVulkan(t | |||||
| 30 | } | 30 | } | ||
| 31 | } | 31 | } | ||
| 32 | 32 | ||||
| 33 | - | func TestApplyDefaultRenderStackEnvironment_PreservesExplicitLowLevelOverrides(t *testing.T) { | 33 | + | func TestApplyDefaultRenderStackEnvironment_OverridesConflictingLowLevelDefaults(t *testing.T) { |
| 34 | t.Setenv(dumberRenderStackEnvVar, "") | 34 | t.Setenv(dumberRenderStackEnvVar, "") | ||
| 35 | + | t.Setenv(dumberRenderStackAllowSplitEnvVar, "") | |||
| 36 | + | t.Setenv("GSK_RENDERER", "ngl") | |||
| 37 | + | t.Setenv("PUREGO_CEF2GTK_BACKEND", "glarea") | |||
| 38 | + | t.Setenv("PUREGO_CEF2GTK_ANGLE_BACKEND", "gl-egl") | |||
| 39 | + | ||||
| 40 | + | got := applyDefaultRenderStackEnvironment(context.Background()) | |||
| 41 | + | ||||
| 42 | + | if got != renderStackVulkanDMABUF { | |||
| 43 | + | t.Fatalf("render stack = %q, want %q", got, renderStackVulkanDMABUF) | |||
| 44 | + | } | |||
| 45 | + | if got := os.Getenv("GSK_RENDERER"); got != "vulkan" { | |||
| 46 | + | t.Fatalf("GSK_RENDERER = %q, want coherent vulkan default", got) | |||
| 47 | + | } | |||
| 48 | + | if got := os.Getenv("PUREGO_CEF2GTK_BACKEND"); got != "gdk-dmabuf" { | |||
| 49 | + | t.Fatalf("PUREGO_CEF2GTK_BACKEND = %q, want coherent gdk-dmabuf default", got) | |||
| 50 | + | } | |||
| 51 | + | if got := os.Getenv("PUREGO_CEF2GTK_ANGLE_BACKEND"); got != "vulkan" { | |||
| 52 | + | t.Fatalf("PUREGO_CEF2GTK_ANGLE_BACKEND = %q, want coherent vulkan default", got) | |||
| 53 | + | } | |||
| 54 | + | } | |||
| 55 | + | ||||
| 56 | + | func TestApplyDefaultRenderStackEnvironment_AllowSplitPreservesExplicitLowLevelOverrides(t *testing.T) { | |||
| 57 | + | t.Setenv(dumberRenderStackEnvVar, "") | |||
| 58 | + | t.Setenv(dumberRenderStackAllowSplitEnvVar, "1") | |||
| 35 | t.Setenv("GSK_RENDERER", "ngl") | 59 | t.Setenv("GSK_RENDERER", "ngl") | ||
| 36 | t.Setenv("PUREGO_CEF2GTK_BACKEND", "glarea") | 60 | t.Setenv("PUREGO_CEF2GTK_BACKEND", "glarea") | ||
| 37 | t.Setenv("PUREGO_CEF2GTK_ANGLE_BACKEND", "gl-egl") | 61 | t.Setenv("PUREGO_CEF2GTK_ANGLE_BACKEND", "gl-egl") | ||
M internal/infrastructure/cef/render_stall_watchdog.go +13 -0
| @@ -202,11 +202,24 @@ func (wv *WebView) logRenderStall(bridge *Cef2gtkAdapter, now time.Time, diag re | |||
| 202 | 202 | Uint64("profile_frames_queued", profile.FramesQueued). | |
| 203 | 203 | Uint64("profile_frames_rendered", profile.FramesRendered). | |
| 204 | 204 | Uint32("profile_gc_delta", profile.GC.NumGCDelta). | |
| 205 | + | Str("render_backend", diag.diag.Backend). | |
| 205 | 206 | Int("accelerated_paints", diag.diag.AcceleratedPaints). | |
| 206 | 207 | Int("unsupported_paints", diag.diag.UnsupportedPaints). | |
| 207 | 208 | Int("accelerated_paint_errors", diag.diag.AcceleratedPaintErrors). | |
| 208 | 209 | Int("import_failures", diag.diag.ImportFailures). | |
| 209 | 210 | Int("render_failures", diag.diag.RenderFailures). | |
| 211 | + | Int("textures_built", diag.diag.TexturesBuilt). | |
| 212 | + | Int("texture_build_failures", diag.diag.TextureBuildFailures). | |
| 213 | + | Int("fd_dup_failures", diag.diag.FDDupFailures). | |
| 214 | + | Int("unsupported_formats", diag.diag.UnsupportedFormats). | |
| 215 | + | Int("paintable_swaps", diag.diag.PaintableSwaps). | |
| 216 | + | Bool("pending_frame", diag.diag.PendingFrame). | |
| 217 | + | Bool("pending_scheduled", diag.diag.PendingScheduled). | |
| 218 | + | Int64("pending_age_ms", durationMillis(diag.diag.PendingAge)). | |
| 219 | + | Uint("pending_source_id", diag.diag.PendingSourceID). | |
| 220 | + | Int("pending_reschedules", diag.diag.PendingReschedules). | |
| 221 | + | Int("pending_schedule_failures", diag.diag.PendingScheduleFailures). | |
| 222 | + | Int("pending_idle_callbacks", diag.diag.PendingIdleCallbacks). | |
| 210 | 223 | Str("latest_render_event_kind", diag.latestEvent.Kind). | |
| 211 | 224 | Str("latest_render_event_message", diag.latestEvent.Message). | |
| 212 | 225 | Msg("cef: accelerated rendering appears stalled") | |
| @@ -202,11 +202,24 @@ func (wv *WebView) logRenderStall(bridge *Cef2gtkAdapter, now time.Time, diag re | |||||
| 202 | Uint64("profile_frames_queued", profile.FramesQueued). | 202 | Uint64("profile_frames_queued", profile.FramesQueued). | ||
| 203 | Uint64("profile_frames_rendered", profile.FramesRendered). | 203 | Uint64("profile_frames_rendered", profile.FramesRendered). | ||
| 204 | Uint32("profile_gc_delta", profile.GC.NumGCDelta). | 204 | Uint32("profile_gc_delta", profile.GC.NumGCDelta). | ||
| 205 | + | Str("render_backend", diag.diag.Backend). | |||
| 205 | Int("accelerated_paints", diag.diag.AcceleratedPaints). | 206 | Int("accelerated_paints", diag.diag.AcceleratedPaints). | ||
| 206 | Int("unsupported_paints", diag.diag.UnsupportedPaints). | 207 | Int("unsupported_paints", diag.diag.UnsupportedPaints). | ||
| 207 | Int("accelerated_paint_errors", diag.diag.AcceleratedPaintErrors). | 208 | Int("accelerated_paint_errors", diag.diag.AcceleratedPaintErrors). | ||
| 208 | Int("import_failures", diag.diag.ImportFailures). | 209 | Int("import_failures", diag.diag.ImportFailures). | ||
| 209 | Int("render_failures", diag.diag.RenderFailures). | 210 | Int("render_failures", diag.diag.RenderFailures). | ||
| 211 | + | Int("textures_built", diag.diag.TexturesBuilt). | |||
| 212 | + | Int("texture_build_failures", diag.diag.TextureBuildFailures). | |||
| 213 | + | Int("fd_dup_failures", diag.diag.FDDupFailures). | |||
| 214 | + | Int("unsupported_formats", diag.diag.UnsupportedFormats). | |||
| 215 | + | Int("paintable_swaps", diag.diag.PaintableSwaps). | |||
| 216 | + | Bool("pending_frame", diag.diag.PendingFrame). | |||
| 217 | + | Bool("pending_scheduled", diag.diag.PendingScheduled). | |||
| 218 | + | Int64("pending_age_ms", durationMillis(diag.diag.PendingAge)). | |||
| 219 | + | Uint("pending_source_id", diag.diag.PendingSourceID). | |||
| 220 | + | Int("pending_reschedules", diag.diag.PendingReschedules). | |||
| 221 | + | Int("pending_schedule_failures", diag.diag.PendingScheduleFailures). | |||
| 222 | + | Int("pending_idle_callbacks", diag.diag.PendingIdleCallbacks). | |||
| 210 | Str("latest_render_event_kind", diag.latestEvent.Kind). | 223 | Str("latest_render_event_kind", diag.latestEvent.Kind). | ||
| 211 | Str("latest_render_event_message", diag.latestEvent.Message). | 224 | Str("latest_render_event_message", diag.latestEvent.Message). | ||
| 212 | Msg("cef: accelerated rendering appears stalled") | 225 | Msg("cef: accelerated rendering appears stalled") | ||