From b2014c71942cf2f9e12ef933693e22491ac3d83a Mon Sep 17 00:00:00 2001 From: Matej Date: Tue, 4 Feb 2025 13:19:07 +0100 Subject: [PATCH 01/11] [Linux] Move rendering to raster thread --- .../flutter/shell/platform/linux/fl_engine.cc | 3 +- .../shell/platform/linux/fl_renderer.cc | 120 +++++++++++++++++- .../shell/platform/linux/fl_renderer.h | 8 ++ .../shell/platform/linux/fl_task_runner.cc | 35 ++++- .../shell/platform/linux/fl_task_runner.h | 20 ++- .../flutter/shell/platform/linux/fl_view.cc | 4 + .../platform/linux/testing/mock_renderer.cc | 11 ++ 7 files changed, 186 insertions(+), 15 deletions(-) diff --git a/engine/src/flutter/shell/platform/linux/fl_engine.cc b/engine/src/flutter/shell/platform/linux/fl_engine.cc index 38edff1af9a44..108539afd9629 100644 --- a/engine/src/flutter/shell/platform/linux/fl_engine.cc +++ b/engine/src/flutter/shell/platform/linux/fl_engine.cc @@ -356,7 +356,7 @@ static void fl_engine_post_task(FlutterTask task, void* user_data) { FlEngine* self = static_cast(user_data); - fl_task_runner_post_task(self->task_runner, task, target_time_nanos); + fl_task_runner_post_flutter_task(self->task_runner, task, target_time_nanos); } // Called when a platform message is received from the engine. @@ -630,7 +630,6 @@ gboolean fl_engine_start(FlEngine* self, GError** error) { FlutterCustomTaskRunners custom_task_runners = {}; custom_task_runners.struct_size = sizeof(FlutterCustomTaskRunners); custom_task_runners.platform_task_runner = &platform_task_runner; - custom_task_runners.render_task_runner = &platform_task_runner; g_autoptr(GPtrArray) command_line_args = g_ptr_array_new_with_free_func(g_free); diff --git a/engine/src/flutter/shell/platform/linux/fl_renderer.cc b/engine/src/flutter/shell/platform/linux/fl_renderer.cc index 2d5d918d0d293..9d508b9addee1 100644 --- a/engine/src/flutter/shell/platform/linux/fl_renderer.cc +++ b/engine/src/flutter/shell/platform/linux/fl_renderer.cc @@ -73,6 +73,11 @@ typedef struct { // Framebuffers to render keyed by view ID. GHashTable* framebuffers_by_view_id; + + // Mutex and condition used when block the raster thread until a task + // is completed on platform thread. + GMutex mutex; + GCond cond; } FlRendererPrivate; G_DEFINE_TYPE_WITH_PRIVATE(FlRenderer, fl_renderer, G_TYPE_OBJECT) @@ -313,10 +318,32 @@ static void fl_renderer_dispose(GObject* object) { g_clear_pointer(&priv->framebuffers_by_view_id, g_hash_table_unref); G_OBJECT_CLASS(fl_renderer_parent_class)->dispose(object); + + g_mutex_clear(&priv->mutex); + g_cond_clear(&priv->cond); +} + +static gboolean fl_renderer_schedule_on_main_thread( + FlRenderer* self, + void (*callback)(gpointer data), + gpointer callback_data) { + FlRendererPrivate* priv = reinterpret_cast( + fl_renderer_get_instance_private(self)); + g_autoptr(FlEngine) engine = FL_ENGINE(g_weak_ref_get(&priv->engine)); + + if (engine == nullptr) { + return FALSE; + } + + FlTaskRunner* task_runner = fl_engine_get_task_runner(engine); + fl_task_runner_post_callback(task_runner, callback, callback_data); + return TRUE; } static void fl_renderer_class_init(FlRendererClass* klass) { G_OBJECT_CLASS(klass)->dispose = fl_renderer_dispose; + FL_RENDERER_CLASS(klass)->schedule_on_main_thread = + fl_renderer_schedule_on_main_thread; } static void fl_renderer_init(FlRenderer* self) { @@ -327,6 +354,8 @@ static void fl_renderer_init(FlRenderer* self) { priv->framebuffers_by_view_id = g_hash_table_new_full( g_direct_hash, g_direct_equal, nullptr, reinterpret_cast(g_ptr_array_unref)); + g_mutex_init(&priv->mutex); + g_cond_init(&priv->cond); } void fl_renderer_set_engine(FlRenderer* self, FlEngine* engine) { @@ -388,6 +417,53 @@ guint32 fl_renderer_get_fbo(FlRenderer* self) { return 0; } +struct _FlRendererCallBlockingTrampolineData { + FlRenderer* renderer; + gboolean* finished; + void (*callback)(gpointer data); + gpointer data; +}; + +static void fl_renderer_call_blocking_trampoline(gpointer user_data) { + auto* data = static_cast<_FlRendererCallBlockingTrampolineData*>(user_data); + data->callback(data->data); + FlRendererPrivate* priv = reinterpret_cast( + fl_renderer_get_instance_private(data->renderer)); + g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&priv->mutex); + *data->finished = TRUE; + g_cond_signal(&priv->cond); +} + +// Executes the given function on the main thread and blocks until it completes. +static void fl_renderer_call_blocking(FlRenderer* self, + void (*callback)(gpointer data), + gpointer callback_data) { + gboolean finished = FALSE; + _FlRendererCallBlockingTrampolineData trampoline_data = { + self, + &finished, + callback, + callback_data, + }; + + gboolean scheduled = FL_RENDERER_GET_CLASS(self)->schedule_on_main_thread( + self, fl_renderer_call_blocking_trampoline, &trampoline_data); + + // Don't block if the task was not sucessfully scheduled. + if (!scheduled) { + return; + } + + // Wait for the task to complete. + FlRendererPrivate* priv = reinterpret_cast( + fl_renderer_get_instance_private(self)); + + g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&priv->mutex); + while (!finished) { + g_cond_wait(&priv->cond, &priv->mutex); + } +} + gboolean fl_renderer_create_backing_store( FlRenderer* self, const FlutterBackingStoreConfig* config, @@ -450,10 +526,11 @@ void fl_renderer_wait_for_frame(FlRenderer* self, } } -gboolean fl_renderer_present_layers(FlRenderer* self, - FlutterViewId view_id, - const FlutterLayer** layers, - size_t layers_count) { +static gboolean fl_renderer_present_layers_on_main_thread( + FlRenderer* self, + FlutterViewId view_id, + const FlutterLayer** layers, + size_t layers_count) { FlRendererPrivate* priv = reinterpret_cast( fl_renderer_get_instance_private(self)); @@ -564,6 +641,41 @@ gboolean fl_renderer_present_layers(FlRenderer* self, return TRUE; } +struct _FlRendererPresentLayersData { + FlRenderer* self; + FlutterViewId view_id; + const FlutterLayer** layers; + size_t layers_count; + gboolean* res; +}; + +static void fl_renderer_present_layers_trampoline(gpointer trampoline_data) { + _FlRendererPresentLayersData* data = + reinterpret_cast<_FlRendererPresentLayersData*>(trampoline_data); + fl_renderer_make_current(data->self); + *data->res = fl_renderer_present_layers_on_main_thread( + data->self, data->view_id, data->layers, data->layers_count); + fl_renderer_clear_current(data->self); +} + +gboolean fl_renderer_present_layers(FlRenderer* self, + FlutterViewId view_id, + const FlutterLayer** layers, + size_t layers_count) { + gboolean res = FALSE; + _FlRendererPresentLayersData data = { + .self = self, + .view_id = view_id, + .layers = layers, + .layers_count = layers_count, + .res = &res, + }; + fl_renderer_clear_current(self); + fl_renderer_call_blocking(self, fl_renderer_present_layers_trampoline, &data); + fl_renderer_make_current(self); + return res; +} + void fl_renderer_setup(FlRenderer* self) { FlRendererPrivate* priv = reinterpret_cast( fl_renderer_get_instance_private(self)); diff --git a/engine/src/flutter/shell/platform/linux/fl_renderer.h b/engine/src/flutter/shell/platform/linux/fl_renderer.h index b4b5216c70f80..ca31785feca79 100644 --- a/engine/src/flutter/shell/platform/linux/fl_renderer.h +++ b/engine/src/flutter/shell/platform/linux/fl_renderer.h @@ -55,6 +55,14 @@ struct _FlRendererClass { * @renderer: an #FlRenderer. */ void (*clear_current)(FlRenderer* renderer); + + /** + * Virtual method responsible for scheduling the callback on the main thread. + * Overriden in the mock renderer. + */ + gboolean (*schedule_on_main_thread)(FlRenderer* renderer, + void (*callback)(gpointer data), + gpointer data); }; /** diff --git a/engine/src/flutter/shell/platform/linux/fl_task_runner.cc b/engine/src/flutter/shell/platform/linux/fl_task_runner.cc index 147550c504d3f..d09b96ca9b7ae 100644 --- a/engine/src/flutter/shell/platform/linux/fl_task_runner.cc +++ b/engine/src/flutter/shell/platform/linux/fl_task_runner.cc @@ -22,9 +22,16 @@ struct _FlTaskRunner { }; typedef struct _FlTaskRunnerTask { - // absolute time of task (based on g_get_monotonic_time) + // absolute time of task (based on g_get_monotonic_time). gint64 task_time_micros; + + // flutter task to execute if schedule through + // fl_task_runner_post_flutter_task. FlutterTask task; + + // callback to execute if scheduled through fl_task_runner_post_callback. + void (*callback)(gpointer data); + gpointer callback_data; } FlTaskRunnerTask; G_DEFINE_TYPE(FlTaskRunner, fl_task_runner, G_TYPE_OBJECT) @@ -56,7 +63,11 @@ static void fl_task_runner_process_expired_tasks_locked(FlTaskRunner* self) { l = expired_tasks; while (l != nullptr) { FlTaskRunnerTask* task = static_cast(l->data); - fl_engine_execute_task(engine, &task->task); + if (task->callback != nullptr) { + task->callback(task->callback_data); + } else { + fl_engine_execute_task(engine, &task->task); + } l = l->next; } } @@ -158,9 +169,9 @@ FlTaskRunner* fl_task_runner_new(FlEngine* engine) { return self; } -void fl_task_runner_post_task(FlTaskRunner* self, - FlutterTask task, - uint64_t target_time_nanos) { +void fl_task_runner_post_flutter_task(FlTaskRunner* self, + FlutterTask task, + uint64_t target_time_nanos) { g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&self->mutex); (void)locker; // unused variable @@ -173,6 +184,20 @@ void fl_task_runner_post_task(FlTaskRunner* self, fl_task_runner_tasks_did_change_locked(self); } +void fl_task_runner_post_callback(FlTaskRunner* self, + void (*callback)(gpointer data), + gpointer data) { + g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&self->mutex); + (void)locker; // unused variable + + FlTaskRunnerTask* runner_task = g_new0(FlTaskRunnerTask, 1); + runner_task->callback = callback; + runner_task->callback_data = data; + + 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 diff --git a/engine/src/flutter/shell/platform/linux/fl_task_runner.h b/engine/src/flutter/shell/platform/linux/fl_task_runner.h index ca4f84b3ee521..7e1383ce8d605 100644 --- a/engine/src/flutter/shell/platform/linux/fl_task_runner.h +++ b/engine/src/flutter/shell/platform/linux/fl_task_runner.h @@ -25,7 +25,7 @@ G_DECLARE_FINAL_TYPE(FlTaskRunner, fl_task_runner, FL, TASK_RUNNER, GObject); FlTaskRunner* fl_task_runner_new(FlEngine* engine); /** - * fl_task_runner_post_task: + * fl_task_runner_post_flutter_task: * @task_runner: an #FlTaskRunner. * @task: Flutter task being scheduled * @target_time_nanos: absolute time in nanoseconds @@ -33,9 +33,21 @@ FlTaskRunner* fl_task_runner_new(FlEngine* engine); * 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); +void fl_task_runner_post_flutter_task(FlTaskRunner* task_runner, + FlutterTask task, + uint64_t target_time_nanos); + +/** + * fl_task_runner_post_callback: + * @task_runner: an #FlTaskRunner. + * @callback: callback to be scheduled + * @data: data to be passed to the callback + * + * Schedules arbitrary callback to be executed on main thread. + */ +void fl_task_runner_post_callback(FlTaskRunner* task_runner, + void (*callback)(gpointer data), + gpointer data); /** * fl_task_runner_block_main_thread: diff --git a/engine/src/flutter/shell/platform/linux/fl_view.cc b/engine/src/flutter/shell/platform/linux/fl_view.cc index 1299a086f6ce6..964e2aeb41728 100644 --- a/engine/src/flutter/shell/platform/linux/fl_view.cc +++ b/engine/src/flutter/shell/platform/linux/fl_view.cc @@ -476,6 +476,10 @@ static void realize_cb(FlView* self) { fl_renderer_add_renderable(FL_RENDERER(self->renderer), self->view_id, FL_RENDERABLE(self)); + // Flutter engine will need to make the context current from raster thread + // during initialization. + fl_renderer_clear_current(FL_RENDERER(self->renderer)); + if (!fl_engine_start(self->engine, &error)) { g_warning("Failed to start Flutter engine: %s", error->message); return; diff --git a/engine/src/flutter/shell/platform/linux/testing/mock_renderer.cc b/engine/src/flutter/shell/platform/linux/testing/mock_renderer.cc index 5ae2926efa53d..f40a94b58e76e 100644 --- a/engine/src/flutter/shell/platform/linux/testing/mock_renderer.cc +++ b/engine/src/flutter/shell/platform/linux/testing/mock_renderer.cc @@ -29,6 +29,15 @@ static void fl_mock_renderer_make_current(FlRenderer* renderer) {} // Implements FlRenderer::make_resource_current. static void fl_mock_renderer_make_resource_current(FlRenderer* renderer) {} +// Implements FlRenderer::schedule_on_main_thread. +static gboolean fl_mock_renderer_schedule_on_main_thread( + FlRenderer* renderer, + void (*callback)(gpointer data), + gpointer data) { + callback(data); + return TRUE; +} + // Implements FlRenderer::clear_current. static void fl_mock_renderer_clear_current(FlRenderer* renderer) {} @@ -37,6 +46,8 @@ static void fl_mock_renderer_class_init(FlMockRendererClass* klass) { FL_RENDERER_CLASS(klass)->make_resource_current = fl_mock_renderer_make_resource_current; FL_RENDERER_CLASS(klass)->clear_current = fl_mock_renderer_clear_current; + FL_RENDERER_CLASS(klass)->schedule_on_main_thread = + fl_mock_renderer_schedule_on_main_thread; } static void fl_mock_renderer_init(FlMockRenderer* self) {} From 5394d0025d1d55af7dbc2d447ff10690a5a96a2a Mon Sep 17 00:00:00 2001 From: Matej Date: Tue, 11 Feb 2025 23:24:06 +0100 Subject: [PATCH 02/11] simplify --- .../shell/platform/linux/fl_renderer.cc | 115 +++++++----------- .../shell/platform/linux/fl_renderer.h | 8 -- .../platform/linux/testing/mock_renderer.cc | 9 -- 3 files changed, 41 insertions(+), 91 deletions(-) diff --git a/engine/src/flutter/shell/platform/linux/fl_renderer.cc b/engine/src/flutter/shell/platform/linux/fl_renderer.cc index 9d508b9addee1..1d8e0b3b4b0a1 100644 --- a/engine/src/flutter/shell/platform/linux/fl_renderer.cc +++ b/engine/src/flutter/shell/platform/linux/fl_renderer.cc @@ -323,27 +323,8 @@ static void fl_renderer_dispose(GObject* object) { g_cond_clear(&priv->cond); } -static gboolean fl_renderer_schedule_on_main_thread( - FlRenderer* self, - void (*callback)(gpointer data), - gpointer callback_data) { - FlRendererPrivate* priv = reinterpret_cast( - fl_renderer_get_instance_private(self)); - g_autoptr(FlEngine) engine = FL_ENGINE(g_weak_ref_get(&priv->engine)); - - if (engine == nullptr) { - return FALSE; - } - - FlTaskRunner* task_runner = fl_engine_get_task_runner(engine); - fl_task_runner_post_callback(task_runner, callback, callback_data); - return TRUE; -} - static void fl_renderer_class_init(FlRendererClass* klass) { G_OBJECT_CLASS(klass)->dispose = fl_renderer_dispose; - FL_RENDERER_CLASS(klass)->schedule_on_main_thread = - fl_renderer_schedule_on_main_thread; } static void fl_renderer_init(FlRenderer* self) { @@ -417,53 +398,6 @@ guint32 fl_renderer_get_fbo(FlRenderer* self) { return 0; } -struct _FlRendererCallBlockingTrampolineData { - FlRenderer* renderer; - gboolean* finished; - void (*callback)(gpointer data); - gpointer data; -}; - -static void fl_renderer_call_blocking_trampoline(gpointer user_data) { - auto* data = static_cast<_FlRendererCallBlockingTrampolineData*>(user_data); - data->callback(data->data); - FlRendererPrivate* priv = reinterpret_cast( - fl_renderer_get_instance_private(data->renderer)); - g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&priv->mutex); - *data->finished = TRUE; - g_cond_signal(&priv->cond); -} - -// Executes the given function on the main thread and blocks until it completes. -static void fl_renderer_call_blocking(FlRenderer* self, - void (*callback)(gpointer data), - gpointer callback_data) { - gboolean finished = FALSE; - _FlRendererCallBlockingTrampolineData trampoline_data = { - self, - &finished, - callback, - callback_data, - }; - - gboolean scheduled = FL_RENDERER_GET_CLASS(self)->schedule_on_main_thread( - self, fl_renderer_call_blocking_trampoline, &trampoline_data); - - // Don't block if the task was not sucessfully scheduled. - if (!scheduled) { - return; - } - - // Wait for the task to complete. - FlRendererPrivate* priv = reinterpret_cast( - fl_renderer_get_instance_private(self)); - - g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&priv->mutex); - while (!finished) { - g_cond_wait(&priv->cond, &priv->mutex); - } -} - gboolean fl_renderer_create_backing_store( FlRenderer* self, const FlutterBackingStoreConfig* config, @@ -646,34 +580,67 @@ struct _FlRendererPresentLayersData { FlutterViewId view_id; const FlutterLayer** layers; size_t layers_count; - gboolean* res; + gboolean res; + gboolean finished; }; -static void fl_renderer_present_layers_trampoline(gpointer trampoline_data) { +// Scheduled from the raster thread to run on main thread and forward +// the arguments to fl_renderer_present_layers_on_main_thread. +static void fl_renderer_present_layers_trampoline(gpointer user_data) { _FlRendererPresentLayersData* data = - reinterpret_cast<_FlRendererPresentLayersData*>(trampoline_data); + static_cast<_FlRendererPresentLayersData*>(user_data); + fl_renderer_make_current(data->self); - *data->res = fl_renderer_present_layers_on_main_thread( + data->res = fl_renderer_present_layers_on_main_thread( data->self, data->view_id, data->layers, data->layers_count); fl_renderer_clear_current(data->self); + + // Unblock the raster thread. + FlRendererPrivate* priv = reinterpret_cast( + fl_renderer_get_instance_private(data->self)); + g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&priv->mutex); + data->finished = TRUE; + g_cond_signal(&priv->cond); } +// Called by the engine on the raster thread. Will reschedule +// the call to fl_renderer_present_layers_on_main_thread +// and block until it completes. gboolean fl_renderer_present_layers(FlRenderer* self, FlutterViewId view_id, const FlutterLayer** layers, size_t layers_count) { - gboolean res = FALSE; _FlRendererPresentLayersData data = { .self = self, .view_id = view_id, .layers = layers, .layers_count = layers_count, - .res = &res, + .res = FALSE, + .finished = FALSE, }; fl_renderer_clear_current(self); - fl_renderer_call_blocking(self, fl_renderer_present_layers_trampoline, &data); + FlRendererPrivate* priv = reinterpret_cast( + fl_renderer_get_instance_private(self)); + g_autoptr(FlEngine) engine = FL_ENGINE(g_weak_ref_get(&priv->engine)); + + if (engine == nullptr) { + // Task runner not available (i.e. test environment), run the callback + // synchronously. + fl_renderer_present_layers_trampoline(&data); + return data.res; + } + + FlTaskRunner* task_runner = fl_engine_get_task_runner(engine); + fl_task_runner_post_callback(task_runner, + fl_renderer_present_layers_trampoline, &data); + + // Wait for the task to complete. + g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&priv->mutex); + while (!data.finished) { + g_cond_wait(&priv->cond, &priv->mutex); + } fl_renderer_make_current(self); - return res; + return data.res; } void fl_renderer_setup(FlRenderer* self) { diff --git a/engine/src/flutter/shell/platform/linux/fl_renderer.h b/engine/src/flutter/shell/platform/linux/fl_renderer.h index ca31785feca79..b4b5216c70f80 100644 --- a/engine/src/flutter/shell/platform/linux/fl_renderer.h +++ b/engine/src/flutter/shell/platform/linux/fl_renderer.h @@ -55,14 +55,6 @@ struct _FlRendererClass { * @renderer: an #FlRenderer. */ void (*clear_current)(FlRenderer* renderer); - - /** - * Virtual method responsible for scheduling the callback on the main thread. - * Overriden in the mock renderer. - */ - gboolean (*schedule_on_main_thread)(FlRenderer* renderer, - void (*callback)(gpointer data), - gpointer data); }; /** diff --git a/engine/src/flutter/shell/platform/linux/testing/mock_renderer.cc b/engine/src/flutter/shell/platform/linux/testing/mock_renderer.cc index f40a94b58e76e..e40a558d61c1c 100644 --- a/engine/src/flutter/shell/platform/linux/testing/mock_renderer.cc +++ b/engine/src/flutter/shell/platform/linux/testing/mock_renderer.cc @@ -30,13 +30,6 @@ static void fl_mock_renderer_make_current(FlRenderer* renderer) {} static void fl_mock_renderer_make_resource_current(FlRenderer* renderer) {} // Implements FlRenderer::schedule_on_main_thread. -static gboolean fl_mock_renderer_schedule_on_main_thread( - FlRenderer* renderer, - void (*callback)(gpointer data), - gpointer data) { - callback(data); - return TRUE; -} // Implements FlRenderer::clear_current. static void fl_mock_renderer_clear_current(FlRenderer* renderer) {} @@ -46,8 +39,6 @@ static void fl_mock_renderer_class_init(FlMockRendererClass* klass) { FL_RENDERER_CLASS(klass)->make_resource_current = fl_mock_renderer_make_resource_current; FL_RENDERER_CLASS(klass)->clear_current = fl_mock_renderer_clear_current; - FL_RENDERER_CLASS(klass)->schedule_on_main_thread = - fl_mock_renderer_schedule_on_main_thread; } static void fl_mock_renderer_init(FlMockRenderer* self) {} From b5d8acbd98282868b46e497b043caa145a5461f7 Mon Sep 17 00:00:00 2001 From: Matej Date: Tue, 11 Feb 2025 23:31:42 +0100 Subject: [PATCH 03/11] Add documentation --- engine/src/flutter/shell/platform/linux/fl_task_runner.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/engine/src/flutter/shell/platform/linux/fl_task_runner.h b/engine/src/flutter/shell/platform/linux/fl_task_runner.h index 7e1383ce8d605..953583090caae 100644 --- a/engine/src/flutter/shell/platform/linux/fl_task_runner.h +++ b/engine/src/flutter/shell/platform/linux/fl_task_runner.h @@ -43,7 +43,9 @@ void fl_task_runner_post_flutter_task(FlTaskRunner* task_runner, * @callback: callback to be scheduled * @data: data to be passed to the callback * - * Schedules arbitrary callback to be executed on main thread. + * Schedules arbitrary callback to be executed on main thread. The callback + * will be executed in next run loop turn.This function is thread + * safe and may be called from any thread. */ void fl_task_runner_post_callback(FlTaskRunner* task_runner, void (*callback)(gpointer data), From ab6e170ecbd3ca8c3743f6180d527e8fe099bdfb Mon Sep 17 00:00:00 2001 From: Matej Date: Tue, 11 Feb 2025 23:32:28 +0100 Subject: [PATCH 04/11] reorder --- engine/src/flutter/shell/platform/linux/fl_renderer.cc | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/engine/src/flutter/shell/platform/linux/fl_renderer.cc b/engine/src/flutter/shell/platform/linux/fl_renderer.cc index 1d8e0b3b4b0a1..e5848e3c6e93b 100644 --- a/engine/src/flutter/shell/platform/linux/fl_renderer.cc +++ b/engine/src/flutter/shell/platform/linux/fl_renderer.cc @@ -316,11 +316,10 @@ static void fl_renderer_dispose(GObject* object) { g_weak_ref_clear(&priv->engine); g_clear_pointer(&priv->views, g_hash_table_unref); g_clear_pointer(&priv->framebuffers_by_view_id, g_hash_table_unref); - - G_OBJECT_CLASS(fl_renderer_parent_class)->dispose(object); - g_mutex_clear(&priv->mutex); g_cond_clear(&priv->cond); + + G_OBJECT_CLASS(fl_renderer_parent_class)->dispose(object); } static void fl_renderer_class_init(FlRendererClass* klass) { From e799ce9a0aa37ca4f884c28c6a43093d63436275 Mon Sep 17 00:00:00 2001 From: Matej Date: Tue, 11 Feb 2025 23:36:25 +0100 Subject: [PATCH 05/11] document every field --- engine/src/flutter/shell/platform/linux/fl_renderer.cc | 7 +++++-- engine/src/flutter/shell/platform/linux/fl_task_runner.cc | 2 ++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/engine/src/flutter/shell/platform/linux/fl_renderer.cc b/engine/src/flutter/shell/platform/linux/fl_renderer.cc index e5848e3c6e93b..fc3c3d8c30b84 100644 --- a/engine/src/flutter/shell/platform/linux/fl_renderer.cc +++ b/engine/src/flutter/shell/platform/linux/fl_renderer.cc @@ -74,9 +74,12 @@ typedef struct { // Framebuffers to render keyed by view ID. GHashTable* framebuffers_by_view_id; - // Mutex and condition used when block the raster thread until a task - // is completed on platform thread. + // Mutex used when blocking the raster thread until a task is completed on + // platform thread. GMutex mutex; + + // Condition to unblock the raster thread after task is completed on platform + // thread. GCond cond; } FlRendererPrivate; diff --git a/engine/src/flutter/shell/platform/linux/fl_task_runner.cc b/engine/src/flutter/shell/platform/linux/fl_task_runner.cc index d09b96ca9b7ae..5a225da1ed692 100644 --- a/engine/src/flutter/shell/platform/linux/fl_task_runner.cc +++ b/engine/src/flutter/shell/platform/linux/fl_task_runner.cc @@ -31,6 +31,8 @@ typedef struct _FlTaskRunnerTask { // callback to execute if scheduled through fl_task_runner_post_callback. void (*callback)(gpointer data); + + // data for the callback. gpointer callback_data; } FlTaskRunnerTask; From 58a23785a4ebeef4c5429fb15fd2e10fa91e4c78 Mon Sep 17 00:00:00 2001 From: Matej Date: Tue, 11 Feb 2025 23:38:00 +0100 Subject: [PATCH 06/11] missing space --- engine/src/flutter/shell/platform/linux/fl_task_runner.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/flutter/shell/platform/linux/fl_task_runner.h b/engine/src/flutter/shell/platform/linux/fl_task_runner.h index 953583090caae..0c7c7effc2ac7 100644 --- a/engine/src/flutter/shell/platform/linux/fl_task_runner.h +++ b/engine/src/flutter/shell/platform/linux/fl_task_runner.h @@ -44,7 +44,7 @@ void fl_task_runner_post_flutter_task(FlTaskRunner* task_runner, * @data: data to be passed to the callback * * Schedules arbitrary callback to be executed on main thread. The callback - * will be executed in next run loop turn.This function is thread + * will be executed in next run loop turn. This function is thread * safe and may be called from any thread. */ void fl_task_runner_post_callback(FlTaskRunner* task_runner, From de3fc9184e8cb312ec2c267d96dfc4096616280b Mon Sep 17 00:00:00 2001 From: Matej Date: Tue, 11 Feb 2025 23:44:34 +0100 Subject: [PATCH 07/11] restore context --- engine/src/flutter/shell/platform/linux/fl_renderer.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/engine/src/flutter/shell/platform/linux/fl_renderer.cc b/engine/src/flutter/shell/platform/linux/fl_renderer.cc index fc3c3d8c30b84..54c4ab3be1d6f 100644 --- a/engine/src/flutter/shell/platform/linux/fl_renderer.cc +++ b/engine/src/flutter/shell/platform/linux/fl_renderer.cc @@ -612,6 +612,8 @@ gboolean fl_renderer_present_layers(FlRenderer* self, FlutterViewId view_id, const FlutterLayer** layers, size_t layers_count) { + fl_renderer_clear_current(self); + _FlRendererPresentLayersData data = { .self = self, .view_id = view_id, @@ -620,7 +622,6 @@ gboolean fl_renderer_present_layers(FlRenderer* self, .res = FALSE, .finished = FALSE, }; - fl_renderer_clear_current(self); FlRendererPrivate* priv = reinterpret_cast( fl_renderer_get_instance_private(self)); g_autoptr(FlEngine) engine = FL_ENGINE(g_weak_ref_get(&priv->engine)); @@ -629,6 +630,7 @@ gboolean fl_renderer_present_layers(FlRenderer* self, // Task runner not available (i.e. test environment), run the callback // synchronously. fl_renderer_present_layers_trampoline(&data); + fl_renderer_make_current(self); return data.res; } @@ -641,6 +643,7 @@ gboolean fl_renderer_present_layers(FlRenderer* self, while (!data.finished) { g_cond_wait(&priv->cond, &priv->mutex); } + fl_renderer_make_current(self); return data.res; } From 81c68d32649807f2f2532faafedcb132d2fd8e4f Mon Sep 17 00:00:00 2001 From: Matej Date: Tue, 11 Feb 2025 23:47:37 +0100 Subject: [PATCH 08/11] remove obsolete comment --- .../src/flutter/shell/platform/linux/testing/mock_renderer.cc | 2 -- 1 file changed, 2 deletions(-) diff --git a/engine/src/flutter/shell/platform/linux/testing/mock_renderer.cc b/engine/src/flutter/shell/platform/linux/testing/mock_renderer.cc index e40a558d61c1c..5ae2926efa53d 100644 --- a/engine/src/flutter/shell/platform/linux/testing/mock_renderer.cc +++ b/engine/src/flutter/shell/platform/linux/testing/mock_renderer.cc @@ -29,8 +29,6 @@ static void fl_mock_renderer_make_current(FlRenderer* renderer) {} // Implements FlRenderer::make_resource_current. static void fl_mock_renderer_make_resource_current(FlRenderer* renderer) {} -// Implements FlRenderer::schedule_on_main_thread. - // Implements FlRenderer::clear_current. static void fl_mock_renderer_clear_current(FlRenderer* renderer) {} From e40a802fcde917f94c371847f4fa7e8cd17db745 Mon Sep 17 00:00:00 2001 From: Matej Date: Thu, 13 Feb 2025 10:41:13 +0100 Subject: [PATCH 09/11] Fix tests instead of having special codepath in fl_renderer --- .../shell/platform/linux/fl_renderer.cc | 8 - .../shell/platform/linux/fl_renderer_test.cc | 218 ++++++++++++++---- 2 files changed, 174 insertions(+), 52 deletions(-) diff --git a/engine/src/flutter/shell/platform/linux/fl_renderer.cc b/engine/src/flutter/shell/platform/linux/fl_renderer.cc index 54c4ab3be1d6f..363da64854b3f 100644 --- a/engine/src/flutter/shell/platform/linux/fl_renderer.cc +++ b/engine/src/flutter/shell/platform/linux/fl_renderer.cc @@ -626,14 +626,6 @@ gboolean fl_renderer_present_layers(FlRenderer* self, fl_renderer_get_instance_private(self)); g_autoptr(FlEngine) engine = FL_ENGINE(g_weak_ref_get(&priv->engine)); - if (engine == nullptr) { - // Task runner not available (i.e. test environment), run the callback - // synchronously. - fl_renderer_present_layers_trampoline(&data); - fl_renderer_make_current(self); - return data.res; - } - FlTaskRunner* task_runner = fl_engine_get_task_runner(engine); fl_task_runner_post_callback(task_runner, fl_renderer_present_layers_trampoline, &data); diff --git a/engine/src/flutter/shell/platform/linux/fl_renderer_test.cc b/engine/src/flutter/shell/platform/linux/fl_renderer_test.cc index 0c808fb2f1707..ab300fad0dff7 100644 --- a/engine/src/flutter/shell/platform/linux/fl_renderer_test.cc +++ b/engine/src/flutter/shell/platform/linux/fl_renderer_test.cc @@ -2,10 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include #include "gtest/gtest.h" #include "flutter/common/constants.h" #include "flutter/fml/logging.h" +#include "flutter/fml/synchronization/waitable_event.h" #include "flutter/shell/platform/linux/fl_framebuffer.h" #include "flutter/shell/platform/linux/testing/mock_epoxy.h" #include "flutter/shell/platform/linux/testing/mock_renderer.h" @@ -14,6 +16,8 @@ TEST(FlRendererTest, BackgroundColor) { ::testing::NiceMock epoxy; + g_autoptr(FlDartProject) project = fl_dart_project_new(); + g_autoptr(FlEngine) engine = fl_engine_new(project); ON_CALL(epoxy, epoxy_is_desktop_gl).WillByDefault(::testing::Return(true)); EXPECT_CALL(epoxy, epoxy_gl_version).WillRepeatedly(::testing::Return(30)); @@ -25,6 +29,7 @@ TEST(FlRendererTest, BackgroundColor) { g_autoptr(FlMockRenderable) renderable = fl_mock_renderable_new(); g_autoptr(FlMockRenderer) renderer = fl_mock_renderer_new(); fl_renderer_setup(FL_RENDERER(renderer)); + fl_renderer_set_engine(FL_RENDERER(renderer), engine); fl_renderer_add_renderable(FL_RENDERER(renderer), flutter::kFlutterImplicitViewId, FL_RENDERABLE(renderable)); @@ -35,27 +40,47 @@ TEST(FlRendererTest, BackgroundColor) { FlutterBackingStore backing_store; fl_renderer_create_backing_store(FL_RENDERER(renderer), &config, &backing_store); - const FlutterLayer layer0 = {.struct_size = sizeof(FlutterLayer), - .type = kFlutterLayerContentTypeBackingStore, - .backing_store = &backing_store, - .size = {.width = 1024, .height = 1024}}; - const FlutterLayer* layers[] = {&layer0}; - fl_renderer_present_layers(FL_RENDERER(renderer), - flutter::kFlutterImplicitViewId, layers, 1); + + fml::AutoResetWaitableEvent latch; + + // Simulate raster thread. + std::thread([&]() { + const FlutterLayer layer0 = {.struct_size = sizeof(FlutterLayer), + .type = kFlutterLayerContentTypeBackingStore, + .backing_store = &backing_store, + .size = {.width = 1024, .height = 1024}}; + const FlutterLayer* layers[] = {&layer0}; + + fl_renderer_present_layers(FL_RENDERER(renderer), + flutter::kFlutterImplicitViewId, layers, 1); + latch.Signal(); + }).detach(); + + while (fl_mock_renderable_get_redraw_count(renderable) == 0) { + g_main_context_iteration(nullptr, true); + } + GdkRGBA background_color = { .red = 0.2, .green = 0.3, .blue = 0.4, .alpha = 0.5}; fl_renderer_render(FL_RENDERER(renderer), flutter::kFlutterImplicitViewId, 1024, 1024, &background_color); + + // Wait until the raster thread has finished before letting + // the engine go out of scope. + latch.Wait(); } TEST(FlRendererTest, RestoresGLState) { ::testing::NiceMock epoxy; + g_autoptr(FlDartProject) project = fl_dart_project_new(); + g_autoptr(FlEngine) engine = fl_engine_new(project); constexpr int kWidth = 100; constexpr int kHeight = 100; g_autoptr(FlMockRenderable) renderable = fl_mock_renderable_new(); g_autoptr(FlMockRenderer) renderer = fl_mock_renderer_new(); + fl_renderer_set_engine(FL_RENDERER(renderer), engine); g_autoptr(FlFramebuffer) framebuffer = fl_framebuffer_new(GL_RGB, kWidth, kHeight); @@ -79,9 +104,20 @@ TEST(FlRendererTest, RestoresGLState) { constexpr GLuint kFakeTextureName = 123; glBindTexture(GL_TEXTURE_2D, kFakeTextureName); - fl_renderer_present_layers(FL_RENDERER(renderer), - flutter::kFlutterImplicitViewId, layers.data(), - layers.size()); + fml::AutoResetWaitableEvent latch; + + // Simulate raster thread. + std::thread([&]() { + fl_renderer_present_layers(FL_RENDERER(renderer), + flutter::kFlutterImplicitViewId, layers.data(), + layers.size()); + latch.Signal(); + }).detach(); + + while (fl_mock_renderable_get_redraw_count(renderable) == 0) { + g_main_context_iteration(nullptr, true); + } + GdkRGBA background_color = { .red = 0.0, .green = 0.0, .blue = 0.0, .alpha = 1.0}; fl_renderer_render(FL_RENDERER(renderer), flutter::kFlutterImplicitViewId, @@ -91,10 +127,16 @@ TEST(FlRendererTest, RestoresGLState) { glGetIntegerv(GL_TEXTURE_BINDING_2D, reinterpret_cast(&texture_2d_binding)); EXPECT_EQ(texture_2d_binding, kFakeTextureName); + + // Wait until the raster thread has finished before letting + // the engine go out of scope. + latch.Wait(); } TEST(FlRendererTest, BlitFramebuffer) { ::testing::NiceMock epoxy; + g_autoptr(FlDartProject) project = fl_dart_project_new(); + g_autoptr(FlEngine) engine = fl_engine_new(project); // OpenGL 3.0 ON_CALL(epoxy, glGetString(GL_VENDOR)) @@ -108,6 +150,7 @@ TEST(FlRendererTest, BlitFramebuffer) { g_autoptr(FlMockRenderable) renderable = fl_mock_renderable_new(); g_autoptr(FlMockRenderer) renderer = fl_mock_renderer_new(); fl_renderer_setup(FL_RENDERER(renderer)); + fl_renderer_set_engine(FL_RENDERER(renderer), engine); fl_renderer_add_renderable(FL_RENDERER(renderer), flutter::kFlutterImplicitViewId, FL_RENDERABLE(renderable)); @@ -118,21 +161,37 @@ TEST(FlRendererTest, BlitFramebuffer) { FlutterBackingStore backing_store; fl_renderer_create_backing_store(FL_RENDERER(renderer), &config, &backing_store); - const FlutterLayer layer0 = {.struct_size = sizeof(FlutterLayer), - .type = kFlutterLayerContentTypeBackingStore, - .backing_store = &backing_store, - .size = {.width = 1024, .height = 1024}}; - const FlutterLayer* layers[] = {&layer0}; - fl_renderer_present_layers(FL_RENDERER(renderer), - flutter::kFlutterImplicitViewId, layers, 1); + + fml::AutoResetWaitableEvent latch; + + // Simulate raster thread. + std::thread([&]() { + const FlutterLayer layer0 = {.struct_size = sizeof(FlutterLayer), + .type = kFlutterLayerContentTypeBackingStore, + .backing_store = &backing_store, + .size = {.width = 1024, .height = 1024}}; + const FlutterLayer* layers[] = {&layer0}; + fl_renderer_present_layers(FL_RENDERER(renderer), + flutter::kFlutterImplicitViewId, layers, 1); + latch.Signal(); + }).detach(); + + while (fl_mock_renderable_get_redraw_count(renderable) == 0) { + g_main_context_iteration(nullptr, true); + } + GdkRGBA background_color = { .red = 0.0, .green = 0.0, .blue = 0.0, .alpha = 1.0}; fl_renderer_render(FL_RENDERER(renderer), flutter::kFlutterImplicitViewId, 1024, 1024, &background_color); + + latch.Wait(); } TEST(FlRendererTest, BlitFramebufferExtension) { ::testing::NiceMock epoxy; + g_autoptr(FlDartProject) project = fl_dart_project_new(); + g_autoptr(FlEngine) engine = fl_engine_new(project); // OpenGL 2.0 with GL_EXT_framebuffer_blit extension ON_CALL(epoxy, glGetString(GL_VENDOR)) @@ -151,6 +210,7 @@ TEST(FlRendererTest, BlitFramebufferExtension) { g_autoptr(FlMockRenderable) renderable = fl_mock_renderable_new(); g_autoptr(FlMockRenderer) renderer = fl_mock_renderer_new(); fl_renderer_setup(FL_RENDERER(renderer)); + fl_renderer_set_engine(FL_RENDERER(renderer), engine); fl_renderer_add_renderable(FL_RENDERER(renderer), flutter::kFlutterImplicitViewId, FL_RENDERABLE(renderable)); @@ -161,21 +221,37 @@ TEST(FlRendererTest, BlitFramebufferExtension) { FlutterBackingStore backing_store; fl_renderer_create_backing_store(FL_RENDERER(renderer), &config, &backing_store); - const FlutterLayer layer0 = {.struct_size = sizeof(FlutterLayer), - .type = kFlutterLayerContentTypeBackingStore, - .backing_store = &backing_store, - .size = {.width = 1024, .height = 1024}}; - const FlutterLayer* layers[] = {&layer0}; - fl_renderer_present_layers(FL_RENDERER(renderer), - flutter::kFlutterImplicitViewId, layers, 1); + + fml::AutoResetWaitableEvent latch; + + // Simulate raster thread. + std::thread([&]() { + const FlutterLayer layer0 = {.struct_size = sizeof(FlutterLayer), + .type = kFlutterLayerContentTypeBackingStore, + .backing_store = &backing_store, + .size = {.width = 1024, .height = 1024}}; + const FlutterLayer* layers[] = {&layer0}; + fl_renderer_present_layers(FL_RENDERER(renderer), + flutter::kFlutterImplicitViewId, layers, 1); + latch.Signal(); + }).detach(); + + while (fl_mock_renderable_get_redraw_count(renderable) == 0) { + g_main_context_iteration(nullptr, true); + } GdkRGBA background_color = { .red = 0.0, .green = 0.0, .blue = 0.0, .alpha = 1.0}; fl_renderer_render(FL_RENDERER(renderer), flutter::kFlutterImplicitViewId, 1024, 1024, &background_color); + // Wait until the raster thread has finished before letting + // the engine go out of scope. + latch.Wait(); } TEST(FlRendererTest, NoBlitFramebuffer) { ::testing::NiceMock epoxy; + g_autoptr(FlDartProject) project = fl_dart_project_new(); + g_autoptr(FlEngine) engine = fl_engine_new(project); // OpenGL 2.0 ON_CALL(epoxy, glGetString(GL_VENDOR)) @@ -187,6 +263,7 @@ TEST(FlRendererTest, NoBlitFramebuffer) { g_autoptr(FlMockRenderable) renderable = fl_mock_renderable_new(); g_autoptr(FlMockRenderer) renderer = fl_mock_renderer_new(); fl_renderer_setup(FL_RENDERER(renderer)); + fl_renderer_set_engine(FL_RENDERER(renderer), engine); fl_renderer_add_renderable(FL_RENDERER(renderer), flutter::kFlutterImplicitViewId, FL_RENDERABLE(renderable)); @@ -197,21 +274,39 @@ TEST(FlRendererTest, NoBlitFramebuffer) { FlutterBackingStore backing_store; fl_renderer_create_backing_store(FL_RENDERER(renderer), &config, &backing_store); - const FlutterLayer layer0 = {.struct_size = sizeof(FlutterLayer), - .type = kFlutterLayerContentTypeBackingStore, - .backing_store = &backing_store, - .size = {.width = 1024, .height = 1024}}; - const FlutterLayer* layers[] = {&layer0}; - fl_renderer_present_layers(FL_RENDERER(renderer), - flutter::kFlutterImplicitViewId, layers, 1); + + fml::AutoResetWaitableEvent latch; + + // Simulate raster thread. + std::thread([&]() { + const FlutterLayer layer0 = {.struct_size = sizeof(FlutterLayer), + .type = kFlutterLayerContentTypeBackingStore, + .backing_store = &backing_store, + .size = {.width = 1024, .height = 1024}}; + const FlutterLayer* layers[] = {&layer0}; + fl_renderer_present_layers(FL_RENDERER(renderer), + flutter::kFlutterImplicitViewId, layers, 1); + latch.Signal(); + }).detach(); + + while (fl_mock_renderable_get_redraw_count(renderable) == 0) { + g_main_context_iteration(nullptr, true); + } + GdkRGBA background_color = { .red = 0.0, .green = 0.0, .blue = 0.0, .alpha = 1.0}; fl_renderer_render(FL_RENDERER(renderer), flutter::kFlutterImplicitViewId, 1024, 1024, &background_color); + + // Wait until the raster thread has finished before letting + // the engine go out of scope. + latch.Wait(); } TEST(FlRendererTest, BlitFramebufferNvidia) { ::testing::NiceMock epoxy; + g_autoptr(FlDartProject) project = fl_dart_project_new(); + g_autoptr(FlEngine) engine = fl_engine_new(project); // OpenGL 3.0, but on NVIDIA driver so temporarily disabled due to // https://github.com/flutter/flutter/issues/152099 @@ -224,6 +319,7 @@ TEST(FlRendererTest, BlitFramebufferNvidia) { g_autoptr(FlMockRenderable) renderable = fl_mock_renderable_new(); g_autoptr(FlMockRenderer) renderer = fl_mock_renderer_new(); fl_renderer_setup(FL_RENDERER(renderer)); + fl_renderer_set_engine(FL_RENDERER(renderer), engine); fl_renderer_add_renderable(FL_RENDERER(renderer), flutter::kFlutterImplicitViewId, FL_RENDERABLE(renderable)); @@ -234,21 +330,39 @@ TEST(FlRendererTest, BlitFramebufferNvidia) { FlutterBackingStore backing_store; fl_renderer_create_backing_store(FL_RENDERER(renderer), &config, &backing_store); - const FlutterLayer layer0 = {.struct_size = sizeof(FlutterLayer), - .type = kFlutterLayerContentTypeBackingStore, - .backing_store = &backing_store, - .size = {.width = 1024, .height = 1024}}; - const FlutterLayer* layers[] = {&layer0}; - fl_renderer_present_layers(FL_RENDERER(renderer), - flutter::kFlutterImplicitViewId, layers, 1); + + fml::AutoResetWaitableEvent latch; + + // Simulate raster thread. + std::thread([&]() { + const FlutterLayer layer0 = {.struct_size = sizeof(FlutterLayer), + .type = kFlutterLayerContentTypeBackingStore, + .backing_store = &backing_store, + .size = {.width = 1024, .height = 1024}}; + const FlutterLayer* layers[] = {&layer0}; + fl_renderer_present_layers(FL_RENDERER(renderer), + flutter::kFlutterImplicitViewId, layers, 1); + latch.Signal(); + }).detach(); + + while (fl_mock_renderable_get_redraw_count(renderable) == 0) { + g_main_context_iteration(nullptr, true); + } + GdkRGBA background_color = { .red = 0.0, .green = 0.0, .blue = 0.0, .alpha = 1.0}; fl_renderer_render(FL_RENDERER(renderer), flutter::kFlutterImplicitViewId, 1024, 1024, &background_color); + + // Wait until the raster thread has finished before letting + // the engine go out of scope. + latch.Wait(); } TEST(FlRendererTest, MultiView) { ::testing::NiceMock epoxy; + g_autoptr(FlDartProject) project = fl_dart_project_new(); + g_autoptr(FlEngine) engine = fl_engine_new(project); // OpenGL 3.0 ON_CALL(epoxy, glGetString(GL_VENDOR)) @@ -262,6 +376,7 @@ TEST(FlRendererTest, MultiView) { g_autoptr(FlMockRenderer) renderer = fl_mock_renderer_new(); fl_renderer_setup(FL_RENDERER(renderer)); + fl_renderer_set_engine(FL_RENDERER(renderer), engine); fl_renderer_add_renderable(FL_RENDERER(renderer), flutter::kFlutterImplicitViewId, FL_RENDERABLE(renderable)); @@ -280,15 +395,30 @@ TEST(FlRendererTest, MultiView) { FlutterBackingStore backing_store; fl_renderer_create_backing_store(FL_RENDERER(renderer), &config, &backing_store); - const FlutterLayer layer0 = {.struct_size = sizeof(FlutterLayer), - .type = kFlutterLayerContentTypeBackingStore, - .backing_store = &backing_store, - .size = {.width = 1024, .height = 1024}}; - const FlutterLayer* layers[] = {&layer0}; - fl_renderer_present_layers(FL_RENDERER(renderer), 1, layers, 1); + + fml::AutoResetWaitableEvent latch; + + // Simulate raster thread. + std::thread([&]() { + const FlutterLayer layer0 = {.struct_size = sizeof(FlutterLayer), + .type = kFlutterLayerContentTypeBackingStore, + .backing_store = &backing_store, + .size = {.width = 1024, .height = 1024}}; + const FlutterLayer* layers[] = {&layer0}; + fl_renderer_present_layers(FL_RENDERER(renderer), 1, layers, 1); + latch.Signal(); + }).detach(); + + while (fl_mock_renderable_get_redraw_count(secondary_renderable) == 0) { + g_main_context_iteration(nullptr, true); + } EXPECT_EQ(fl_mock_renderable_get_redraw_count(renderable), static_cast(0)); EXPECT_EQ(fl_mock_renderable_get_redraw_count(secondary_renderable), static_cast(1)); + + // Wait until the raster thread has finished before letting + // the engine go out of scope. + latch.Wait(); } From e1f68676c293162a1872a6078d9bb8beb3ee6145 Mon Sep 17 00:00:00 2001 From: Robert Ancell Date: Tue, 4 Mar 2025 12:50:54 +1300 Subject: [PATCH 10/11] Improvements to naming, structure and comments --- .../shell/platform/linux/fl_renderer.cc | 330 +++++++++--------- 1 file changed, 165 insertions(+), 165 deletions(-) diff --git a/engine/src/flutter/shell/platform/linux/fl_renderer.cc b/engine/src/flutter/shell/platform/linux/fl_renderer.cc index 363da64854b3f..e720f87dbcc47 100644 --- a/engine/src/flutter/shell/platform/linux/fl_renderer.cc +++ b/engine/src/flutter/shell/platform/linux/fl_renderer.cc @@ -76,11 +76,11 @@ typedef struct { // Mutex used when blocking the raster thread until a task is completed on // platform thread. - GMutex mutex; + GMutex present_mutex; // Condition to unblock the raster thread after task is completed on platform // thread. - GCond cond; + GCond present_condition; } FlRendererPrivate; G_DEFINE_TYPE_WITH_PRIVATE(FlRenderer, fl_renderer, G_TYPE_OBJECT) @@ -309,6 +309,151 @@ static void render(FlRenderer* self, } } +static gboolean present_layers(FlRenderer* self, + FlutterViewId view_id, + const FlutterLayer** layers, + size_t layers_count) { + FlRendererPrivate* priv = reinterpret_cast( + fl_renderer_get_instance_private(self)); + + g_return_val_if_fail(FL_IS_RENDERER(self), FALSE); + + // 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); + + g_autoptr(GPtrArray) framebuffers = + g_ptr_array_new_with_free_func(g_object_unref); + for (size_t i = 0; i < layers_count; ++i) { + const FlutterLayer* layer = layers[i]; + switch (layer->type) { + case kFlutterLayerContentTypeBackingStore: { + const FlutterBackingStore* backing_store = layer->backing_store; + FlFramebuffer* framebuffer = + FL_FRAMEBUFFER(backing_store->open_gl.framebuffer.user_data); + g_ptr_array_add(framebuffers, g_object_ref(framebuffer)); + } break; + case kFlutterLayerContentTypePlatformView: { + // TODO(robert-ancell) Not implemented - + // https://github.com/flutter/flutter/issues/41724 + } break; + } + } + + GWeakRef* ref = static_cast( + g_hash_table_lookup(priv->views, GINT_TO_POINTER(view_id))); + g_autoptr(FlRenderable) renderable = + ref != nullptr ? FL_RENDERABLE(g_weak_ref_get(ref)) : nullptr; + if (renderable == nullptr) { + return TRUE; + } + + if (view_id == flutter::kFlutterImplicitViewId) { + // Store for rendering later + g_hash_table_insert(priv->framebuffers_by_view_id, GINT_TO_POINTER(view_id), + g_ptr_array_ref(framebuffers)); + } else { + // Composite into a single framebuffer. + if (framebuffers->len > 1) { + size_t width = 0, height = 0; + + for (guint i = 0; i < framebuffers->len; i++) { + FlFramebuffer* framebuffer = + FL_FRAMEBUFFER(g_ptr_array_index(framebuffers, i)); + + size_t w = fl_framebuffer_get_width(framebuffer); + size_t h = fl_framebuffer_get_height(framebuffer); + if (w > width) { + width = w; + } + if (h > height) { + height = h; + } + } + + FlFramebuffer* view_framebuffer = + fl_framebuffer_new(priv->general_format, width, height); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, + fl_framebuffer_get_id(view_framebuffer)); + render(self, framebuffers, width, height); + g_ptr_array_set_size(framebuffers, 0); + g_ptr_array_add(framebuffers, view_framebuffer); + } + + // Read back pixel values. + FlFramebuffer* framebuffer = + FL_FRAMEBUFFER(g_ptr_array_index(framebuffers, 0)); + size_t width = fl_framebuffer_get_width(framebuffer); + size_t height = fl_framebuffer_get_height(framebuffer); + size_t data_length = width * height * 4; + g_autofree uint8_t* data = static_cast(malloc(data_length)); + glBindFramebuffer(GL_READ_FRAMEBUFFER, fl_framebuffer_get_id(framebuffer)); + glReadPixels(0, 0, width, height, priv->general_format, GL_UNSIGNED_BYTE, + data); + + // Write into a texture in the views context. + fl_renderable_make_current(renderable); + FlFramebuffer* view_framebuffer = + fl_framebuffer_new(priv->general_format, width, height); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, + fl_framebuffer_get_id(view_framebuffer)); + glBindTexture(GL_TEXTURE_2D, + fl_framebuffer_get_texture_id(view_framebuffer)); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, + GL_UNSIGNED_BYTE, data); + + g_autoptr(GPtrArray) secondary_framebuffers = + g_ptr_array_new_with_free_func(g_object_unref); + g_ptr_array_add(secondary_framebuffers, g_object_ref(view_framebuffer)); + g_hash_table_insert(priv->framebuffers_by_view_id, GINT_TO_POINTER(view_id), + g_ptr_array_ref(secondary_framebuffers)); + } + + fl_renderable_redraw(renderable); + + return TRUE; +} + +typedef struct { + FlRenderer* self; + + FlutterViewId view_id; + + const FlutterLayer** layers; + size_t layers_count; + + gboolean result; + + gboolean finished; +} PresentLayersData; + +// Perform the present on the main thread. +static void present_layers_task_cb(gpointer user_data) { + PresentLayersData* data = static_cast(user_data); + FlRendererPrivate* priv = reinterpret_cast( + fl_renderer_get_instance_private(data->self)); + + // Perform the present. + fl_renderer_make_current(data->self); + data->result = present_layers(data->self, data->view_id, data->layers, + data->layers_count); + fl_renderer_clear_current(data->self); + + // Complete fl_renderer_present_layers(). + g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&priv->present_mutex); + data->finished = TRUE; + g_cond_signal(&priv->present_condition); +} + static void fl_renderer_dispose(GObject* object) { FlRenderer* self = FL_RENDERER(object); FlRendererPrivate* priv = reinterpret_cast( @@ -319,8 +464,8 @@ static void fl_renderer_dispose(GObject* object) { g_weak_ref_clear(&priv->engine); g_clear_pointer(&priv->views, g_hash_table_unref); g_clear_pointer(&priv->framebuffers_by_view_id, g_hash_table_unref); - g_mutex_clear(&priv->mutex); - g_cond_clear(&priv->cond); + g_mutex_clear(&priv->present_mutex); + g_cond_clear(&priv->present_condition); G_OBJECT_CLASS(fl_renderer_parent_class)->dispose(object); } @@ -337,8 +482,8 @@ static void fl_renderer_init(FlRenderer* self) { priv->framebuffers_by_view_id = g_hash_table_new_full( g_direct_hash, g_direct_equal, nullptr, reinterpret_cast(g_ptr_array_unref)); - g_mutex_init(&priv->mutex); - g_cond_init(&priv->cond); + g_mutex_init(&priv->present_mutex); + g_cond_init(&priv->present_condition); } void fl_renderer_set_engine(FlRenderer* self, FlEngine* engine) { @@ -462,182 +607,37 @@ void fl_renderer_wait_for_frame(FlRenderer* self, } } -static gboolean fl_renderer_present_layers_on_main_thread( - FlRenderer* self, - FlutterViewId view_id, - const FlutterLayer** layers, - size_t layers_count) { - FlRendererPrivate* priv = reinterpret_cast( - fl_renderer_get_instance_private(self)); - - g_return_val_if_fail(FL_IS_RENDERER(self), FALSE); - - // 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); - - g_autoptr(GPtrArray) framebuffers = - g_ptr_array_new_with_free_func(g_object_unref); - for (size_t i = 0; i < layers_count; ++i) { - const FlutterLayer* layer = layers[i]; - switch (layer->type) { - case kFlutterLayerContentTypeBackingStore: { - const FlutterBackingStore* backing_store = layer->backing_store; - FlFramebuffer* framebuffer = - FL_FRAMEBUFFER(backing_store->open_gl.framebuffer.user_data); - g_ptr_array_add(framebuffers, g_object_ref(framebuffer)); - } break; - case kFlutterLayerContentTypePlatformView: { - // TODO(robert-ancell) Not implemented - - // https://github.com/flutter/flutter/issues/41724 - } break; - } - } - - GWeakRef* ref = static_cast( - g_hash_table_lookup(priv->views, GINT_TO_POINTER(view_id))); - g_autoptr(FlRenderable) renderable = - ref != nullptr ? FL_RENDERABLE(g_weak_ref_get(ref)) : nullptr; - if (renderable == nullptr) { - return TRUE; - } - - if (view_id == flutter::kFlutterImplicitViewId) { - // Store for rendering later - g_hash_table_insert(priv->framebuffers_by_view_id, GINT_TO_POINTER(view_id), - g_ptr_array_ref(framebuffers)); - } else { - // Composite into a single framebuffer. - if (framebuffers->len > 1) { - size_t width = 0, height = 0; - - for (guint i = 0; i < framebuffers->len; i++) { - FlFramebuffer* framebuffer = - FL_FRAMEBUFFER(g_ptr_array_index(framebuffers, i)); - - size_t w = fl_framebuffer_get_width(framebuffer); - size_t h = fl_framebuffer_get_height(framebuffer); - if (w > width) { - width = w; - } - if (h > height) { - height = h; - } - } - - FlFramebuffer* view_framebuffer = - fl_framebuffer_new(priv->general_format, width, height); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, - fl_framebuffer_get_id(view_framebuffer)); - render(self, framebuffers, width, height); - g_ptr_array_set_size(framebuffers, 0); - g_ptr_array_add(framebuffers, view_framebuffer); - } - - // Read back pixel values. - FlFramebuffer* framebuffer = - FL_FRAMEBUFFER(g_ptr_array_index(framebuffers, 0)); - size_t width = fl_framebuffer_get_width(framebuffer); - size_t height = fl_framebuffer_get_height(framebuffer); - size_t data_length = width * height * 4; - g_autofree uint8_t* data = static_cast(malloc(data_length)); - glBindFramebuffer(GL_READ_FRAMEBUFFER, fl_framebuffer_get_id(framebuffer)); - glReadPixels(0, 0, width, height, priv->general_format, GL_UNSIGNED_BYTE, - data); - - // Write into a texture in the views context. - fl_renderable_make_current(renderable); - FlFramebuffer* view_framebuffer = - fl_framebuffer_new(priv->general_format, width, height); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, - fl_framebuffer_get_id(view_framebuffer)); - glBindTexture(GL_TEXTURE_2D, - fl_framebuffer_get_texture_id(view_framebuffer)); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, - GL_UNSIGNED_BYTE, data); - - g_autoptr(GPtrArray) secondary_framebuffers = - g_ptr_array_new_with_free_func(g_object_unref); - g_ptr_array_add(secondary_framebuffers, g_object_ref(view_framebuffer)); - g_hash_table_insert(priv->framebuffers_by_view_id, GINT_TO_POINTER(view_id), - g_ptr_array_ref(secondary_framebuffers)); - } - - fl_renderable_redraw(renderable); - - return TRUE; -} - -struct _FlRendererPresentLayersData { - FlRenderer* self; - FlutterViewId view_id; - const FlutterLayer** layers; - size_t layers_count; - gboolean res; - gboolean finished; -}; - -// Scheduled from the raster thread to run on main thread and forward -// the arguments to fl_renderer_present_layers_on_main_thread. -static void fl_renderer_present_layers_trampoline(gpointer user_data) { - _FlRendererPresentLayersData* data = - static_cast<_FlRendererPresentLayersData*>(user_data); - - fl_renderer_make_current(data->self); - data->res = fl_renderer_present_layers_on_main_thread( - data->self, data->view_id, data->layers, data->layers_count); - fl_renderer_clear_current(data->self); - - // Unblock the raster thread. - FlRendererPrivate* priv = reinterpret_cast( - fl_renderer_get_instance_private(data->self)); - g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&priv->mutex); - data->finished = TRUE; - g_cond_signal(&priv->cond); -} - -// Called by the engine on the raster thread. Will reschedule -// the call to fl_renderer_present_layers_on_main_thread -// and block until it completes. gboolean fl_renderer_present_layers(FlRenderer* self, FlutterViewId view_id, const FlutterLayer** layers, size_t layers_count) { fl_renderer_clear_current(self); - _FlRendererPresentLayersData data = { + FlRendererPrivate* priv = reinterpret_cast( + fl_renderer_get_instance_private(self)); + g_autoptr(FlEngine) engine = FL_ENGINE(g_weak_ref_get(&priv->engine)); + + // Schedule the present to run on the main thread. + FlTaskRunner* task_runner = fl_engine_get_task_runner(engine); + PresentLayersData data = { .self = self, .view_id = view_id, .layers = layers, .layers_count = layers_count, - .res = FALSE, + .result = FALSE, .finished = FALSE, }; - FlRendererPrivate* priv = reinterpret_cast( - fl_renderer_get_instance_private(self)); - g_autoptr(FlEngine) engine = FL_ENGINE(g_weak_ref_get(&priv->engine)); - - FlTaskRunner* task_runner = fl_engine_get_task_runner(engine); - fl_task_runner_post_callback(task_runner, - fl_renderer_present_layers_trampoline, &data); + fl_task_runner_post_callback(task_runner, present_layers_task_cb, &data); - // Wait for the task to complete. - g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&priv->mutex); + // Block until present completes. + g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&priv->present_mutex); while (!data.finished) { - g_cond_wait(&priv->cond, &priv->mutex); + g_cond_wait(&priv->present_condition, &priv->present_mutex); } fl_renderer_make_current(self); - return data.res; + + return data.result; } void fl_renderer_setup(FlRenderer* self) { From 432b8bd33bbbb3bd00ed6a5ce1c8791d8ecb1798 Mon Sep 17 00:00:00 2001 From: Matej Date: Tue, 4 Mar 2025 11:47:48 +0100 Subject: [PATCH 11/11] Comments --- engine/src/flutter/shell/platform/linux/fl_renderer.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/engine/src/flutter/shell/platform/linux/fl_renderer.cc b/engine/src/flutter/shell/platform/linux/fl_renderer.cc index e720f87dbcc47..518b0b2904302 100644 --- a/engine/src/flutter/shell/platform/linux/fl_renderer.cc +++ b/engine/src/flutter/shell/platform/linux/fl_renderer.cc @@ -611,6 +611,8 @@ gboolean fl_renderer_present_layers(FlRenderer* self, FlutterViewId view_id, const FlutterLayer** layers, size_t layers_count) { + // Detach the context from raster thread. Needed because blitting + // will be done on the main thread, which will make the context current. fl_renderer_clear_current(self); FlRendererPrivate* priv = reinterpret_cast( @@ -635,6 +637,8 @@ gboolean fl_renderer_present_layers(FlRenderer* self, g_cond_wait(&priv->present_condition, &priv->present_mutex); } + // Restore the context to the raster thread in case the engine needs it + // to do some cleanup. fl_renderer_make_current(self); return data.result; pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy