From cfccc5758345a473b2e294a72f2834b0d8389cc4 Mon Sep 17 00:00:00 2001 From: Tatsuyuki Ishi Date: Sun, 5 Sep 2021 11:31:48 +0900 Subject: [PATCH] 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. --- shell/platform/linux/fl_engine.cc | 73 ++++++++++++++++++++++-- shell/platform/linux/fl_engine_private.h | 9 --- shell/platform/linux/fl_renderer.cc | 7 +++ shell/platform/linux/fl_renderer.h | 9 +++ shell/platform/linux/fl_task_runner.cc | 8 +-- shell/platform/linux/fl_task_runner.h | 5 +- 6 files changed, 90 insertions(+), 21 deletions(-) diff --git a/shell/platform/linux/fl_engine.cc b/shell/platform/linux/fl_engine.cc index 116fd3f5c7e18..ea5bdd871c4d1 100644 --- a/shell/platform/linux/fl_engine.cc +++ b/shell/platform/linux/fl_engine.cc @@ -6,6 +6,7 @@ #include +#include #include #include "flutter/shell/platform/linux/fl_binary_messenger_private.h" @@ -20,6 +21,8 @@ // Unique number associated with platform tasks. static constexpr size_t kPlatformTaskRunnerIdentifier = 1; +static constexpr int kMicrosecondsPerNanosecond = 1000; + struct _FlEngine { GObject parent_instance; @@ -44,6 +47,9 @@ struct _FlEngine { FlEngineUpdateSemanticsNodeHandler update_semantics_node_handler; gpointer update_semantics_node_handler_data; GDestroyNotify update_semantics_node_handler_destroy_notify; + + // Stored baton for plumbing vsync callbacks. + intptr_t vsync_baton; }; 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) { return result; } +static void fl_engine_handle_frame_clock_update(GdkFrameClock* clk, + void* user_data) { + FlEngine* self = static_cast(user_data); + if (self->vsync_baton != 0) { + // Note: it's crucial to reset the vsync_baton before we call OnVsync, since + // OnVsync (either synchronous or ran on another thread) might request + // another vsync callback inside it. + auto btn = self->vsync_baton; + self->vsync_baton = 0; + gint64 frame_time = gdk_frame_clock_get_frame_time(clk); + gint64 refresh_interval; + gint64 presentation_time; + gdk_frame_clock_get_refresh_info(clk, frame_time, &refresh_interval, + &presentation_time); + if (presentation_time == 0) { + // GDK could not predict next presentation due to lack of history. + // A fallback is used. + presentation_time = frame_time + refresh_interval; + } + self->embedder_api.OnVsync(self->engine, btn, + frame_time * kMicrosecondsPerNanosecond, + presentation_time * kMicrosecondsPerNanosecond); + } +} + +static gboolean fl_engine_request_vsync(FlEngine* self) { + GdkFrameClock* clk = gtk_widget_get_frame_clock( + GTK_WIDGET(fl_renderer_get_view(self->renderer))); + if (fl_renderer_is_blocking_main_thread(self->renderer)) { + // Layout updates happens inside the "layout" phase of frame clock; if + // blocking is in progress, then we never advance to the next "update" phase + // where the vsync callback is usually called. Hence we just issue the + // callback immediately if blocking is in progress. + fl_engine_handle_frame_clock_update(clk, self); + } else { + gdk_frame_clock_request_phase(clk, GDK_FRAME_CLOCK_PHASE_UPDATE); + } + return false; +} + +static void fl_engine_vsync_callback(void* user_data, intptr_t btn) { + FlEngine* self = static_cast(user_data); + // Thread safety: only one of vsync_callback or handle_frame_clock_update can + // execute at one time. This is because VSync callback can only be requested + // upon the completion of the previous VSync callback. + self->vsync_baton = btn; + // Run frame clock operations on the main thread to synchronize the accesses. + std::function delegate = [=] { fl_engine_request_vsync(self); }; + fl_task_runner_post_task(self->task_runner, std::move(delegate), 0); +} + // Called by the engine to determine if it is on the GTK thread. static bool fl_engine_runs_task_on_current_thread(void* user_data) { FlEngine* self = static_cast(user_data); @@ -225,8 +282,11 @@ static void fl_engine_post_task(FlutterTask task, uint64_t target_time_nanos, void* user_data) { FlEngine* self = static_cast(user_data); + std::function delegate = [=] { + self->embedder_api.RunTask(self->engine, &task); + }; - fl_task_runner_post_task(self->task_runner, task, target_time_nanos); + fl_task_runner_post_task(self->task_runner, delegate, target_time_nanos); } // Called when a platform message is received from the engine. @@ -326,6 +386,7 @@ static void fl_engine_class_init(FlEngineClass* klass) { static void fl_engine_init(FlEngine* self) { self->thread = g_thread_self(); + self->vsync_baton = 0; self->embedder_api.struct_size = sizeof(FlutterEngineProcTable); FlutterEngineGetProcAddresses(&self->embedder_api); @@ -401,6 +462,11 @@ gboolean fl_engine_start(FlEngine* self, GError** error) { dart_entrypoint_args != nullptr ? g_strv_length(dart_entrypoint_args) : 0; args.dart_entrypoint_argv = reinterpret_cast(dart_entrypoint_args); + args.vsync_callback = fl_engine_vsync_callback; + GdkFrameClock* clk = gtk_widget_get_frame_clock( + GTK_WIDGET(fl_renderer_get_view(self->renderer))); + g_signal_connect(clk, "update", + G_CALLBACK(fl_engine_handle_frame_clock_update), self); FlutterCompositor compositor = {}; compositor.struct_size = sizeof(FlutterCompositor); @@ -683,8 +749,3 @@ FlTaskRunner* fl_engine_get_task_runner(FlEngine* self) { g_return_val_if_fail(FL_IS_ENGINE(self), nullptr); return self->task_runner; } - -void fl_engine_execute_task(FlEngine* self, FlutterTask* task) { - g_return_if_fail(FL_IS_ENGINE(self)); - self->embedder_api.RunTask(self->engine, task); -} diff --git a/shell/platform/linux/fl_engine_private.h b/shell/platform/linux/fl_engine_private.h index b5e0081eb0fbb..6f70cd15f3689 100644 --- a/shell/platform/linux/fl_engine_private.h +++ b/shell/platform/linux/fl_engine_private.h @@ -244,15 +244,6 @@ GBytes* fl_engine_send_platform_message_finish(FlEngine* engine, */ FlTaskRunner* fl_engine_get_task_runner(FlEngine* engine); -/** - * fl_engine_execute_task: - * @engine: an #FlEngine. - * @task: a #FlutterTask to execute. - * - * Executes given Flutter task. - */ -void fl_engine_execute_task(FlEngine* engine, FlutterTask* task); - G_END_DECLS #endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_ENGINE_PRIVATE_H_ diff --git a/shell/platform/linux/fl_renderer.cc b/shell/platform/linux/fl_renderer.cc index 76cfbfad22d93..30b6835edb5c6 100644 --- a/shell/platform/linux/fl_renderer.cc +++ b/shell/platform/linux/fl_renderer.cc @@ -179,3 +179,10 @@ gboolean fl_renderer_present_layers(FlRenderer* self, return FL_RENDERER_GET_CLASS(self)->present_layers(self, layers, layers_count); } + +gboolean fl_renderer_is_blocking_main_thread(FlRenderer* self) { + FlRendererPrivate* priv = reinterpret_cast( + fl_renderer_get_instance_private(self)); + + return priv->blocking_main_thread; +} diff --git a/shell/platform/linux/fl_renderer.h b/shell/platform/linux/fl_renderer.h index 545f7b8f584d7..e457782e2d8f8 100644 --- a/shell/platform/linux/fl_renderer.h +++ b/shell/platform/linux/fl_renderer.h @@ -256,6 +256,15 @@ void fl_renderer_wait_for_frame(FlRenderer* renderer, int target_width, int target_height); +/** + * fl_renderer_is_blocking_main_thread: + * @renderer: an #FlRenderer. + * + * Returns %TRUE if we are currently blocking the main thread waiting for a + * frame to arrive. + */ +gboolean fl_renderer_is_blocking_main_thread(FlRenderer* self); + G_END_DECLS #endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_RENDERER_H_ diff --git a/shell/platform/linux/fl_task_runner.cc b/shell/platform/linux/fl_task_runner.cc index ce9938915ccda..18d22a34f8c29 100644 --- a/shell/platform/linux/fl_task_runner.cc +++ b/shell/platform/linux/fl_task_runner.cc @@ -24,7 +24,7 @@ struct _FlTaskRunner { typedef struct _FlTaskRunnerTask { // absolute time of task (based on g_get_monotonic_time) gint64 task_time_micros; - FlutterTask task; + std::function delegate; } FlTaskRunnerTask; 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) { l = expired_tasks; while (l != nullptr && self->engine) { FlTaskRunnerTask* task = static_cast(l->data); - fl_engine_execute_task(self->engine, &task->task); + task->delegate(); l = l->next; } @@ -167,13 +167,13 @@ FlTaskRunner* fl_task_runner_new(FlEngine* engine) { } void fl_task_runner_post_task(FlTaskRunner* self, - FlutterTask task, + std::function delegate, uint64_t target_time_nanos) { g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&self->mutex); (void)locker; // unused variable FlTaskRunnerTask* runner_task = g_new0(FlTaskRunnerTask, 1); - runner_task->task = task; + runner_task->delegate = std::move(delegate); runner_task->task_time_micros = target_time_nanos / kMicrosecondsPerNanosecond; diff --git a/shell/platform/linux/fl_task_runner.h b/shell/platform/linux/fl_task_runner.h index ca4f84b3ee521..12ab31a5809b6 100644 --- a/shell/platform/linux/fl_task_runner.h +++ b/shell/platform/linux/fl_task_runner.h @@ -6,6 +6,7 @@ #define FLUTTER_SHELL_PLATFORM_LINUX_FL_TASK_RUNNER_H_ #include +#include #include "flutter/shell/platform/embedder/embedder.h" #include "flutter/shell/platform/linux/public/flutter_linux/fl_engine.h" @@ -27,14 +28,14 @@ FlTaskRunner* fl_task_runner_new(FlEngine* engine); /** * fl_task_runner_post_task: * @task_runner: an #FlTaskRunner. - * @task: Flutter task being scheduled + * @delegate: The callback to be scheduled * @target_time_nanos: absolute time in nanoseconds * * Posts a Flutter task to be executed on main thread. This function is thread * safe and may be called from any thread. */ void fl_task_runner_post_task(FlTaskRunner* task_runner, - FlutterTask task, + std::function delegate, uint64_t target_time_nanos); /**