You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
perf(linux): app is capped at 60 fps because the GTK embedder has no vsync #980
On Linux the app never draws faster than 60 fps, even on a 120 / 144 Hz monitor and even when frames are cheap. This is an engine limitation, not something our widget code can fix.
Evidence
Profile build, Hyprland (Wayland), monitor 2880x1800@120, PlatformDispatcher.views.first.display.refreshRate == 120.0. Real-app benchmark (benchmark/app_perf_bench.dart), sidebar toggle animation: build p50 4 ms, raster p50 0.8 ms, yet the interval between presented frames is 16.67 ms (p50), i.e. 60 fps. Same for every scenario (grid scroll, hover, tree scroll).
Cause (upstream)
shell/platform/linux/fl_engine.cc never sets FlutterProjectArgs.vsync_callback, so the engine uses VsyncWaiterFallback.
It cannot be worked around from Dart: frames only enter the raster pipeline from a vsync-driven Animator::BeginFrame (a pacer built on PlatformDispatcher.scheduleWarmUpFrame was prototyped and produced 0 presented frames).
Built libflutter_linux_gtk.so (profile) from the 3.47.1 tag with only the production vsync changes of flutter/flutter#192342 (fl_engine.cc, fl_display_monitor.cc/.h). The Wayland subsurface part of that PR does not apply: 3.47.1 has no subsurface renderer yet. The patched library was swapped into a profile bundle of benchmark/app_perf_bench.dart; everything else identical. Hyprland, 120 Hz, window 2056×2616.
Note: the patch is a display-rate timer (GTK exposes no vsync for the window), not a hardware vsync.
Scenario
stock engine
patched engine
Grid horizontal scroll
59 fps
119 fps
Hover over grid
60 fps
120 fps
Grid vertical scroll
60 fps
110 fps
Tree scroll (1500 tables)
60 fps
112 fps
Tree expand / collapse
60 fps
114 fps
Sidebar toggle animation
60 fps
60 fps
Sidebar stays at 60 fps
Dart work per frame is ~4.5 ms (Animator::BeginFrame) + ~1.1 ms semantics, well under 8.3 ms, yet every other tick is missed. Experiment: wrapping the app in ExcludeSemantics (bench only) raises the sidebar to ~91 fps (median interval 8.33 ms, ~30 % of frames still late) with the same Dart cost. The sidebar animation changes the geometry of the whole semantics tree every frame; on Linux those updates are turned into ATK objects on the GTK main thread, which is also where the engine's vsync callback runs, so ticks are delayed. This ties into #981 (fewer semantics nodes) and #984 (sidebar relayout).
Conclusion
The engine change works: Linux reaches the display rate for scroll, hover and tree interactions.
To ship it before upstream lands we would need our own engine build per Flutter upgrade (this build: gclient sync without Android / JDK / Fuchsia deps, gn --runtime-mode release, ninja libflutter_linux_gtk.so, ~4000 steps).
Semantics volume is the next limiter on Linux even with vsync.
CI wiring: patched engine repo + splice script (PR #995)
Turned the local test above into infra:
QueryaHub/flutter-engine-linux-120hz — new repo holding the vsync patch (patches/vsync-fl_engine-fl_display_monitor.patch, just the fl_engine.cc/fl_display_monitor.cc/.h part of Use vsync in Linux embedder flutter/flutter#192342), a scripts/build.sh/publish_release.sh pair to rebuild it for a new Flutter version, and the first published build: release-and-profile libflutter_linux_gtk.so for engine content hash 11d79658c444477b06513d32b52c8c4ccb7276b0 (Flutter 3.47.1's engine).
Note: .github/workflows/build.yml in that repo (a workflow_dispatch job to rebuild in CI) is committed to the branch but not yet pushed — my token lacked the workflow OAuth scope needed to push files under .github/workflows/. Either grant that scope (gh auth refresh -s workflow) or add the file by hand from the branch; it's a straightforward, documented-but-unvalidated translation of the manual recipe used for this issue's benchmark.
querya-desktop#995: scripts/linux/apply_patched_engine.sh splices the matching release asset into build/linux/x64/release/bundle/lib/libflutter_linux_gtk.so after flutter build linux --release, keyed by the running Flutter SDK's engine content hash. No match → warns and ships the stock engine, never fails the build.
Blocker before this actually changes what ships: our CI pins Flutter 3.41.6; the published patched build matches 3.47.1's engine (built against my local SDK while validating the fix). Need a call on:
Build+publish a patched engine for 3.41.6's specific content hash instead (keeps the current Flutter pin).
Leaving this issue open until one of those lands and a Linux release build actually ships at the display refresh rate (the original acceptance criteria).
Activity
ZhuchkaTriplesix commentedon Sep 25, 2026
Local test: engine 3.47.1 + vsync part of flutter/flutter#192342
Built
libflutter_linux_gtk.so(profile) from the3.47.1tag with only the production vsync changes of flutter/flutter#192342 (fl_engine.cc,fl_display_monitor.cc/.h). The Wayland subsurface part of that PR does not apply: 3.47.1 has no subsurface renderer yet. The patched library was swapped into a profile bundle ofbenchmark/app_perf_bench.dart; everything else identical. Hyprland, 120 Hz, window 2056×2616.Note: the patch is a display-rate timer (GTK exposes no vsync for the window), not a hardware vsync.
Sidebar stays at 60 fps
Dart work per frame is ~4.5 ms (
Animator::BeginFrame) + ~1.1 ms semantics, well under 8.3 ms, yet every other tick is missed. Experiment: wrapping the app inExcludeSemantics(bench only) raises the sidebar to ~91 fps (median interval 8.33 ms, ~30 % of frames still late) with the same Dart cost. The sidebar animation changes the geometry of the whole semantics tree every frame; on Linux those updates are turned into ATK objects on the GTK main thread, which is also where the engine's vsync callback runs, so ticks are delayed. This ties into #981 (fewer semantics nodes) and #984 (sidebar relayout).Conclusion
gclient syncwithout Android / JDK / Fuchsia deps,gn --runtime-mode release,ninja libflutter_linux_gtk.so, ~4000 steps).ZhuchkaTriplesix commentedon Sep 27, 2026
CI wiring: patched engine repo + splice script (PR #995)
Turned the local test above into infra:
patches/vsync-fl_engine-fl_display_monitor.patch, just thefl_engine.cc/fl_display_monitor.cc/.hpart of Use vsync in Linux embedder flutter/flutter#192342), ascripts/build.sh/publish_release.shpair to rebuild it for a new Flutter version, and the first published build: release-and-profilelibflutter_linux_gtk.sofor engine content hash11d79658c444477b06513d32b52c8c4ccb7276b0(Flutter 3.47.1's engine)..github/workflows/build.ymlin that repo (aworkflow_dispatchjob to rebuild in CI) is committed to the branch but not yet pushed — my token lacked theworkflowOAuth scope needed to push files under.github/workflows/. Either grant that scope (gh auth refresh -s workflow) or add the file by hand from the branch; it's a straightforward, documented-but-unvalidated translation of the manual recipe used for this issue's benchmark.scripts/linux/apply_patched_engine.shsplices the matching release asset intobuild/linux/x64/release/bundle/lib/libflutter_linux_gtk.soafterflutter build linux --release, keyed by the running Flutter SDK's engine content hash. No match → warns and ships the stock engine, never fails the build.Blocker before this actually changes what ships: our CI pins Flutter 3.41.6; the published patched build matches 3.47.1's engine (built against my local SDK while validating the fix). Need a call on:
Leaving this issue open until one of those lands and a Linux release build actually ships at the display refresh rate (the original acceptance criteria).
Merge pull request #995 from QueryaHub/issue/980-linux-vsync-patched-…
ZhuchkaTriplesix commentedon Oct 8, 2026
Done in #995