docs(cef): clarify adaptive osr frame rate
- commit
- f0ca3ee219bf9ca8764150a16499a34b363c43e0
- author
- brice · · 2026-05-20 06:01 UTC
- parent
- 45b07b3
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
4 files · +29 -10
M README.md +3 -1
| @@ -101,7 +101,9 @@ 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. For driver compatibility, switch to the EGL/OpenGL stack with `engine.cef.render_stack = "egl"`; the default is `"vulkan"`. CEF OSR frame rate adapts to the active Wayland monitor refresh rate by default, capped by `engine.cef.windowless_frame_rate_max = 240`; set `engine.cef.windowless_frame_rate` to a positive value for an explicit fixed cap. | |
| 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 | + | ||
| 106 | + | CEF OSR frame rate adapts to the active Wayland monitor refresh rate by default when `engine.cef.adaptive_windowless_frame_rate = true`. Adaptive mode is capped by `engine.cef.windowless_frame_rate_max = 240`. To use a fixed frame rate instead, set `engine.cef.windowless_frame_rate` to a positive value. | |
| 105 | 107 | ||
| 106 | 108 | ## Keyboard-Driven | |
| 107 | 109 | ||
| @@ -101,7 +101,9 @@ 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. For driver compatibility, switch to the EGL/OpenGL stack with `engine.cef.render_stack = "egl"`; the default is `"vulkan"`. CEF OSR frame rate adapts to the active Wayland monitor refresh rate by default, capped by `engine.cef.windowless_frame_rate_max = 240`; set `engine.cef.windowless_frame_rate` to a positive value for an explicit fixed cap. | 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 | + | ||||
| 106 | + | CEF OSR frame rate adapts to the active Wayland monitor refresh rate by default when `engine.cef.adaptive_windowless_frame_rate = true`. Adaptive mode is capped by `engine.cef.windowless_frame_rate_max = 240`. To use a fixed frame rate instead, set `engine.cef.windowless_frame_rate` to a positive value. | |||
| 105 | 107 | ||||
| 106 | ## Keyboard-Driven | 108 | ## Keyboard-Driven | ||
| 107 | 109 | ||||
M docs/config/index.md +3 -3
| @@ -190,9 +190,9 @@ border = "#363636" | |||
| 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 | 192 | | `engine.cef.render_stack` | string | `"vulkan"` | `vulkan`, `egl` | CEF GPU render stack | | |
| 193 | - | | `engine.cef.adaptive_windowless_frame_rate` | bool | `true` | - | Adapt CEF OSR FPS to the active Wayland monitor when `windowless_frame_rate = 0` | | |
| 194 | - | | `engine.cef.windowless_frame_rate` | int32 | `0` | >= 0 | Explicit CEF OSR FPS cap; 0 lets adaptive mode choose | | |
| 195 | - | | `engine.cef.windowless_frame_rate_max` | int32 | `240` | > 0 | Hard cap for adaptive CEF OSR FPS | | |
| 193 | + | | `engine.cef.adaptive_windowless_frame_rate` | bool | `true` | - | Enable adaptive CEF OSR FPS polling (only applies when `windowless_frame_rate = 0`) | | |
| 194 | + | | `engine.cef.windowless_frame_rate` | int32 | `0` | >= 0 | Explicit CEF OSR FPS cap; 0 uses adaptive mode if enabled, otherwise CEF default | | |
| 195 | + | | `engine.cef.windowless_frame_rate_max` | int32 | `240` | >= 0 | Hard cap for adaptive CEF OSR FPS; 0 uses the built-in default cap | | |
| 196 | 196 | | `default_ui_scale` | float | `1.0` | > 0 | GTK widget UI scale (1.0=100%, 2.0=200%) | | |
| 197 | 197 | | `default_webpage_zoom` | float | `1.2` | > 0 | Default page zoom (1.0=100%, 1.2=120%) | | |
| 198 | 198 | ||
| @@ -190,9 +190,9 @@ border = "#363636" | |||||
| 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 | | `engine.cef.render_stack` | string | `"vulkan"` | `vulkan`, `egl` | CEF GPU render stack | | ||
| 193 | - | | `engine.cef.adaptive_windowless_frame_rate` | bool | `true` | - | Adapt CEF OSR FPS to the active Wayland monitor when `windowless_frame_rate = 0` | | 193 | + | | `engine.cef.adaptive_windowless_frame_rate` | bool | `true` | - | Enable adaptive CEF OSR FPS polling (only applies when `windowless_frame_rate = 0`) | |
| 194 | - | | `engine.cef.windowless_frame_rate` | int32 | `0` | >= 0 | Explicit CEF OSR FPS cap; 0 lets adaptive mode choose | | 194 | + | | `engine.cef.windowless_frame_rate` | int32 | `0` | >= 0 | Explicit CEF OSR FPS cap; 0 uses adaptive mode if enabled, otherwise CEF default | |
| 195 | - | | `engine.cef.windowless_frame_rate_max` | int32 | `240` | > 0 | Hard cap for adaptive CEF OSR FPS | | 195 | + | | `engine.cef.windowless_frame_rate_max` | int32 | `240` | >= 0 | Hard cap for adaptive CEF OSR FPS; 0 uses the built-in default cap | |
| 196 | | `default_ui_scale` | float | `1.0` | > 0 | GTK widget UI scale (1.0=100%, 2.0=200%) | | 196 | | `default_ui_scale` | float | `1.0` | > 0 | GTK widget UI scale (1.0=100%, 2.0=200%) | | ||
| 197 | | `default_webpage_zoom` | float | `1.2` | > 0 | Default page zoom (1.0=100%, 1.2=120%) | | 197 | | `default_webpage_zoom` | float | `1.2` | > 0 | Default page zoom (1.0=100%, 1.2=120%) | | ||
| 198 | 198 | ||||
M internal/infrastructure/cef/adaptive_frame_rate_test.go +21 -4
| @@ -26,10 +26,27 @@ func TestAdaptiveFrameRateForRefresh(t *testing.T) { | |||
| 26 | 26 | } | |
| 27 | 27 | ||
| 28 | 28 | func TestIsWaylandDisplayName(t *testing.T) { | |
| 29 | - | if !isWaylandDisplayName("Wayland") || !isWaylandDisplayName("wayland-0") { | |
| 30 | - | t.Fatal("expected wayland display names to match") | |
| 29 | + | tests := []struct { | |
| 30 | + | name string | |
| 31 | + | input string | |
| 32 | + | want bool | |
| 33 | + | }{ | |
| 34 | + | {name: "exact lowercase", input: "wayland", want: true}, | |
| 35 | + | {name: "exact uppercase", input: "WAYLAND", want: true}, | |
| 36 | + | {name: "mixed case", input: "WayLand", want: true}, | |
| 37 | + | {name: "numeric suffix", input: "wayland-1", want: true}, | |
| 38 | + | {name: "punctuation suffix", input: "wayland:0", want: true}, | |
| 39 | + | {name: "leading whitespace", input: " wayland ", want: true}, | |
| 40 | + | {name: "substring", input: "mywayland-display", want: true}, | |
| 41 | + | {name: "x11", input: "x11", want: false}, | |
| 42 | + | {name: "empty", input: "", want: false}, | |
| 43 | + | {name: "whitespace", input: " ", want: false}, | |
| 31 | 44 | } | |
| 32 | - | if isWaylandDisplayName("x11") || isWaylandDisplayName("") { | |
| 33 | - | t.Fatal("unexpected non-wayland display match") | |
| 45 | + | for _, tt := range tests { | |
| 46 | + | t.Run(tt.name, func(t *testing.T) { | |
| 47 | + | if got := isWaylandDisplayName(tt.input); got != tt.want { | |
| 48 | + | t.Fatalf("isWaylandDisplayName(%q) = %v, want %v", tt.input, got, tt.want) | |
| 49 | + | } | |
| 50 | + | }) | |
| 34 | 51 | } | |
| 35 | 52 | } | |
| @@ -26,10 +26,27 @@ func TestAdaptiveFrameRateForRefresh(t *testing.T) { | |||||
| 26 | } | 26 | } | ||
| 27 | 27 | ||||
| 28 | func TestIsWaylandDisplayName(t *testing.T) { | 28 | func TestIsWaylandDisplayName(t *testing.T) { | ||
| 29 | - | if !isWaylandDisplayName("Wayland") || !isWaylandDisplayName("wayland-0") { | 29 | + | tests := []struct { |
| 30 | - | t.Fatal("expected wayland display names to match") | 30 | + | name string |
| 31 | + | input string | |||
| 32 | + | want bool | |||
| 33 | + | }{ | |||
| 34 | + | {name: "exact lowercase", input: "wayland", want: true}, | |||
| 35 | + | {name: "exact uppercase", input: "WAYLAND", want: true}, | |||
| 36 | + | {name: "mixed case", input: "WayLand", want: true}, | |||
| 37 | + | {name: "numeric suffix", input: "wayland-1", want: true}, | |||
| 38 | + | {name: "punctuation suffix", input: "wayland:0", want: true}, | |||
| 39 | + | {name: "leading whitespace", input: " wayland ", want: true}, | |||
| 40 | + | {name: "substring", input: "mywayland-display", want: true}, | |||
| 41 | + | {name: "x11", input: "x11", want: false}, | |||
| 42 | + | {name: "empty", input: "", want: false}, | |||
| 43 | + | {name: "whitespace", input: " ", want: false}, | |||
| 31 | } | 44 | } | ||
| 32 | - | if isWaylandDisplayName("x11") || isWaylandDisplayName("") { | 45 | + | for _, tt := range tests { |
| 33 | - | t.Fatal("unexpected non-wayland display match") | 46 | + | t.Run(tt.name, func(t *testing.T) { |
| 47 | + | if got := isWaylandDisplayName(tt.input); got != tt.want { | |||
| 48 | + | t.Fatalf("isWaylandDisplayName(%q) = %v, want %v", tt.input, got, tt.want) | |||
| 49 | + | } | |||
| 50 | + | }) | |||
| 34 | } | 51 | } | ||
| 35 | } | 52 | } | ||
M internal/infrastructure/config/schema_provider.go +2 -2
| @@ -958,8 +958,8 @@ func (*SchemaProvider) getPerformanceKeys(defaults *Config) []entity.ConfigKeyIn | |||
| 958 | 958 | Key: "engine.cef.windowless_frame_rate_max", | |
| 959 | 959 | Type: "int32", | |
| 960 | 960 | Default: fmt.Sprintf("%d", defaults.Engine.CEF.CEFWindowlessFrameRateMax()), | |
| 961 | - | Description: "Hard cap for adaptive CEF OSR frame rate", | |
| 962 | - | Range: ">0", | |
| 961 | + | Description: "Hard cap for adaptive CEF OSR frame rate; 0 uses the built-in default cap", | |
| 962 | + | Range: ">=0", | |
| 963 | 963 | Section: SectionPerformance, | |
| 964 | 964 | }, | |
| 965 | 965 | { | |
| @@ -958,8 +958,8 @@ func (*SchemaProvider) getPerformanceKeys(defaults *Config) []entity.ConfigKeyIn | |||||
| 958 | Key: "engine.cef.windowless_frame_rate_max", | 958 | Key: "engine.cef.windowless_frame_rate_max", | ||
| 959 | Type: "int32", | 959 | Type: "int32", | ||
| 960 | Default: fmt.Sprintf("%d", defaults.Engine.CEF.CEFWindowlessFrameRateMax()), | 960 | Default: fmt.Sprintf("%d", defaults.Engine.CEF.CEFWindowlessFrameRateMax()), | ||
| 961 | - | Description: "Hard cap for adaptive CEF OSR frame rate", | 961 | + | Description: "Hard cap for adaptive CEF OSR frame rate; 0 uses the built-in default cap", |
| 962 | - | Range: ">0", | 962 | + | Range: ">=0", |
| 963 | Section: SectionPerformance, | 963 | Section: SectionPerformance, | ||
| 964 | }, | 964 | }, | ||
| 965 | { | 965 | { | ||