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

Commit cfccc57

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. FlTaskRunner is also modified to allow the VSync callback to get through while the main thread is blocked. v2: Fix compatibility with smooth resizing.
1 parent 1c4e42e commit cfccc57

6 files changed

Lines changed: 90 additions & 21 deletions

File tree

shell/platform/linux/fl_engine.cc

Lines changed: 67 additions & 6 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"
@@ -20,6 +21,8 @@
2021
// Unique number associated with platform tasks.
2122
static constexpr size_t kPlatformTaskRunnerIdentifier = 1;
2223

24+
static constexpr int kMicrosecondsPerNanosecond = 1000;
25+
2326
struct _FlEngine {
2427
GObject parent_instance;
2528

@@ -44,6 +47,9 @@ struct _FlEngine {
4447
FlEngineUpdateSemanticsNodeHandler update_semantics_node_handler;
4548
gpointer update_semantics_node_handler_data;
4649
GDestroyNotify update_semantics_node_handler_destroy_notify;
50+
51+
// Stored baton for plumbing vsync callbacks.
52+
intptr_t vsync_baton;
4753
};
4854

4955
G_DEFINE_QUARK(fl_engine_error_quark, fl_engine_error)
@@ -214,6 +220,57 @@ static bool fl_engine_gl_make_resource_current(void* user_data) {
214220
return result;
215221
}
216222

223+
static void fl_engine_handle_frame_clock_update(GdkFrameClock* clk,
224+
void* user_data) {
225+
FlEngine* self = static_cast<FlEngine*>(user_data);
226+
if (self->vsync_baton != 0) {
227+
// Note: it's crucial to reset the vsync_baton before we call OnVsync, since
228+
// OnVsync (either synchronous or ran on another thread) might request
229+
// another vsync callback inside it.
230+
auto btn = self->vsync_baton;
231+
self->vsync_baton = 0;
232+
gint64 frame_time = gdk_frame_clock_get_frame_time(clk);
233+
gint64 refresh_interval;
234+
gint64 presentation_time;
235+
gdk_frame_clock_get_refresh_info(clk, frame_time, &refresh_interval,
236+
&presentation_time);
237+
if (presentation_time == 0) {
238+
// GDK could not predict next presentation due to lack of history.
239+
// A fallback is used.
240+
presentation_time = frame_time + refresh_interval;
241+
}
242+
self->embedder_api.OnVsync(self->engine, btn,
243+
frame_time * kMicrosecondsPerNanosecond,
244+
presentation_time * kMicrosecondsPerNanosecond);
245+
}
246+
}
247+
248+
static gboolean fl_engine_request_vsync(FlEngine* self) {
249+
GdkFrameClock* clk = gtk_widget_get_frame_clock(
250+
GTK_WIDGET(fl_renderer_get_view(self->renderer)));
251+
if (fl_renderer_is_blocking_main_thread(self->renderer)) {
252+
// Layout updates happens inside the "layout" phase of frame clock; if
253+
// blocking is in progress, then we never advance to the next "update" phase
254+
// where the vsync callback is usually called. Hence we just issue the
255+
// callback immediately if blocking is in progress.
256+
fl_engine_handle_frame_clock_update(clk, self);
257+
} else {
258+
gdk_frame_clock_request_phase(clk, GDK_FRAME_CLOCK_PHASE_UPDATE);
259+
}
260+
return false;
261+
}
262+
263+
static void fl_engine_vsync_callback(void* user_data, intptr_t btn) {
264+
FlEngine* self = static_cast<FlEngine*>(user_data);
265+
// Thread safety: only one of vsync_callback or handle_frame_clock_update can
266+
// execute at one time. This is because VSync callback can only be requested
267+
// upon the completion of the previous VSync callback.
268+
self->vsync_baton = btn;
269+
// Run frame clock operations on the main thread to synchronize the accesses.
270+
std::function<void()> delegate = [=] { fl_engine_request_vsync(self); };
271+
fl_task_runner_post_task(self->task_runner, std::move(delegate), 0);
272+
}
273+
217274
// Called by the engine to determine if it is on the GTK thread.
218275
static bool fl_engine_runs_task_on_current_thread(void* user_data) {
219276
FlEngine* self = static_cast<FlEngine*>(user_data);
@@ -225,8 +282,11 @@ static void fl_engine_post_task(FlutterTask task,
225282
uint64_t target_time_nanos,
226283
void* user_data) {
227284
FlEngine* self = static_cast<FlEngine*>(user_data);
285+
std::function<void()> delegate = [=] {
286+
self->embedder_api.RunTask(self->engine, &task);
287+
};
228288

229-
fl_task_runner_post_task(self->task_runner, task, target_time_nanos);
289+
fl_task_runner_post_task(self->task_runner, delegate, target_time_nanos);
230290
}
231291

232292
// Called when a platform message is received from the engine.
@@ -326,6 +386,7 @@ static void fl_engine_class_init(FlEngineClass* klass) {
326386

327387
static void fl_engine_init(FlEngine* self) {
328388
self->thread = g_thread_self();
389+
self->vsync_baton = 0;
329390

330391
self->embedder_api.struct_size = sizeof(FlutterEngineProcTable);
331392
FlutterEngineGetProcAddresses(&self->embedder_api);
@@ -401,6 +462,11 @@ gboolean fl_engine_start(FlEngine* self, GError** error) {
401462
dart_entrypoint_args != nullptr ? g_strv_length(dart_entrypoint_args) : 0;
402463
args.dart_entrypoint_argv =
403464
reinterpret_cast<const char* const*>(dart_entrypoint_args);
465+
args.vsync_callback = fl_engine_vsync_callback;
466+
GdkFrameClock* clk = gtk_widget_get_frame_clock(
467+
GTK_WIDGET(fl_renderer_get_view(self->renderer)));
468+
g_signal_connect(clk, "update",
469+
G_CALLBACK(fl_engine_handle_frame_clock_update), self);
404470

405471
FlutterCompositor compositor = {};
406472
compositor.struct_size = sizeof(FlutterCompositor);
@@ -683,8 +749,3 @@ FlTaskRunner* fl_engine_get_task_runner(FlEngine* self) {
683749
g_return_val_if_fail(FL_IS_ENGINE(self), nullptr);
684750
return self->task_runner;
685751
}
686-
687-
void fl_engine_execute_task(FlEngine* self, FlutterTask* task) {
688-
g_return_if_fail(FL_IS_ENGINE(self));
689-
self->embedder_api.RunTask(self->engine, task);
690-
}

shell/platform/linux/fl_engine_private.h

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -244,15 +244,6 @@ GBytes* fl_engine_send_platform_message_finish(FlEngine* engine,
244244
*/
245245
FlTaskRunner* fl_engine_get_task_runner(FlEngine* engine);
246246

247-
/**
248-
* fl_engine_execute_task:
249-
* @engine: an #FlEngine.
250-
* @task: a #FlutterTask to execute.
251-
*
252-
* Executes given Flutter task.
253-
*/
254-
void fl_engine_execute_task(FlEngine* engine, FlutterTask* task);
255-
256247
G_END_DECLS
257248

258249
#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_ENGINE_PRIVATE_H_

shell/platform/linux/fl_renderer.cc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,3 +179,10 @@ gboolean fl_renderer_present_layers(FlRenderer* self,
179179
return FL_RENDERER_GET_CLASS(self)->present_layers(self, layers,
180180
layers_count);
181181
}
182+
183+
gboolean fl_renderer_is_blocking_main_thread(FlRenderer* self) {
184+
FlRendererPrivate* priv = reinterpret_cast<FlRendererPrivate*>(
185+
fl_renderer_get_instance_private(self));
186+
187+
return priv->blocking_main_thread;
188+
}

shell/platform/linux/fl_renderer.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,15 @@ void fl_renderer_wait_for_frame(FlRenderer* renderer,
256256
int target_width,
257257
int target_height);
258258

259+
/**
260+
* fl_renderer_is_blocking_main_thread:
261+
* @renderer: an #FlRenderer.
262+
*
263+
* Returns %TRUE if we are currently blocking the main thread waiting for a
264+
* frame to arrive.
265+
*/
266+
gboolean fl_renderer_is_blocking_main_thread(FlRenderer* self);
267+
259268
G_END_DECLS
260269

261270
#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_RENDERER_H_

shell/platform/linux/fl_task_runner.cc

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ struct _FlTaskRunner {
2424
typedef struct _FlTaskRunnerTask {
2525
// absolute time of task (based on g_get_monotonic_time)
2626
gint64 task_time_micros;
27-
FlutterTask task;
27+
std::function<void()> delegate;
2828
} FlTaskRunnerTask;
2929

3030
G_DEFINE_TYPE(FlTaskRunner, fl_task_runner, G_TYPE_OBJECT)
@@ -54,7 +54,7 @@ static void fl_task_runner_process_expired_tasks_locked(FlTaskRunner* self) {
5454
l = expired_tasks;
5555
while (l != nullptr && self->engine) {
5656
FlTaskRunnerTask* task = static_cast<FlTaskRunnerTask*>(l->data);
57-
fl_engine_execute_task(self->engine, &task->task);
57+
task->delegate();
5858
l = l->next;
5959
}
6060

@@ -167,13 +167,13 @@ FlTaskRunner* fl_task_runner_new(FlEngine* engine) {
167167
}
168168

169169
void fl_task_runner_post_task(FlTaskRunner* self,
170-
FlutterTask task,
170+
std::function<void()> delegate,
171171
uint64_t target_time_nanos) {
172172
g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&self->mutex);
173173
(void)locker; // unused variable
174174

175175
FlTaskRunnerTask* runner_task = g_new0(FlTaskRunnerTask, 1);
176-
runner_task->task = task;
176+
runner_task->delegate = std::move(delegate);
177177
runner_task->task_time_micros =
178178
target_time_nanos / kMicrosecondsPerNanosecond;
179179

shell/platform/linux/fl_task_runner.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#define FLUTTER_SHELL_PLATFORM_LINUX_FL_TASK_RUNNER_H_
77

88
#include <glib-object.h>
9+
#include <functional>
910

1011
#include "flutter/shell/platform/embedder/embedder.h"
1112
#include "flutter/shell/platform/linux/public/flutter_linux/fl_engine.h"
@@ -27,14 +28,14 @@ FlTaskRunner* fl_task_runner_new(FlEngine* engine);
2728
/**
2829
* fl_task_runner_post_task:
2930
* @task_runner: an #FlTaskRunner.
30-
* @task: Flutter task being scheduled
31+
* @delegate: The callback to be scheduled
3132
* @target_time_nanos: absolute time in nanoseconds
3233
*
3334
* Posts a Flutter task to be executed on main thread. This function is thread
3435
* safe and may be called from any thread.
3536
*/
3637
void fl_task_runner_post_task(FlTaskRunner* task_runner,
37-
FlutterTask task,
38+
std::function<void()> delegate,
3839
uint64_t target_time_nanos);
3940

4041
/**

0 commit comments

Comments
 (0)