[Linux] Move rendering to raster thread (#161879)

Fixes https://github.com/flutter/flutter/issues/155045

*If you had to change anything in the [flutter/tests] repo, include a
link to the migration guide as per the [breaking change policy].*

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md

---------

Co-authored-by: Robert Ancell <robert.ancell@canonical.com>
This commit is contained in:
Matej Knopp 2025-03-11 10:30:03 +01:00 committed by GitHub
parent 7b07e597f5
commit 92fdf29e6f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 410 additions and 156 deletions

View File

@ -356,7 +356,7 @@ static void fl_engine_post_task(FlutterTask task,
void* user_data) {
FlEngine* self = static_cast<FlEngine*>(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);

View File

@ -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<FlRendererPrivate*>(
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<GWeakRef*>(
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<uint8_t*>(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<PresentLayersData*>(user_data);
FlRendererPrivate* priv = reinterpret_cast<FlRendererPrivate*>(
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<FlRendererPrivate*>(
@ -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<GDestroyNotify>(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<FlRendererPrivate*>(
fl_renderer_get_instance_private(self));
g_autoptr(FlEngine) engine = FL_ENGINE(g_weak_ref_get(&priv->engine));
g_return_val_if_fail(FL_IS_RENDERER(self), FALSE);
// 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);
// 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;
// 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);
}
priv->had_first_frame = true;
// Restore the context to the raster thread in case the engine needs it
// to do some cleanup.
fl_renderer_make_current(self);
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<GWeakRef*>(
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<uint8_t*>(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;
return data.result;
}
void fl_renderer_setup(FlRenderer* self) {

View File

@ -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 <thread>
#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<flutter::testing::MockEpoxy> 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<flutter::testing::MockEpoxy> 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<GLint*>(&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<flutter::testing::MockEpoxy> 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<flutter::testing::MockEpoxy> 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<flutter::testing::MockEpoxy> 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<flutter::testing::MockEpoxy> 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<flutter::testing::MockEpoxy> 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<size_t>(0));
EXPECT_EQ(fl_mock_renderable_get_redraw_count(secondary_renderable),
static_cast<size_t>(1));
// Wait until the raster thread has finished before letting
// the engine go out of scope.
latch.Wait();
}

View File

@ -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<FlTaskRunnerTask*>(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

View File

@ -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:

View File

@ -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;