Skip to content

[Linux] Move rendering to raster thread #161879

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Mar 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions engine/src/flutter/shell/platform/linux/fl_engine.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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);
Expand Down
284 changes: 182 additions & 102 deletions engine/src/flutter/shell/platform/linux/fl_renderer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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*>(
Expand All @@ -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);
}
Expand All @@ -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) {
Expand Down Expand Up @@ -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));

// 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<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));
// 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) {
Expand Down
Loading
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