You do not need these to build Kute. The patched libcef.dll ships in resources/cef/ (Git LFS) and the build copies it over the stock one. They are here so everybody can see what the DLL changes, and to rebuild it for a new CEF version.
Plain git diff files against chromium/src at 151.0.7922.174 (CEF branch 7922, the version of the cef crate in Cargo.toml), applied on top of CEF's own Chromium patches.
-
01-input-priority.patch:main_thread_scheduler_impl.cc, featureKuteInputNormalPriority(off by default, Kute turns it on for the "Input at Normal Priority" setting). Input task queues run at normal instead of highest priority and the compositor priority is capped at normal. Without it, continuous mouse input with--disable-frame-rate-limitstarves WebSocket and worker messages on a busy main thread (the Krunker "aim freeze", Chromium bug 415071737). With the feature off both functions run the stock code. From bigjakk/Electron-Websocket-Fix. -
02-frame-pacing.patch:cc/scheduler/scheduler_state_machine.cc, featureKuteFramePacing(off by default, Kute turns it on for the "Frame Pacing" setting).IsDrawThrottled()no longer exemptsdisable_frame_rate_limit, so the renderer stops flooding the main thread with back to back BeginMainFrames. This is what fixes the stutter when the GPU is the limit. Queue depth is the feature paramCustomMaxPendingFrames:count/N(default 1), which stays enabled by default and only carries the number. WithKuteFramePacingoff and the count at 1 the function is stock. From thegu5 and bigjakk. -
03-raw-input-movement.patch:ui/views/win/hwnd_message_handler.cc, featureKuteRawInputMovementOnly(off by default, Kute turns it on insrc/app.rs). The raw mouse path keeps the movement of every packet and takes no button state from raw input. Stock Chromium skips a packet whose only flag is a wheel step, and Kute used to drop every packet with a button change before Chromium read it, so the page would not see the button in move events. Both threw away the movement in that packet: 6 to 9 counts per click or release, right at the shot. Also reads a mouse packet with oneGetRawInputDatacall into a stack buffer instead of a size query, a heap allocation and a second call. While the feature is on,src/modules/input.rsskips its ownWM_INPUTfilter;--disable-features=KuteRawInputMovementOnlyinuser_flags.jsonbrings the old way back. -
04-panner-per-quantum.patch:panner_handler.cc, featureKuteAudioPannerPerQuantumwith the paramquanta(default 3), off by default, Kute turns it on for the "Fix Audio Stutters" setting.panner.positionX.value = xschedules an automation event, so a game that writes sound positions once per rendered frame keeps every panner on Chromium's sample accurate path, which recomputes azimuth, elevation and distance gain for every frame of every quantum. With the feature the panner stays on the per quantum path and is re-panned at most everyquantaquanta (8 ms at 48 kHz), which also stops the HRTF kernel crossfade from running in every quantum. The angles a plain assignment produces are constant across a quantum anyway. Panning itself is unchanged: hard left, hard right and a one second sweep verified in an OfflineAudioContext. -
05-audioparam-coalesce.patch:audio_param_handler.cc, featureKuteAudioParamCoalesce, off by default.setTargetAtTime/setValueAtTimeclamp their start time tocurrentTime, which stops while a context is suspended. Krunker's Howler suspends its context 30 s after its last sound (withambient_*blocked that's most of a match) and the game keeps moving the listener withsetTargetAtTimeevery frame, so every param gains one event per frame, all with the same time.InsertEvent's overlap scan then walks the whole list on every call, and the list never shrinks. An event followed by another at the same time lasts zero time, so the patch replaces a trailing SetTarget when a SetTarget or SetValue with the same time arrives (and a trailing SetValue before another SetValue) instead of appending. A SetValue followed by a SetTarget stays, the SetTarget starts from that value. -
06-canvas-buffer-cache.patch:third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.cc, featureKuteCanvasBufferCachewith the paramcount(default 3), off by default, Kute turns it on insrc/app.rs. WebGL keeps at most one spare color buffer on Windows (kDefaultColorBufferCacheLimit = 1, Fuchsia already uses 2 "to avoid reallocation", crbug.com/1087941). When the compositor hands two back at once, one is destroyed and a new shared image is created a frame later: on the orb bench scene 0.06 to 0.17 creations per frame, each a GPU allocation plus clear that shows up as a frame with 4x the GPU time. How often depends on the timing phase a launch settles into, which is why launches landed in a fast or a slow mode. Measured 2026-09-28 on the orb scene, same DLL, 8 launches each: off 1384 to 1626 FPS (1 % low 377 to 445), on 1970 to 2033 FPS (1 % low 693 to 1319), every launch in the fast mode. In a Krunker match the churn is ten times rarer (0.017 per frame) and FPS did not change measurably; it matters for the auto-detect client bench, which runs on the orb scene. Costs one or two extra canvas sized buffers of GPU memory. -
07-high-qos-foreground.patch:base/process/process_win.cc, featureKuteHighQoSForeground, off by default, Kute turns it on insrc/app.rs.Process::SetPrioritygave a foreground (kUserBlocking) process an unset EcoQoS state, which hands the decision back to Windows' heuristics, and those may throttle a windowless process such as the game's renderer (the host sets HighQoS on itself, but the browser overwrites the renderer's state from outside). With the feature a foreground process is set to explicit HighQoS (ProcessPowerState::kDisabled); background processes still get EcoQoS as before. -
08-frame-limiter.patch:components/viz/service/display/display_scheduler.{h,cc}, featureKuteFrameLimiter, off by default, Kute turns it on for the "Chromium FPS Limiter" setting. With--disable-frame-rate-limitan FPS cap had to be held elsewhere: the DXGI hook slept insidePresenton the GPU thread (sleep plus a 1 ms spin per frame, and the whole wait spun once frames were under 2 ms: 1.65 cores of CPU at a 500 cap), and without the hook the bundle busy-waited on the game's main thread (every other task up to 11 ms late at a 240 cap). What the hook's sleep really did was delay the swap ack, and a pending swap is what keepsDisplaySchedulerfrom drawing the next frame (pending_swaps_, the "Swap throttled" deadline mode). With the featureDidReceiveSwapBuffersAckholds the ack until the next deadline of the interval the host writes into itsKuteFrameTimingmapping (target_fps, read live, the slider works), then runs the stock handling. Frames reach the screen on a fixed grid through the same state machine path as with the hook, no thread sits inPresent, nothing spins: a preciseDeadlineTimercovers all but the last 2 ms, a high resolution waitable timer the rest (a plain delayed task got its wake aligned, 1.7 ms late at 240). Measured against the hook's limiter: same pacing on the orb scene (p99 4.8 ms at 240, 2.5 ms at 500), 20 of 20 seconds at exactly 500 with the hook on and off, input age in the game page equal or slightly better (p99 5.2 vs 5.3 ms at 240, 2.9 vs 3.1 ms at 500), half the CPU of the old hook wait, and it works with the hook off and on any backend. Three earlier designs failed and are worth knowing: pacing theBackToBackBeginFrameSourcehalves the rate at 500 (the Display draws one tick after the renderer submits); holding the renderer's surface acks inDisplay::DrawAndSwapworks for seconds and then drops to half rate, because a renderer that waits for its ack is a "pending surface" to the scheduler and which deadline mode it picks depends on interleaving. The host writeslimiter_modeinto the mapping so the hook skips its own wait while this paces (render-dllLIMITER_VIZ). -
09-frame-limiter-linux.patch: applies on top of 08, Linux only (#elif BUILDFLAG(IS_LINUX), the Windows code is untouched). 08 reads the limit from a Windows file mapping and does its last 2 ms on a waitable timer, so on Linux the feature did nothing. Here the host'sKuteFrameTimingblock is POSIX shared memory (shm_open("/KuteFrameTiming"), created byapp.rsbefore the GPU process starts, Kute runs without the sandbox), and the last 2 ms are an absoluteclock_nanosleeponCLOCK_MONOTONIC, the clockbase::TimeTicksuses on Linux. Not measured yet: pacing and input age need a real Linux session with a real GPU, WSLg presents through RDP. -
10-present-stats-linux.patch: newcomponents/viz/service/display/kute_present_stats.{h,cc}plus three calls indisplay.cc, featureKutePresentStats, off by default, the Linux host always turns it on (app.rs). On Windowsrender.dll's Present1 hook fills the host'sKuteFrameTimingblock with present statistics; Linux has no such hook, so viz writes the same fields itself:frame_nsandfpsas a moving average (gaps over 250 ms restart it),hook_stateready, and on a host request (stats_request/stats_ack) the p50, p99 and maximum of the present intervals since the last request, all with render-dll's constants so auto-detect compares the same numbers on both platforms. A present is a frameDisplay::DrawAndSwaphands toSwapBuffers, after patch 08/09's pacing, the counterpart ofPresent1. The sort runs after the swap was issued, never in front of the frame. Each window has its ownDisplay: the one that swaps keeps the statistics and another (a social popup) only takes over after 300 ms without a swap, render-dll'sMAIN_SILENT_MS; displays under 200 px high never count.arrive_p99_nsstays 0, there is no wait in front of the swap. Code inside is Linux only (BUILDFLAG(IS_LINUX)), elsewhere the feature has nothing to map. -
11-no-exclusive-bubble.patch:chrome/browser/ui/views/frame/browser_view.cc, featureKuteNoExclusiveAccessBubble, off by default, the Linux host always turns it on (app.rs). On Linux the browser is a Chrome style Views browser, so Kute's F11 (Window::set_fullscreen) goes through Chrome's fullscreen controller, which shows the "Press Esc to exit full screen" bubble, and pointer lock shows "Press Esc to show your cursor". The bubble is a separate window over the game: while it shows, Wayland and X11 compositors (Hyprland, KWin) give the game no direct scanout and no tearing (player report 2026-10-07, the first seconds after F11 played with more latency). With the featureUpdateExclusiveAccessBubbletakes the path it already has for trusted pinned mode: no bubble, a shown one is closed. Esc handling itself is untouched. Not applied to the Windows DLL: the Win32 host makes its own window fullscreen and the browser never knows. -
12-libcef-thinlto-max.patch:cef/BUILD.gn(CEF's own repo, not Chromium), no feature switch, build-time only. Thelibceftarget links with Chromium'sthinlto_optimize_defaultconfig, which runs the ThinLTO link step at optimization level 0 (/opt:lldlto=0);chrome.dllitself takesthinlto_optimize_max(/opt:lldlto=2, cross-module inlining and optimization at link time). The patch gives libcef the same config. Relink only, about 10 minutes, the DLL grows from 288 to 304 MB. Measured 2026-10-13 on a 12900K/3090 Ti: orb bench scene +5 % frames with less CPU in four of four interleaved rounds, private Burg match with the host's input replay +6 to +13 % frames (1119/1151/1153 to 1252/1218/1299) with shorter frame tails in three of three rounds. Thorium's twelve extra-mllvmlink flags on top measured below this alone, and a ThinLTOimport_instr_limitof 100 instead of 30 measured within noise (two rounds up, two down), both dropped. The Linuxlibcef.sois relinked with it too.
Every patch is the exact diff of the tree the shipped DLL was built from. Since 2026-10-01 each one has a feature switch and a setting in Kute's Engine category (src/app.rs::PATCHES maps setting to feature), so a player and the auto-detect bench can turn any of them off: a --disable-features= line is pushed for a setting that is off, and Chromium lets the disable list win over user_flags.json.
What the shipped DLL was built with: CEF branch 7922, CEF commit 2384915b7b1f0fe5ad1107e48d80c34e86b698d7, Chromium 151.0.7922.174 (cef_binary_151.3.24+g2384915+chromium-151.0.7922.174, the distribution cef-dll-sys downloads, see Cargo.lock). Windows x64, Visual Studio 2022 with ATL, Windows SDK 10.0.26100.0, Python 3.12, git with long paths, about 100 GB of disk. For a newer CEF, take the branch and commit from the tarball name cef-dll-sys downloads.
-
Environment for every step (cmd):
set GN_DEFINES=is_official_build=true set GYP_MSVS_VERSION=2022 set DEPOT_TOOLS_WIN_TOOLCHAIN=0 set CEF_ARCHIVE_FORMAT=tar.bz2 -
Checkout with CEF's
automate-git.py(from the CEF repo,tools/automate/), about 30 GB and 40 minutes. This fetches depot_tools, CEF and Chromium and applies CEF's own patches:py -3.12 automate-git.py --download-dir=C:\cef --branch=7922 --checkout=2384915b7b1f0fe5ad1107e48d80c34e86b698d7 --x64-build --no-chromium-history --with-pgo-profiles --no-build --no-distrib -
Generate the build directories (with
C:\cef\depot_toolsonPATH): inchromium\src\cefrunpython3.bat tools\gclient_hook.py. Then append tochromium\src\out\Release_GN_x64\args.gn:symbol_level=0 blink_symbol_level=0 v8_symbol_level=0 -
Apply the patches in
chromium\src, in order:git apply <kute>\patches\01-input-priority.patch git apply <kute>\patches\02-frame-pacing.patch git apply <kute>\patches\03-raw-input-movement.patch ... git apply <kute>\patches\12-libcef-thinlto-max.patchIf one rejects on a new Chromium, the places to find are
MainThreadSchedulerImpl::ComputePriority(thekInputcase) andComputeCompositorPriority(01),SchedulerStateMachine::IsDrawThrottled(02),HWNDMessageHandler::OnInputEvent(03), theshared_library("libcef")target'sconfigsincef/BUILD.gn(12, the file lives in the nestedcefgit repo, so save its diff withgit -C cef diff --src-prefix=a/cef/ --dst-prefix=b/cef/). Save the re-anchoredgit diffback into this folder. -
Build (with
C:\cef\depot_toolsonPATH, inchromium\src), about 3 hours from scratch on a 12900K, one to two minutes for a small change afterwards:autoninja -C out\Release_GN_x64 cefclient bootstrap bootstrapc -
Copy
chromium\src\out\Release_GN_x64\libcef.dlltoresources\cef\libcef.dlland commit it (Git LFS). Onlylibcef.dlldiffers from the official distribution;v8_context_snapshot.binandicudtl.datcome out byte identical, so the rest stays stock.
A libcef.dll that does not match the cef crate version crashes on start: when bumping the crate, rebuild first or delete resources/cef/libcef.dll.
Same CEF branch, commit and Chromium version as above. Built in a WSL2 Ubuntu 24.04 distro with 48 GB of memory and 32 GB of swap (.wslconfig), the tree on the distro's own disk, never under /mnt/c. The checkout took 9 minutes and 30 GB.
-
Environment for every step:
export GN_DEFINES="use_sysroot=true is_official_build=true proprietary_codecs=true ffmpeg_branding=Chrome symbol_level=0 blink_symbol_level=0 v8_symbol_level=0" export CEF_ARCHIVE_FORMAT=tar.bz2use_sysroot=truebuilds against Chromium's Debian bullseye sysroot like CEF's own releases (old glibc, runs on older distros). Without it GN looks for the host's development packages and stops at the first missing one (libpipewire-0.3). -
Checkout with the same
automate-git.pycall as on Windows (--download-dir=$HOME/cef, nopy -3.12), thensudo build/install-build-deps.sh --no-prompt --no-arm --no-nacl --no-chromeos-fontsandpython3 build/linux/sysroot_scripts/install-sysroot.py --arch=amd64inchromium/src. -
Apply 01, 02, 04, 05, 06, 08, 09, 10 and 11 in
chromium/src. 03 and 07 only touch Windows files and are left out, soKuteRawInputMovementOnlyandKuteHighQoSForegrounddo not exist in this build. 12 applies on Linux too (-Wl,--lto-O2there, the relink took 15 minutes in WSL2, stripped 282 to 299 MB); the shippedlibcef.sohas it since 2026-10-13, checked in WSLg: page loads, limit 60 gives 60 fps (p50 16.7 ms), uncapped 109 (RDP presents, no performance number possible there). -
In
chromium/src/cefrun./cef_create_projects.sh, then inchromium/src:autoninja -C out/Release_GN_x64 libcef1 h 45 min from scratch on a 12900K in WSL2 (63k steps), the link of
libcef.soalone a few minutes. -
strip --strip-unneededthe result (525 MB to 282 MB) intoresources/cef-linux/libcef.soand commit it (Git LFS).postbuild.jscopiesresources/cef-linux/over the stock runtime on Linux,resources/cef/on Windows.
Checked in WSLg (2026-10-05): every Linux feature name is in the binary, KuteRawInputMovementOnly is not. With a limit of 30 and 60 the game page ran at exactly 30 and 60 FPS (median frame 33.3 and 16.7 ms), the stock libcef.so with the same flags at 348 and 251. WSLg cannot say anything about pacing or latency.
- 01 and 02: the aim freeze stress test (a 12 s mouse flood over CDP on a page that spends 3 ms of JavaScript per frame, next to a 60 Hz WebSocket). Stock CEF freezes WebSocket delivery for 7 to 12 s, the patched DLL keeps every gap under about 36 ms, with no frame rate cost.
- 04: the audio thread's own time per second of audio, from Chromium's
webaudiotrace events, with 48 moving HRTF voices and a fixed 300 position writes a second, interleaved with a restart per configuration. Off: 407.6 and 309.6 ms/s. On: 139.6 and 137.9 ms/s.PannerHandler::Processwent from 18.8 us per quantum to 7.9. - 05:
C:\cef\harness�udioparam-equiv.htmlrenders 16 automation scripts offline (ramps, curves, cancels, cancel and hold, suspend and resume, same-time piles, the value setter, an HRTF panner moved like Howler does), each also without its same-time duplicates. With the feature off the patched DLL is bit-identical to stock in all of them. With it on, the scripts without duplicates stay bit-identical, and every script with duplicates renders bit-identical to stock rendering the same script without them. Stock itself deviates there: a pile of same-time SetTargets after a SetValue loses the SetValue, while one SetTarget keeps it. Chromium's AudioParam, Panner, AudioListener and ConstantSource web tests (94 files, 2461 subtests,wpt-run.mjs): same results in all three configurations (one subtest fails in stock too and has an expected file upstream). 200k calls into a suspended context: stock 9.5 ms per 10k calls growing to 773, patched a flat 3. In a match, idle with the context suspended: uncapped FPS stayed at 1480 to 1520 for 150 s (stock fell from 1600 to about 550); capped at 240 the main thread stayed at 28 % busy for 229 s (stock reached 96 % at 214 s). - 03: raw mouse counts against the movement the page received over the same 60 s in a match: 99.88 % with the patch, 99.65 % without (the difference is what the old filter dropped), presses and releases identical. The aim freeze test still passes with it (worst gaps 20.6 and 24.5 ms, stock 7.4 and 8.3 s in the same session).
- 06: orb bench scene (the auto-detect client bench), same DLL with the feature off and on through
user_flags.json, 8 alternating launches each: off 1384 to 1626 FPS (median 1583), 1061 to 1495 frames over twice the median per 8 s, 1 % low 377 to 445; on 1970 to 2033 FPS (median 2009), 3 to 187 such frames, 1 % low 693 to 1319. A Chromium trace of fast and slow launches (event counts per presented frame on the GPU main thread) showedSharedImageStub::OnCreateSharedImage/OnDestroySharedImageas the only difference, 0.056 vs 0.169 per frame. In a private match: 0.017 per frame, FPS unchanged within noise (1375 to 1490 both ways). Enabled by default in a build: 3 of 3 launches 1963 to 2031 FPS. - 07: measured neutral on an i9-12900K (forcing HighQoS on renderer and GPU process from outside changed nothing, 1480 FPS either way); it is a safety net for PCs where Windows would otherwise guess EcoQoS. Checked with GetProcessInformation(ProcessPowerThrottling) on every client process: the game's renderer reports explicit HighQoS, background renderers keep EcoQoS.
- 11: in WSLg (X11), page fullscreen over CDP with a user gesture: the old
libcef.somaps a 422x51 window for the bubble that stays for the 7.5 s watched, the new one maps none, and the new one with--disable-features=KuteNoExclusiveAccessBubblemaps it again. Page FPS old against new, three interleaved launches each: 101, 136, 107 against 97, 98, 99 (WSLg, presents through RDP; the code only runs when a bubble would change). Pointer lock could not be tested in WSLg, the window does not get focus. Direct scanout and tearing themselves need a real Hyprland or KWin session.