diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index c1eba92abb184..2d4afaff7f585 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1480,6 +1480,8 @@ FILE: ../../../flutter/shell/platform/linux/fl_standard_method_codec.cc FILE: ../../../flutter/shell/platform/linux/fl_standard_method_codec_test.cc FILE: ../../../flutter/shell/platform/linux/fl_string_codec.cc FILE: ../../../flutter/shell/platform/linux/fl_string_codec_test.cc +FILE: ../../../flutter/shell/platform/linux/fl_task_runner.cc +FILE: ../../../flutter/shell/platform/linux/fl_task_runner.h FILE: ../../../flutter/shell/platform/linux/fl_text_input_plugin.cc FILE: ../../../flutter/shell/platform/linux/fl_text_input_plugin.h FILE: ../../../flutter/shell/platform/linux/fl_value.cc diff --git a/shell/platform/linux/BUILD.gn b/shell/platform/linux/BUILD.gn index 038909683bc18..b7ed3e4946d62 100644 --- a/shell/platform/linux/BUILD.gn +++ b/shell/platform/linux/BUILD.gn @@ -114,6 +114,8 @@ source_set("flutter_linux_sources") { "fl_standard_message_codec.cc", "fl_standard_method_codec.cc", "fl_string_codec.cc", + "fl_task_runner.cc", + "fl_task_runner.h", "fl_text_input_plugin.cc", "fl_value.cc", "fl_view.cc", diff --git a/shell/platform/linux/fl_engine.cc b/shell/platform/linux/fl_engine.cc index 2212b68ed6489..8028d21005a58 100644 --- a/shell/platform/linux/fl_engine.cc +++ b/shell/platform/linux/fl_engine.cc @@ -17,8 +17,6 @@ #include "flutter/shell/platform/linux/fl_settings_plugin.h" #include "flutter/shell/platform/linux/public/flutter_linux/fl_plugin_registry.h" -static constexpr int kMicrosecondsPerNanosecond = 1000; - // Unique number associated with platform tasks. static constexpr size_t kPlatformTaskRunnerIdentifier = 1; @@ -32,6 +30,7 @@ struct _FlEngine { FlRenderer* renderer; FlBinaryMessenger* binary_messenger; FlSettingsPlugin* settings_plugin; + FlTaskRunner* task_runner; FlutterEngineAOTData aot_data; FLUTTER_API_SYMBOL(FlutterEngine) engine; FlutterEngineProcTable embedder_api; @@ -59,13 +58,6 @@ G_DEFINE_TYPE_WITH_CODE( G_IMPLEMENT_INTERFACE(fl_plugin_registry_get_type(), fl_engine_plugin_registry_iface_init)) -// Subclass of GSource that integrates Flutter tasks into the GLib main loop. -typedef struct { - GSource parent; - FlEngine* engine; - FlutterTask task; -} FlutterSource; - // Parse a locale into its components. static void parse_locale(const gchar* locale, gchar** language, @@ -143,40 +135,6 @@ static void setup_locales(FlEngine* self) { } } -// Callback to run a Flutter task in the GLib main loop. -static gboolean flutter_source_dispatch(GSource* source, - GSourceFunc callback, - gpointer user_data) { - FlutterSource* fl_source = reinterpret_cast(source); - FlEngine* self = fl_source->engine; - - FlutterEngineResult result = - self->embedder_api.RunTask(self->engine, &fl_source->task); - if (result != kSuccess) { - g_warning("Failed to run Flutter task\n"); - } - - return G_SOURCE_REMOVE; -} - -// Called when the engine is disposed. -static void engine_weak_notify_cb(gpointer user_data, - GObject* where_the_object_was) { - FlutterSource* source = reinterpret_cast(user_data); - source->engine = nullptr; - g_source_destroy(reinterpret_cast(source)); -} - -// Called when a flutter source completes. -static void flutter_source_finalize(GSource* source) { - FlutterSource* fl_source = reinterpret_cast(source); - if (fl_source->engine != nullptr) { - g_object_weak_unref(G_OBJECT(fl_source->engine), engine_weak_notify_cb, - fl_source); - fl_source->engine = nullptr; - } -} - // Called when engine needs a backing store for a specific #FlutterLayer. static bool compositor_create_backing_store_callback( const FlutterBackingStoreConfig* config, @@ -204,16 +162,6 @@ static bool compositor_present_layers_callback(const FlutterLayer** layers, layers_count); } -// Table of functions for Flutter GLib main loop integration. -static GSourceFuncs flutter_source_funcs = { - nullptr, // prepare - nullptr, // check - flutter_source_dispatch, // dispatch - flutter_source_finalize, // finalize - nullptr, - nullptr // Internal usage -}; - // Flutter engine rendering callbacks. static void* fl_engine_gl_proc_resolver(void* user_data, const char* name) { @@ -278,15 +226,7 @@ static void fl_engine_post_task(FlutterTask task, void* user_data) { FlEngine* self = static_cast(user_data); - g_autoptr(GSource) source = - g_source_new(&flutter_source_funcs, sizeof(FlutterSource)); - FlutterSource* fl_source = reinterpret_cast(source); - fl_source->engine = self; - g_object_weak_ref(G_OBJECT(self), engine_weak_notify_cb, fl_source); - fl_source->task = task; - g_source_set_ready_time(source, - target_time_nanos / kMicrosecondsPerNanosecond); - g_source_attach(source, nullptr); + fl_task_runner_post_task(self->task_runner, task, target_time_nanos); } // Called when a platform message is received from the engine. @@ -361,6 +301,7 @@ static void fl_engine_dispose(GObject* object) { g_clear_object(&self->renderer); g_clear_object(&self->binary_messenger); g_clear_object(&self->settings_plugin); + g_clear_object(&self->task_runner); if (self->platform_message_handler_destroy_notify) { self->platform_message_handler_destroy_notify( @@ -410,6 +351,8 @@ G_MODULE_EXPORT FlEngine* fl_engine_new_headless(FlDartProject* project) { gboolean fl_engine_start(FlEngine* self, GError** error) { g_return_val_if_fail(FL_IS_ENGINE(self), FALSE); + self->task_runner = fl_task_runner_new(self); + FlutterRendererConfig config = {}; config.type = kOpenGL; config.open_gl.struct_size = sizeof(FlutterOpenGLRendererConfig); @@ -722,3 +665,13 @@ G_MODULE_EXPORT FlBinaryMessenger* fl_engine_get_binary_messenger( g_return_val_if_fail(FL_IS_ENGINE(self), nullptr); return self->binary_messenger; } + +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 23f186921fca6..95e6daa19f294 100644 --- a/shell/platform/linux/fl_engine_private.h +++ b/shell/platform/linux/fl_engine_private.h @@ -9,6 +9,7 @@ #include "flutter/shell/platform/embedder/embedder.h" #include "flutter/shell/platform/linux/fl_renderer.h" +#include "flutter/shell/platform/linux/fl_task_runner.h" #include "flutter/shell/platform/linux/public/flutter_linux/fl_dart_project.h" #include "flutter/shell/platform/linux/public/flutter_linux/fl_engine.h" @@ -226,6 +227,24 @@ GBytes* fl_engine_send_platform_message_finish(FlEngine* engine, GAsyncResult* result, GError** error); +/** + * fl_engine_get_task_runner: + * @engine: an #FlEngine. + * @result: a #FlTaskRunner. + * + * Returns: task runner responsible for scheduling Flutter tasks. + */ +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 1b3e44399b32a..76cfbfad22d93 100644 --- a/shell/platform/linux/fl_renderer.cc +++ b/shell/platform/linux/fl_renderer.cc @@ -9,19 +9,50 @@ #include "flutter/shell/platform/embedder/embedder.h" #include "flutter/shell/platform/linux/fl_backing_store_provider.h" +#include "flutter/shell/platform/linux/fl_engine_private.h" G_DEFINE_QUARK(fl_renderer_error_quark, fl_renderer_error) typedef struct { FlView* view; + // target dimension for resizing + int target_width; + int target_height; + + // whether the renderer waits for frame render + bool blocking_main_thread; + + // true if frame was completed; resizing is not synchronized until first frame + // was rendered + bool had_first_frame; + GdkGLContext* main_context; GdkGLContext* resource_context; } FlRendererPrivate; G_DEFINE_TYPE_WITH_PRIVATE(FlRenderer, fl_renderer, G_TYPE_OBJECT) -static void fl_renderer_class_init(FlRendererClass* klass) {} +static void fl_renderer_unblock_main_thread(FlRenderer* self) { + FlRendererPrivate* priv = reinterpret_cast( + fl_renderer_get_instance_private(self)); + if (priv->blocking_main_thread) { + priv->blocking_main_thread = false; + + FlTaskRunner* runner = + fl_engine_get_task_runner(fl_view_get_engine(priv->view)); + fl_task_runner_release_main_thread(runner); + } +} + +static void fl_renderer_dispose(GObject* self) { + fl_renderer_unblock_main_thread(FL_RENDERER(self)); + G_OBJECT_CLASS(fl_renderer_parent_class)->dispose(self); +} + +static void fl_renderer_class_init(FlRendererClass* klass) { + G_OBJECT_CLASS(klass)->dispose = fl_renderer_dispose; +} static void fl_renderer_init(FlRenderer* self) {} @@ -109,9 +140,42 @@ gboolean fl_renderer_collect_backing_store( backing_store); } +void fl_renderer_wait_for_frame(FlRenderer* self, + int target_width, + int target_height) { + FlRendererPrivate* priv = reinterpret_cast( + fl_renderer_get_instance_private(self)); + + priv->target_width = target_width; + priv->target_height = target_height; + + if (priv->had_first_frame && !priv->blocking_main_thread) { + priv->blocking_main_thread = true; + FlTaskRunner* runner = + fl_engine_get_task_runner(fl_view_get_engine(priv->view)); + fl_task_runner_block_main_thread(runner); + } +} + gboolean fl_renderer_present_layers(FlRenderer* self, const FlutterLayer** layers, size_t layers_count) { + FlRendererPrivate* priv = reinterpret_cast( + fl_renderer_get_instance_private(self)); + + // ignore incoming frame with wrong dimensions in trivial case with just one + // layer + if (priv->blocking_main_thread && layers_count == 1 && + layers[0]->offset.x == 0 && layers[0]->offset.y == 0 && + (layers[0]->size.width != priv->target_width || + layers[0]->size.height != priv->target_height)) { + return true; + } + + priv->had_first_frame = true; + + fl_renderer_unblock_main_thread(self); + return FL_RENDERER_GET_CLASS(self)->present_layers(self, layers, layers_count); } diff --git a/shell/platform/linux/fl_renderer.h b/shell/platform/linux/fl_renderer.h index 17cacd4c41480..545f7b8f584d7 100644 --- a/shell/platform/linux/fl_renderer.h +++ b/shell/platform/linux/fl_renderer.h @@ -242,6 +242,20 @@ gboolean fl_renderer_present_layers(FlRenderer* renderer, const FlutterLayer** layers, size_t layers_count); +/** + * fl_renderer_wait_for_frame: + * @renderer: an #FlRenderer. + * @target_width: width of frame being waited for + * @target_height: height of frame being waited for + * + * Holds the thread until frame with requested dimensions is presented. + * While waiting for frame Flutter platform and raster tasks are being + * processed. + */ +void fl_renderer_wait_for_frame(FlRenderer* renderer, + int target_width, + int target_height); + 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 new file mode 100644 index 0000000000000..ce9938915ccda --- /dev/null +++ b/shell/platform/linux/fl_task_runner.cc @@ -0,0 +1,213 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/platform/linux/fl_task_runner.h" +#include "flutter/shell/platform/linux/fl_engine_private.h" + +static constexpr int kMicrosecondsPerNanosecond = 1000; +static constexpr int kMillisecondsPerMicrosecond = 1000; + +struct _FlTaskRunner { + GObject parent_instance; + + FlEngine* engine; + + GMutex mutex; + GCond cond; + + guint timeout_source_id; + GList /**/* pending_tasks; + gboolean blocking_main_thread; +}; + +typedef struct _FlTaskRunnerTask { + // absolute time of task (based on g_get_monotonic_time) + gint64 task_time_micros; + FlutterTask task; +} FlTaskRunnerTask; + +G_DEFINE_TYPE(FlTaskRunner, fl_task_runner, G_TYPE_OBJECT) + +// Removes expired tasks from the task queue and executes them. +// The execution is performed with mutex unlocked. +static void fl_task_runner_process_expired_tasks_locked(FlTaskRunner* self) { + GList* expired_tasks = nullptr; + + gint64 current_time = g_get_monotonic_time(); + + GList* l = self->pending_tasks; + while (l != nullptr) { + FlTaskRunnerTask* task = static_cast(l->data); + if (task->task_time_micros <= current_time) { + GList* link = l; + l = l->next; + self->pending_tasks = g_list_remove_link(self->pending_tasks, link); + expired_tasks = g_list_concat(expired_tasks, link); + } else { + l = l->next; + } + } + + g_mutex_unlock(&self->mutex); + + l = expired_tasks; + while (l != nullptr && self->engine) { + FlTaskRunnerTask* task = static_cast(l->data); + fl_engine_execute_task(self->engine, &task->task); + l = l->next; + } + + g_list_free_full(expired_tasks, g_free); + + g_mutex_lock(&self->mutex); +} + +static void fl_task_runner_tasks_did_change_locked(FlTaskRunner* self); + +// Invoked from a timeout source. Removes and executes expired tasks +// and reschedules timeout if needed. +static gboolean fl_task_runner_on_expired_timeout(gpointer data) { + FlTaskRunner* self = FL_TASK_RUNNER(data); + + g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&self->mutex); + (void)locker; // unused variable + + g_object_ref(self); + + self->timeout_source_id = 0; + fl_task_runner_process_expired_tasks_locked(self); + + // reschedule timeout + fl_task_runner_tasks_did_change_locked(self); + + g_object_unref(self); + + return FALSE; +} + +// Returns the absolute time of next expired task (in microseconds, based on +// g_get_monotonic_time). If no task is scheduled returns G_MAXINT64. +static gint64 fl_task_runner_next_task_expiration_time_locked( + FlTaskRunner* self) { + gint64 min_time = G_MAXINT64; + GList* l = self->pending_tasks; + while (l != nullptr) { + FlTaskRunnerTask* task = static_cast(l->data); + min_time = MIN(min_time, task->task_time_micros); + l = l->next; + } + return min_time; +} + +static void fl_task_runner_tasks_did_change_locked(FlTaskRunner* self) { + if (self->blocking_main_thread) { + // Wake up blocked thread + g_cond_signal(&self->cond); + } else { + // Reschedule timeout + if (self->timeout_source_id != 0) { + g_source_remove(self->timeout_source_id); + self->timeout_source_id = 0; + } + gint64 min_time = fl_task_runner_next_task_expiration_time_locked(self); + if (min_time != G_MAXINT64) { + gint64 remaining = MAX(min_time - g_get_monotonic_time(), 0); + self->timeout_source_id = + g_timeout_add(remaining / kMillisecondsPerMicrosecond + 1, + fl_task_runner_on_expired_timeout, self); + } + } +} + +static void engine_weak_notify_cb(gpointer user_data, + GObject* where_the_object_was) { + FlTaskRunner* self = FL_TASK_RUNNER(user_data); + self->engine = nullptr; +} + +void fl_task_runner_dispose(GObject* object) { + FlTaskRunner* self = FL_TASK_RUNNER(object); + + // this should never happen because the task runner is retained while blocking + // main thread + g_assert(!self->blocking_main_thread); + + if (self->engine != nullptr) { + g_object_weak_unref(G_OBJECT(self->engine), engine_weak_notify_cb, self); + self->engine = nullptr; + } + + g_mutex_clear(&self->mutex); + g_cond_clear(&self->cond); + + g_list_free_full(self->pending_tasks, g_free); + if (self->timeout_source_id != 0) { + g_source_remove(self->timeout_source_id); + } + + G_OBJECT_CLASS(fl_task_runner_parent_class)->dispose(object); +} + +static void fl_task_runner_class_init(FlTaskRunnerClass* klass) { + G_OBJECT_CLASS(klass)->dispose = fl_task_runner_dispose; +} + +static void fl_task_runner_init(FlTaskRunner* self) { + g_mutex_init(&self->mutex); + g_cond_init(&self->cond); +} + +FlTaskRunner* fl_task_runner_new(FlEngine* engine) { + FlTaskRunner* res = + FL_TASK_RUNNER(g_object_new(fl_task_runner_get_type(), nullptr)); + res->engine = engine; + g_object_weak_ref(G_OBJECT(engine), engine_weak_notify_cb, res); + return res; +} + +void fl_task_runner_post_task(FlTaskRunner* self, + FlutterTask task, + 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->task_time_micros = + target_time_nanos / kMicrosecondsPerNanosecond; + + self->pending_tasks = g_list_append(self->pending_tasks, runner_task); + fl_task_runner_tasks_did_change_locked(self); +} + +void fl_task_runner_block_main_thread(FlTaskRunner* self) { + g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&self->mutex); + (void)locker; // unused variable + + g_return_if_fail(self->blocking_main_thread == FALSE); + + g_object_ref(self); + + self->blocking_main_thread = true; + while (self->blocking_main_thread) { + g_cond_wait_until(&self->cond, &self->mutex, + fl_task_runner_next_task_expiration_time_locked(self)); + fl_task_runner_process_expired_tasks_locked(self); + } + + // Tasks might have changed in the meanwhile, reschedule timeout + fl_task_runner_tasks_did_change_locked(self); + + g_object_unref(self); +} + +void fl_task_runner_release_main_thread(FlTaskRunner* self) { + g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&self->mutex); + (void)locker; // unused variable + + g_return_if_fail(self->blocking_main_thread == TRUE); + + self->blocking_main_thread = FALSE; + g_cond_signal(&self->cond); +} diff --git a/shell/platform/linux/fl_task_runner.h b/shell/platform/linux/fl_task_runner.h new file mode 100644 index 0000000000000..ca4f84b3ee521 --- /dev/null +++ b/shell/platform/linux/fl_task_runner.h @@ -0,0 +1,62 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_TASK_RUNNER_H_ +#define FLUTTER_SHELL_PLATFORM_LINUX_FL_TASK_RUNNER_H_ + +#include + +#include "flutter/shell/platform/embedder/embedder.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_engine.h" + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE(FlTaskRunner, fl_task_runner, FL, TASK_RUNNER, GObject); + +/** + * fl_task_runner_new: + * @engine: the #FlEngine owning the task runner. + * + * Creates new task runner instance. + * + * Returns: an #FlTaskRunner. + */ +FlTaskRunner* fl_task_runner_new(FlEngine* engine); + +/** + * fl_task_runner_post_task: + * @task_runner: an #FlTaskRunner. + * @task: Flutter task being 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, + uint64_t target_time_nanos); + +/** + * fl_task_runner_block_main_thread: + * @task_runner: an #FlTaskRunner. + * + * Blocks main thread until fl_task_runner_release_main_thread is called. + * While main thread is blocked tasks posted to #FlTaskRunner are executed as + * usual. + * Must be invoked on main thread. + */ +void fl_task_runner_block_main_thread(FlTaskRunner* task_runner); + +/** + * fl_task_runner_release_main_thread: + * @task_runner: an #FlTaskRunner. + * + * Unblocks main thread. This will resume normal processing of main loop. + * Can be invoked from any thread. + */ +void fl_task_runner_release_main_thread(FlTaskRunner* self); + +G_END_DECLS + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_TASK_RUNNER_H_ diff --git a/shell/platform/linux/fl_view.cc b/shell/platform/linux/fl_view.cc index 4287f60b6c4be..b14d3632a4c44 100644 --- a/shell/platform/linux/fl_view.cc +++ b/shell/platform/linux/fl_view.cc @@ -137,6 +137,9 @@ static void fl_view_geometry_changed(FlView* self) { fl_engine_send_window_metrics_event( self->engine, allocation.width * scale_factor, allocation.height * scale_factor, scale_factor); + + fl_renderer_wait_for_frame(self->renderer, allocation.width * scale_factor, + allocation.height * scale_factor); } // Implements FlPluginRegistry::get_registrar_for_plugin.