Skip to content

Desktop app frame rate isn't synced to display #49757

Open
flutter-team-archive/engine
#49159

Description

@luigi-rosso

I'm running on a 120hz monitor, I've scheduled a persistent frame callback and can see that I get ~60fps. It would be great if Flutter could use something like CADisplayLink on MacOS to sync frame rendering with the display.

Gist of a simple app showing this: https://gist.github.com/luigi-rosso/0e3ed273e52764bf1d7bdacca49cccd2

image

I can see in various sites, apps, and games that the display is actually refreshing at 120hz.
image

Previously filed here: google/flutter-desktop-embedding#656

Fix status:

  • Windows (note: has the right rate, but may be out of phase)
  • macOS
  • Linux

Activity

  1. added
    e: glfwGLFW desktop embedding
    engineflutter/engine related. See also e: labels.
    frameworkflutter/packages/flutter repository. See also f: labels.
    perf: speedPerformance issues related to (mostly rendering) speed
    c: renderingUI glitches reported at the engine/skia or impeller rendering level
    platform-macosBuilding on or for macOS specifically
    on Jan 30, 2020
  2. removed
    e: glfwGLFW desktop embedding
    c: renderingUI glitches reported at the engine/skia or impeller rendering level
    on Jan 30, 2020
  3. removed
    frameworkflutter/packages/flutter repository. See also f: labels.
    on Jan 30, 2020
  4. added
    c: performanceRelates to speed or footprint issues (see "perf:" labels)
    on Mar 30, 2020
  5. added
    P3Issues that are less important to the Flutter project
    on Jun 10, 2020
  6. TahaTesser commented on Nov 5, 2020

    @TahaTesser
    Contributor

    This is not just mac, I can also see it's 60 fps on Windows even tho my laptop is 144hz. It would be across all desktop apps

    image
    image (1)

    flutter doctor -v
    [✓] Flutter (Channel master, 1.24.0-8.0.pre.97, on Microsoft Windows [Version 10.0.19042.610], locale en-US)
        • Flutter version 1.24.0-8.0.pre.97 at C:\Code\flutter_master
        • Framework revision 4c24eb10b8 (57 minutes ago), 2020-11-05 11:18:00 +0100
        • Engine revision e66a720137
        • Dart version 2.12.0 (build 2.12.0-21.0.dev)
    [✓] Android toolchain - develop for Android devices (Android SDK version 30.0.2)
        • Android SDK at C:\Code\sdk
        • Platform android-30, build-tools 30.0.2
        • Java binary at: C:\Code\android-studio\jre\bin\java
        • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b01)
        • All Android licenses accepted.
    [✓] Chrome - develop for the web
        • Chrome at C:\Program Files\Google\Chrome\Application\chrome.exe
    [✓] Visual Studio - develop for Windows (Visual Studio Community 2019 16.7.7)
        • Visual Studio at C:\Program Files (x86)\Microsoft Visual Studio\2019\Community
        • Visual Studio Community 2019 version 16.7.30621.155
        • Windows 10 SDK version 10.0.18362.0
    [✓] Android Studio (version 4.1.0)
        • Android Studio at C:\Code\android-studio
        • Flutter plugin can be installed from:
          🔨 https://plugins.jetbrains.com/plugin/9212-flutter
        • Dart plugin can be installed from:
          🔨 https://plugins.jetbrains.com/plugin/6351-dart
        • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b01)
    [✓] VS Code (version 1.50.1)
        • VS Code at C:\Users\taha\AppData\Local\Programs\Microsoft VS Code
        • Flutter extension version 3.16.0
    [✓] Connected device (5 available)
        • sdk gphone x86 (mobile) • emulator-5554 • android-x86    • Android 11 (API 30) (emulator)
        • Windows (desktop)       • windows       • windows-x64    • Microsoft Windows [Version 10.0.19042.610]
        • Web Server (web)        • web-server    • web-javascript • Flutter Tools
        • Chrome (web)            • chrome        • web-javascript • Google Chrome 86.0.4240.183
        • Edge (web)              • edge          • web-javascript • Microsoft Edge 86.0.622.61
    • No issues found!
  7. 59 remaining items

  8. mit-mit commented on Jul 29, 2024

    @mit-mit
    Member

    @jmagman is this resolved for macOS now that @knopp landed https://github.com/flutter/engine/pull/51210/files ?

  9. cbracken commented on Aug 6, 2024

    @cbracken
    Member

    Yes, flutter-team-archive/engine#51210 uses CVDisplayLink to sync the display rate on macOS.

    Windows has the right rate, but may be out of phase.

    Linux is still open.

  10. RonBis commented on Sep 12, 2024

    @RonBis

    Any update on this for Linux?

  11. nortio commented on Mar 24, 2025

    @nortio

    Hello, I've started implementing a fix for this issue on Linux, but I'm currently stuck. The solution I've implemented closely mirrors the one used for Windows in #28927 which lets the example mentioned in the first comment work (reports 144fps). Unfortunately I don't think it's working properly since animations on other applications are still clearly running at 60fps. I tried the animations sample from the samples repository and every single one of them still seems to be stuck at a lower frame rate. I then created a new example to see if it has the same issue on Windows, and it's not present there, so it's probably an issue with my implementation. I honestly have no idea where to look now since this codebase is currently alien to me, so if anybody has any idea of what it could be please tell me.

    Here's what I've implemented:

    // In flutter/engine/src/flutter/shell/platform/linux/fl_engine.cc
    
    // Copied from the Windows fix
    static std::chrono::nanoseconds SnapToNextTick(
        std::chrono::nanoseconds value,
        std::chrono::nanoseconds tick_phase,
        std::chrono::nanoseconds tick_interval) {
      std::chrono::nanoseconds offset = (tick_phase - value) % tick_interval;
      if (offset != std::chrono::nanoseconds::zero()) {
        offset = offset + tick_interval;
      }
      return value + offset;
    }
    
    static void fl_engine_vsync_cb(void* user_data, intptr_t baton) {
      FlEngine* self = FL_ENGINE(user_data);
      auto current_time =
          std::chrono::nanoseconds(self->embedder_api.GetCurrentTime());
    
      // this function returns a member value of FlDisplayMonitor that I added and which
      // gets updated by the fl_display_monitor_calculate_frame_interval function
      // described in the paragraph below
      auto frame_interval = std::chrono::nanoseconds(
          fl_display_monitor_get_frame_interval(self->display_monitor));
      // To force a specific interval: auto frame_interval = std::chrono::nanoseconds(1000000);
      auto next = SnapToNextTick(current_time, std::chrono::nanoseconds::zero(),
                                 frame_interval);
      self->embedder_api.OnVsync(self->engine, baton, next.count(),
                                 (next + frame_interval).count());
    }

    In fl_engine_start I then obviously added the callback:

      args.vsync_callback = fl_engine_vsync_cb;

    Using the new example given above, I can see that the fps counter reports 144fps, but when I flick the flutter logo the animation is very choppy compared to Windows (on stable channel it's the same, so I don't think I made it worse). Using the profiler I can see that it takes less than 1ms to draw the frame, so I don't think it's a performance issue either.

    The second issue I encountered while implementing this is relatively minor as it only affects setups with more than one monitor: the engine vsync_callback provides FlEngine and as far as I could tell there is not a simple way to retrieve the GtkMonitor the current FlView is on (and by extension, GtkWindow) in the case multiple monitors are present (and perhaps with multiple FlViews, is that even possible?). So to work around this I simply use the lowest interval between the available monitors listed in FlDisplayMonitor.

    static uint64_t fl_display_monitor_calculate_frame_interval(FlDisplayMonitor* self) {
      uint64_t interval = kDefaultFrameInterval60Fps;  // 60fps, it's 16600000
      auto display = self->display;
    
      for(int i = 0; i < gdk_display_get_n_monitors(display); i++) {
        auto monitor = gdk_display_get_monitor(display, i);
        auto refresh_rate = gdk_monitor_get_refresh_rate(monitor);
        if(refresh_rate <= 0) {
          continue;
        }
    
        interval = MIN(interval, (uint64_t)(1000000000000.0 / refresh_rate));
        g_print("selected interval: %lu, refresh_rate: %d, monitor: %d\n", interval, refresh_rate, i);
      }
    
      return interval;
    }

    This function is called within notify_display_update for the time being as I think it's a bit of an hacky solution. The issue seems to be present also on Windows since the main window always updates at 144fps even when positioned on a 60Hz monitor (a secondary monitor, primary is 144Hz).

    (all tests were done on GNOME 47 on Wayland, haven't tested X11)

  12. knopp commented on Mar 24, 2025

    @knopp
    Member

    There isn't much point implementing vsync waiter on linux right now given that Gtk is currently in charge of flipping the buffers and we have no control over when.

    Eventually I'd like to get to the point where we take over the OpenGL rendering completely, at which point we'll be in charge of frame-pacing, and could use wayland frame callbacks for example for vsync waiting on wayland, which I think should give use the highest accuracy.

  13. nortio commented on Mar 24, 2025

    @nortio

    There isn't much point implementing vsync waiter on linux right now given that Gtk is currently in charge of flipping the buffers and we have no control over when.

    Eventually I'd like to get to the point where take over the OpenGL rendering completely, at which point we'll be in charge of frame-pacing, and could use wayland frame callbacks for example for vsync waiting on wayland, which I think should give use the highest accuracy.

    I understand, so basically my solution is useless. I also just discovered that GLArea seems to be capped at 60fps on GTK3 while GTK4 is not. Oh well, thanks anyway

  14. MinikPLayer commented on Aug 15, 2025

    @MinikPLayer

    There isn't much point implementing vsync waiter on linux right now given that Gtk is currently in charge of flipping the buffers and we have no control over when.
    Eventually I'd like to get to the point where take over the OpenGL rendering completely, at which point we'll be in charge of frame-pacing, and could use wayland frame callbacks for example for vsync waiting on wayland, which I think should give use the highest accuracy.

    I understand, so basically my solution is useless. I also just discovered that GLArea seems to be capped at 60fps on GTK3 while GTK4 is not. Oh well, thanks anyway

    There is an option to change this and i was able to create a working demo of a flutter app running at 165Hz (really working at 165Hz, not only reporting it).

    To achieve this you need the second piece of the puzzle - changing the FRAME_INTERVAL constant in gdk/gdkframeidle.c within the gtk3 library to a value corresponding to your monitor refresh time (6061 for 165Hz in my example). This change combined with your code in the flutter engine allows for 60+ FPS rendering.

    Unfortunately, changing this constant won't be accepted in the gtk-3 repo, but a proper patch with refresh rate detection could be merged, which would allow for high refresh rate flutter applications.

    Of course the real solution is to port flutter from GTK3 to GTK4, but this is a much bigger project.

  15. ales-drnz commented on Aug 6, 2026

    @ales-drnz

    I wrote up the full mechanism behind this on #183703, which covers why GDK's frame clock stays at 60 for a Flutter window, with a reproducer that shows the same GTK3 window running at 144fps through cairo and 60fps through gdk_cairo_draw_from_gl.

    @nortio it confirms what you saw with GtkGLArea, and @MinikPLayer it explains why patching FRAME_INTERVAL works: the timings never complete, so the clock always falls back to that constant.

  16. knopp commented on Aug 6, 2026

    @knopp
    Member

    There is effort underway to use wayland subsurfaces on linux, in which case the vsync will come directly from wayland independent of GTK.

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

    P2Important issues not at the top of the work lista: desktopRunning on desktopc: performanceRelates to speed or footprint issues (see "perf:" labels)engineflutter/engine related. See also e: labels.found in release: 3.19Found to occur in 3.19found in release: 3.22Found to occur in 3.22has reproducible stepsThe issue has been confirmed reproducible and is ready to work onperf: speedPerformance issues related to (mostly rendering) speedplatform-linuxBuilding on or for Linux specificallyplatform-macosBuilding on or for macOS specificallyteam-macosOwned by the macOS platform teamtriaged-macosTriaged by the macOS platform team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions