[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:
parent
7b07e597f5
commit
92fdf29e6f
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user