Skip to content

perf(linux): app is capped at 60 fps because the GTK embedder has no vsync #980

Closed

Description

@ZhuchkaTriplesix

Summary

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.
  • shell/common/vsync_waiter_fallback.cc hard-codes kSingleFrameInterval = 1.0 / 60.0.
  • 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).

Upstream work: flutter/flutter#192342 "Use vsync in Linux embedder" (open, Sep 2026) adds a vsync callback and also removes a full-window software repaint per frame on Wayland subsurfaces. Related: flutter/flutter#183703, flutter/flutter#190620.

Windows (DWM vsync) and macOS (CVDisplayLink / ProMotion) already follow the display rate.

Options

  1. Track Use vsync in Linux embedder flutter/flutter#192342 and upgrade Flutter once it ships (no cost, unknown timing).
  2. Build a patched libflutter_linux_gtk.so with that PR for Linux release bundles (CI + maintenance on every Flutter upgrade).
  3. Meanwhile keep our frame cost within the 120 Hz budget so we benefit immediately on Windows / macOS and on Linux once vsync lands.

Acceptance

Linux release reaches the display refresh rate in the sidebar toggle and grid scroll scenarios of benchmark/app_perf_bench.dart.

Activity

  1. added
    performanceTheme parser epic label: performance
    motionAnimation system, transitions, motion tokens
    on Sep 25, 2026
  2. ZhuchkaTriplesix commented on Sep 25, 2026

    @ZhuchkaTriplesix
    MemberAuthor

    Local test: engine 3.47.1 + vsync part of flutter/flutter#192342

    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.
  3. ZhuchkaTriplesix commented on Sep 27, 2026

    @ZhuchkaTriplesix
    MemberAuthor

    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:

    1. Bump CI's Flutter pin to 3.47.1 (then perf(linux): splice in vsync-patched engine on release builds (#980) #995's splice starts working immediately), or
    2. 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).

  4. added a commit that references this issue on Sep 27, 2026
  5. ZhuchkaTriplesix commented on Oct 8, 2026

    @ZhuchkaTriplesix
    MemberAuthor

    Done in #995

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    motionAnimation system, transitions, motion tokensperformanceTheme parser epic label: performance

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions