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..518b0b2904302 100644 --- a/engine/src/flutter/shell/platform/linux/fl_renderer.cc +++ b/engine/src/flutter/shell/platform/linux/fl_renderer.cc @@ -73,6 +73,14 @@ typedef struct { // Framebuffers to render keyed by view ID. GHashTable* framebuffers_by_view_id; + + // Mutex used when blocking the raster thread until a task is completed on + // platform thread. + GMutex present_mutex; + + // Condition to unblock the raster thread after task is completed on platform + // thread. + GCond present_condition; } FlRendererPrivate; G_DEFINE_TYPE_WITH_PRIVATE(FlRenderer, fl_renderer, G_TYPE_OBJECT) @@ -301,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( @@ -311,6 +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->present_mutex); + g_cond_clear(&priv->present_condition); G_OBJECT_CLASS(fl_renderer_parent_class)->dispose(object); } @@ -327,6 +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->present_mutex); + g_cond_init(&priv->present_condition); } void fl_renderer_set_engine(FlRenderer* self, FlEngine* engine) { @@ -454,114 +611,37 @@ 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( 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, + .result = FALSE, + .finished = FALSE, + }; + fl_task_runner_post_callback(task_runner, present_layers_task_cb, &data); - 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)); + // Block until present completes. + g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&priv->present_mutex); + while (!data.finished) { + g_cond_wait(&priv->present_condition, &priv->present_mutex); } - fl_renderable_redraw(renderable); + // Restore the context to the raster thread in case the engine needs it + // to do some cleanup. + fl_renderer_make_current(self); - return TRUE; + return data.result; } void fl_renderer_setup(FlRenderer* self) { 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(); } 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..5a225da1ed692 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,18 @@ 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); + + // data for the callback. + gpointer callback_data; } FlTaskRunnerTask; G_DEFINE_TYPE(FlTaskRunner, fl_task_runner, G_TYPE_OBJECT) @@ -56,7 +65,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 +171,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 +186,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..0c7c7effc2ac7 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,23 @@ 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. 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), + 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; 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