Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit aee1bc6

Browse files
committed
VSync callback support for Linux
This change adds support for VSync callbacks on Linux, making it possible to run at high refresh rates. The heavy lifting is done by GDK; and so the implementation just wires the Flutter internals to it. Unresolved: the implementation conflicts with "smooth resizing" support (#25884). Smooth resizing blocks the main thread until a frame is rendered, but this also blocks the VSync callbacks necessary to make progress in rendering, resulting in a livelock. The smooth resizing is commented out for now.
1 parent 1c4e42e commit aee1bc6

File tree

2 files changed

+54
-2
lines changed

2 files changed

+54
-2
lines changed

shell/platform/linux/fl_engine.cc

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#include <gmodule.h>
88

9+
#include <atomic>
910
#include <cstring>
1011

1112
#include "flutter/shell/platform/linux/fl_binary_messenger_private.h"
@@ -44,6 +45,9 @@ struct _FlEngine {
4445
FlEngineUpdateSemanticsNodeHandler update_semantics_node_handler;
4546
gpointer update_semantics_node_handler_data;
4647
GDestroyNotify update_semantics_node_handler_destroy_notify;
48+
49+
// Stored baton for plumbing vsync callbacks.
50+
intptr_t vsync_baton;
4751
};
4852

4953
G_DEFINE_QUARK(fl_engine_error_quark, fl_engine_error)
@@ -214,6 +218,48 @@ static bool fl_engine_gl_make_resource_current(void* user_data) {
214218
return result;
215219
}
216220

221+
static gboolean fl_engine_request_vsync(gpointer user_data) {
222+
FlEngine* self = static_cast<FlEngine*>(user_data);
223+
GdkFrameClock* clk = gtk_widget_get_frame_clock(
224+
GTK_WIDGET(fl_renderer_get_view(self->renderer)));
225+
gdk_frame_clock_request_phase(clk, GDK_FRAME_CLOCK_PHASE_UPDATE);
226+
return false;
227+
}
228+
229+
static void fl_engine_vsync_callback(void* user_data, intptr_t btn) {
230+
FlEngine* self = static_cast<FlEngine*>(user_data);
231+
// Thread safety: only one of vsync_callback or handle_frame_clock_update can
232+
// execute at one time. This is because VSync callback can only be requested
233+
// upon the completion of the previous VSync callback.
234+
self->vsync_baton = btn;
235+
// Run frame clock operations on the main thread to synchronize the accesses.
236+
g_idle_add(fl_engine_request_vsync, user_data);
237+
}
238+
239+
static void fl_engine_handle_frame_clock_update(GdkFrameClock* clk,
240+
void* user_data) {
241+
FlEngine* self = static_cast<FlEngine*>(user_data);
242+
if (self->vsync_baton != 0) {
243+
// Note: it's crucial to reset the vsync_baton before we call OnVsync, since
244+
// OnVsync (either synchronous or ran on another thread) might request
245+
// another vsync callback inside it.
246+
auto btn = self->vsync_baton;
247+
self->vsync_baton = 0;
248+
gint64 frame_time = gdk_frame_clock_get_frame_time(clk);
249+
gint64 refresh_interval;
250+
gint64 presentation_time;
251+
gdk_frame_clock_get_refresh_info(clk, frame_time, &refresh_interval,
252+
&presentation_time);
253+
if (presentation_time == 0) {
254+
// GDK could not predict next presentation due to lack of history.
255+
// A fallback is used.
256+
presentation_time = frame_time + refresh_interval;
257+
}
258+
self->embedder_api.OnVsync(self->engine, btn, frame_time * 1000,
259+
presentation_time * 1000);
260+
}
261+
}
262+
217263
// Called by the engine to determine if it is on the GTK thread.
218264
static bool fl_engine_runs_task_on_current_thread(void* user_data) {
219265
FlEngine* self = static_cast<FlEngine*>(user_data);
@@ -326,6 +372,7 @@ static void fl_engine_class_init(FlEngineClass* klass) {
326372

327373
static void fl_engine_init(FlEngine* self) {
328374
self->thread = g_thread_self();
375+
self->vsync_baton = 0;
329376

330377
self->embedder_api.struct_size = sizeof(FlutterEngineProcTable);
331378
FlutterEngineGetProcAddresses(&self->embedder_api);
@@ -401,6 +448,11 @@ gboolean fl_engine_start(FlEngine* self, GError** error) {
401448
dart_entrypoint_args != nullptr ? g_strv_length(dart_entrypoint_args) : 0;
402449
args.dart_entrypoint_argv =
403450
reinterpret_cast<const char* const*>(dart_entrypoint_args);
451+
args.vsync_callback = fl_engine_vsync_callback;
452+
GdkFrameClock* clk = gtk_widget_get_frame_clock(
453+
GTK_WIDGET(fl_renderer_get_view(self->renderer)));
454+
g_signal_connect(clk, "update",
455+
G_CALLBACK(fl_engine_handle_frame_clock_update), self);
404456

405457
FlutterCompositor compositor = {};
406458
compositor.struct_size = sizeof(FlutterCompositor);

shell/platform/linux/fl_view.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,8 @@ static void fl_view_geometry_changed(FlView* self) {
142142
self->engine, allocation.width * scale_factor,
143143
allocation.height * scale_factor, scale_factor);
144144

145-
fl_renderer_wait_for_frame(self->renderer, allocation.width * scale_factor,
146-
allocation.height * scale_factor);
145+
// fl_renderer_wait_for_frame(self->renderer, allocation.width * scale_factor,
146+
// allocation.height * scale_factor);
147147
}
148148

149149
// Implements FlPluginRegistry::get_registrar_for_plugin.

0 commit comments

Comments
 (0)