1package config
2
3import (
4 "runtime"
5
6 "github.com/bnema/dumber/internal/application/port"
7)
8
9const (
10 // maxSkiaCPUThreads is the maximum number of Skia CPU painting threads
11 // supported by WebKitGTK (0-8 range).
12 maxSkiaCPUThreads = 8
13
14 // Memory tier thresholds (in bytes)
15 // Explicitly typed as uint64 to prevent overflow on 32-bit systems.
16 ramTierLow uint64 = 8 * 1024 * 1024 * 1024 // 8 GB
17 ramTierMedium uint64 = 16 * 1024 * 1024 * 1024 // 16 GB
18 ramTierHigh uint64 = 32 * 1024 * 1024 * 1024 // 32 GB
19
20 // VRAM tier thresholds (in bytes)
21 vramTierLow uint64 = 4 * 1024 * 1024 * 1024 // 4 GB (low-end discrete / integrated)
22 vramTierMid uint64 = 8 * 1024 * 1024 * 1024 // 8 GB (mid-range discrete)
23 vramTierHigh uint64 = 16 * 1024 * 1024 * 1024 // 16 GB (high-end discrete)
24
25 // GPU thread scaling values
26 gpuThreadsHigh = 8 // For 16GB+ VRAM
27 gpuThreadsMid = 6 // For 8-16GB VRAM
28 gpuThreadsLow = 4 // For 4-8GB VRAM
29 gpuThreadsMin = 2 // For <4GB VRAM or unknown
30
31 // Memory limit values (in MB)
32 webMemHigh = 4096 // For 32GB+ RAM
33 webMemMedium = 3072 // For 16-32GB RAM
34 webMemLow = 2048 // For 8-16GB RAM
35 webMemMin = 1024 // For <8GB RAM
36 netMemHigh = 1024 // For 32GB+ RAM
37 netMemMedium = 768 // For 16-32GB RAM
38 netMemLow = 512 // For 8-16GB RAM
39 netMemMin = 256 // For <8GB RAM
40 webMemFallback = 2048 // When RAM unknown
41 netMemFallback = 512 // When RAM unknown
42
43 // WebView pool prewarm counts
44 poolPrewarmHigh = 12 // For 32GB+ RAM
45 poolPrewarmMedium = 8 // For 16-32GB RAM
46 poolPrewarmLow = 6 // For 8-16GB RAM
47 poolPrewarmMin = 4 // For <8GB RAM
48 poolPrewarmFallback = 8 // When RAM unknown
49)
50
51// ResolvedPerformanceSettings contains the computed performance settings
52// after profile resolution. These are the actual values to apply.
53type ResolvedPerformanceSettings struct {
54 // Skia threading
55 SkiaCPUPaintingThreads int
56 SkiaGPUPaintingThreads int
57 SkiaEnableCPURendering bool
58
59 // Web process memory pressure
60 WebProcessMemoryLimitMB int
61 WebProcessMemoryPollIntervalSec float64
62 WebProcessMemoryConservativeThreshold float64
63 WebProcessMemoryStrictThreshold float64
64
65 // Network process memory pressure
66 NetworkProcessMemoryLimitMB int
67 NetworkProcessMemoryPollIntervalSec float64
68 NetworkProcessMemoryConservativeThreshold float64
69 NetworkProcessMemoryStrictThreshold float64
70
71 // WebView pool
72 WebViewPoolPrewarmCount int
73}
74
75// ResolvePerformanceProfile computes the actual performance settings based on
76// the selected profile. For ProfileCustom, it returns the user's configured values.
77// For other profiles, it computes appropriate values based on detected hardware.
78//
79// The hw parameter is optional - if nil, fallback values are used.
80func ResolvePerformanceProfile(cfg *PerformanceConfig, hw *port.HardwareInfo) ResolvedPerformanceSettings {
81 switch cfg.Profile {
82 case ProfileLite:
83 return resolveLiteProfile(hw)
84 case ProfileBalanced:
85 return resolveBalancedProfile()
86 case ProfileMax:
87 return resolveMaxProfile(hw)
88 case ProfileCustom:
89 return resolveCustomProfile(cfg)
90 default: // ProfileDefault or empty
91 return resolveDefaultProfile()
92 }
93}
94
95// resolveDefaultProfile returns WebKit defaults (no tuning).
96func resolveDefaultProfile() ResolvedPerformanceSettings {
97 return ResolvedPerformanceSettings{
98 // All zeros/negatives mean "unset, use WebKit defaults"
99 SkiaCPUPaintingThreads: 0,
100 SkiaGPUPaintingThreads: -1,
101 SkiaEnableCPURendering: false,
102
103 WebProcessMemoryLimitMB: 0,
104 WebProcessMemoryPollIntervalSec: 0,
105 WebProcessMemoryConservativeThreshold: 0,
106 WebProcessMemoryStrictThreshold: 0,
107
108 NetworkProcessMemoryLimitMB: 0,
109 NetworkProcessMemoryPollIntervalSec: 0,
110 NetworkProcessMemoryConservativeThreshold: 0,
111 NetworkProcessMemoryStrictThreshold: 0,
112
113 WebViewPoolPrewarmCount: 4,
114 }
115}
116
117// resolveBalancedProfile preserves the legacy balanced profile as WebKit's
118// default tuning with the normal WebView prewarm count.
119func resolveBalancedProfile() ResolvedPerformanceSettings {
120 return resolveDefaultProfile()
121}
122
123// resolveLiteProfile returns settings optimized for low-RAM systems.
124// Uses hardware info to scale CPU threads if available.
125func resolveLiteProfile(hw *port.HardwareInfo) ResolvedPerformanceSettings {
126 // Use 2 CPU threads, or scale down on very low-core systems
127 cpuThreads := 2
128 if hw != nil && hw.CPUCores > 0 && hw.CPUCores < 4 {
129 cpuThreads = 1
130 }
131
132 return ResolvedPerformanceSettings{
133 SkiaCPUPaintingThreads: cpuThreads,
134 SkiaGPUPaintingThreads: -1, // unset
135 SkiaEnableCPURendering: false,
136
137 WebProcessMemoryLimitMB: 768,
138 WebProcessMemoryPollIntervalSec: 0, // use WebKit default (30s)
139 WebProcessMemoryConservativeThreshold: 0.25,
140 WebProcessMemoryStrictThreshold: 0.4,
141
142 NetworkProcessMemoryLimitMB: 384,
143 NetworkProcessMemoryPollIntervalSec: 0,
144 NetworkProcessMemoryConservativeThreshold: 0.25,
145 NetworkProcessMemoryStrictThreshold: 0.4,
146
147 WebViewPoolPrewarmCount: 2,
148 }
149}
150
151// resolveMaxProfile returns settings optimized for maximum responsiveness.
152// Scales values based on detected hardware capabilities.
153func resolveMaxProfile(hw *port.HardwareInfo) ResolvedPerformanceSettings {
154 // CPU threads: use physical cores (not threads), capped at WebKitGTK limit
155 cpuThreads := computeMaxCPUThreads(hw)
156
157 // GPU threads: scale based on VRAM
158 gpuThreads := computeMaxGPUThreads(hw)
159
160 // WebView pool: scale based on RAM
161 poolPrewarm := computeMaxPoolPrewarm(hw)
162
163 return ResolvedPerformanceSettings{
164 SkiaCPUPaintingThreads: cpuThreads,
165 SkiaGPUPaintingThreads: gpuThreads,
166 SkiaEnableCPURendering: false,
167
168 // Max profile: no memory limits, let WebKit use defaults
169 WebProcessMemoryLimitMB: 0,
170 WebProcessMemoryPollIntervalSec: 0,
171 WebProcessMemoryConservativeThreshold: 0,
172 WebProcessMemoryStrictThreshold: 0,
173
174 NetworkProcessMemoryLimitMB: 0,
175 NetworkProcessMemoryPollIntervalSec: 0,
176 NetworkProcessMemoryConservativeThreshold: 0,
177 NetworkProcessMemoryStrictThreshold: 0,
178
179 WebViewPoolPrewarmCount: poolPrewarm,
180 }
181}
182
183// computeMaxCPUThreads calculates Skia CPU threads for max profile.
184// Uses physical cores (not hyperthreaded) capped at WebKitGTK's limit.
185func computeMaxCPUThreads(hw *port.HardwareInfo) int {
186 var cpuThreads int
187
188 if hw != nil && hw.CPUCores > 0 {
189 // Use physical cores directly - they're better for parallel painting
190 cpuThreads = hw.CPUCores
191 } else {
192 // Fallback: assume hyperthreading, use half of logical CPUs
193 cpuThreads = runtime.NumCPU() / 2
194 }
195
196 // Minimum 4 for meaningful parallelism
197 if cpuThreads < 4 {
198 cpuThreads = 4
199 }
200 // Cap at WebKitGTK Skia limit
201 if cpuThreads > maxSkiaCPUThreads {
202 cpuThreads = maxSkiaCPUThreads
203 }
204
205 return cpuThreads
206}
207
208// computeMaxGPUThreads calculates Skia GPU threads based on VRAM.
209func computeMaxGPUThreads(hw *port.HardwareInfo) int {
210 if hw == nil || hw.VRAM == 0 {
211 // No VRAM info - use conservative default
212 return gpuThreadsMin
213 }
214
215 // Scale GPU threads based on VRAM tiers
216 switch {
217 case hw.VRAM >= vramTierHigh: // 16GB+ VRAM (high-end: RX 7900, RTX 4080+)
218 return gpuThreadsHigh
219 case hw.VRAM >= vramTierMid: // 8-16GB VRAM (mid-range: RX 7800, RTX 4070)
220 return gpuThreadsMid
221 case hw.VRAM >= vramTierLow: // 4-8GB VRAM (entry discrete)
222 return gpuThreadsLow
223 default: // <4GB VRAM (integrated / old discrete)
224 return gpuThreadsMin
225 }
226}
227
228// computeMaxMemoryLimits returns web process and network process memory limits.
229func computeMaxMemoryLimits(hw *port.HardwareInfo) (webMB, netMB int) {
230 if hw == nil || hw.TotalRAM == 0 {
231 // Fallback: assume 16GB system
232 return webMemFallback, netMemFallback
233 }
234
235 // Scale memory limits based on system RAM
236 switch {
237 case hw.TotalRAM >= ramTierHigh: // 32GB+ RAM
238 return webMemHigh, netMemHigh
239 case hw.TotalRAM >= ramTierMedium: // 16-32GB RAM
240 return webMemMedium, netMemMedium
241 case hw.TotalRAM >= ramTierLow: // 8-16GB RAM
242 return webMemLow, netMemLow
243 default: // <8GB RAM
244 return webMemMin, netMemMin
245 }
246}
247
248// computeMaxPoolPrewarm returns WebView pool prewarm count based on RAM.
249func computeMaxPoolPrewarm(hw *port.HardwareInfo) int {
250 if hw == nil || hw.TotalRAM == 0 {
251 return poolPrewarmFallback
252 }
253
254 switch {
255 case hw.TotalRAM >= ramTierHigh: // 32GB+
256 return poolPrewarmHigh
257 case hw.TotalRAM >= ramTierMedium: // 16-32GB
258 return poolPrewarmMedium
259 case hw.TotalRAM >= ramTierLow: // 8-16GB
260 return poolPrewarmLow
261 default: // <8GB
262 return poolPrewarmMin
263 }
264}
265
266// resolveCustomProfile returns the user's configured values directly.
267func resolveCustomProfile(cfg *PerformanceConfig) ResolvedPerformanceSettings {
268 return ResolvedPerformanceSettings{
269 SkiaCPUPaintingThreads: cfg.SkiaCPUPaintingThreads,
270 SkiaGPUPaintingThreads: cfg.SkiaGPUPaintingThreads,
271 SkiaEnableCPURendering: cfg.SkiaEnableCPURendering,
272
273 WebProcessMemoryLimitMB: cfg.WebProcessMemoryLimitMB,
274 WebProcessMemoryPollIntervalSec: cfg.WebProcessMemoryPollIntervalSec,
275 WebProcessMemoryConservativeThreshold: cfg.WebProcessMemoryConservativeThreshold,
276 WebProcessMemoryStrictThreshold: cfg.WebProcessMemoryStrictThreshold,
277
278 NetworkProcessMemoryLimitMB: cfg.NetworkProcessMemoryLimitMB,
279 NetworkProcessMemoryPollIntervalSec: cfg.NetworkProcessMemoryPollIntervalSec,
280 NetworkProcessMemoryConservativeThreshold: cfg.NetworkProcessMemoryConservativeThreshold,
281 NetworkProcessMemoryStrictThreshold: cfg.NetworkProcessMemoryStrictThreshold,
282
283 WebViewPoolPrewarmCount: cfg.WebViewPoolPrewarmCount,
284 }
285}
286
287// HasCustomPerformanceFields returns true if any individual performance tuning
288// fields are set to non-default values. Used for validation.
289func HasCustomPerformanceFields(cfg *PerformanceConfig) bool {
290 return cfg.SkiaCPUPaintingThreads != 0 ||
291 (cfg.SkiaGPUPaintingThreads != -1 && cfg.SkiaGPUPaintingThreads != 0) ||
292 cfg.SkiaEnableCPURendering ||
293 cfg.WebProcessMemoryLimitMB != 0 ||
294 cfg.WebProcessMemoryPollIntervalSec != 0 ||
295 cfg.WebProcessMemoryConservativeThreshold != 0 ||
296 cfg.WebProcessMemoryStrictThreshold != 0 ||
297 cfg.NetworkProcessMemoryLimitMB != 0 ||
298 cfg.NetworkProcessMemoryPollIntervalSec != 0 ||
299 cfg.NetworkProcessMemoryConservativeThreshold != 0 ||
300 cfg.NetworkProcessMemoryStrictThreshold != 0
301}
302
303// IsValidPerformanceProfile returns true if the profile name is recognized.
304func IsValidPerformanceProfile(profile PerformanceProfile) bool {
305 switch profile {
306 case ProfileDefault, ProfileLite, ProfileBalanced, ProfileMax, ProfileCustom, "":
307 return true
308 default:
309 return false
310 }
311}