Skip to content

Commit 92fdf29

Browse files
knopprobert-ancell
andauthored
[Linux] Move rendering to raster thread (#161879)
Fixes flutter/flutter#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>
1 parent 7b07e59 commit 92fdf29

File tree

6 files changed

+411
-157
lines changed

6 files changed

+411
-157
lines changed

engine/src/flutter/shell/platform/linux/fl_engine.cc

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@ static void fl_engine_post_task(FlutterTask task,
356356
void* user_data) {
357357
FlEngine* self = static_cast<FlEngine*>(user_data);
358358

359-
fl_task_runner_post_task(self->task_runner, task, target_time_nanos);
359+
fl_task_runner_post_flutter_task(self->task_runner, task, target_time_nanos);
360360
}
361361

362362
// Called when a platform message is received from the engine.
@@ -630,7 +630,6 @@ gboolean fl_engine_start(FlEngine* self, GError** error) {
630630
FlutterCustomTaskRunners custom_task_runners = {};
631631
custom_task_runners.struct_size = sizeof(FlutterCustomTaskRunners);
632632
custom_task_runners.platform_task_runner = &platform_task_runner;
633-
custom_task_runners.render_task_runner = &platform_task_runner;
634633

635634
g_autoptr(GPtrArray) command_line_args =
636635
g_ptr_array_new_with_free_func(g_free);

engine/src/flutter/shell/platform/linux/fl_renderer.cc

Lines changed: 182 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,14 @@ typedef struct {
7373

7474
// Framebuffers to render keyed by view ID.
7575
GHashTable* framebuffers_by_view_id;
76+
77+
// Mutex used when blocking the raster thread until a task is completed on
78+
// platform thread.
79+
GMutex present_mutex;
80+
81+
// Condition to unblock the raster thread after task is completed on platform
82+
// thread.
83+
GCond present_condition;
7684
} FlRendererPrivate;
7785

7886
G_DEFINE_TYPE_WITH_PRIVATE(FlRenderer, fl_renderer, G_TYPE_OBJECT)
@@ -301,6 +309,151 @@ static void render(FlRenderer* self,
301309
}
302310
}
303311

312+
static gboolean present_layers(FlRenderer* self,
313+
FlutterViewId view_id,
314+
const FlutterLayer** layers,
315+
size_t layers_count) {
316+
FlRendererPrivate* priv = reinterpret_cast<FlRendererPrivate*>(
317+
fl_renderer_get_instance_private(self));
318+
319+
g_return_val_if_fail(FL_IS_RENDERER(self), FALSE);
320+
321+
// ignore incoming frame with wrong dimensions in trivial case with just one
322+
// layer
323+
if (priv->blocking_main_thread && layers_count == 1 &&
324+
layers[0]->offset.x == 0 && layers[0]->offset.y == 0 &&
325+
(layers[0]->size.width != priv->target_width ||
326+
layers[0]->size.height != priv->target_height)) {
327+
return TRUE;
328+
}
329+
330+
priv->had_first_frame = true;
331+
332+
fl_renderer_unblock_main_thread(self);
333+
334+
g_autoptr(GPtrArray) framebuffers =
335+
g_ptr_array_new_with_free_func(g_object_unref);
336+
for (size_t i = 0; i < layers_count; ++i) {
337+
const FlutterLayer* layer = layers[i];
338+
switch (layer->type) {
339+
case kFlutterLayerContentTypeBackingStore: {
340+
const FlutterBackingStore* backing_store = layer->backing_store;
341+
FlFramebuffer* framebuffer =
342+
FL_FRAMEBUFFER(backing_store->open_gl.framebuffer.user_data);
343+
g_ptr_array_add(framebuffers, g_object_ref(framebuffer));
344+
} break;
345+
case kFlutterLayerContentTypePlatformView: {
346+
// TODO(robert-ancell) Not implemented -
347+
// https://github.com/flutter/flutter/issues/41724
348+
} break;
349+
}
350+
}
351+
352+
GWeakRef* ref = static_cast<GWeakRef*>(
353+
g_hash_table_lookup(priv->views, GINT_TO_POINTER(view_id)));
354+
g_autoptr(FlRenderable) renderable =
355+
ref != nullptr ? FL_RENDERABLE(g_weak_ref_get(ref)) : nullptr;
356+
if (renderable == nullptr) {
357+
return TRUE;
358+
}
359+
360+
if (view_id == flutter::kFlutterImplicitViewId) {
361+
// Store for rendering later
362+
g_hash_table_insert(priv->framebuffers_by_view_id, GINT_TO_POINTER(view_id),
363+
g_ptr_array_ref(framebuffers));
364+
} else {
365+
// Composite into a single framebuffer.
366+
if (framebuffers->len > 1) {
367+
size_t width = 0, height = 0;
368+
369+
for (guint i = 0; i < framebuffers->len; i++) {
370+
FlFramebuffer* framebuffer =
371+
FL_FRAMEBUFFER(g_ptr_array_index(framebuffers, i));
372+
373+
size_t w = fl_framebuffer_get_width(framebuffer);
374+
size_t h = fl_framebuffer_get_height(framebuffer);
375+
if (w > width) {
376+
width = w;
377+
}
378+
if (h > height) {
379+
height = h;
380+
}
381+
}
382+
383+
FlFramebuffer* view_framebuffer =
384+
fl_framebuffer_new(priv->general_format, width, height);
385+
glBindFramebuffer(GL_DRAW_FRAMEBUFFER,
386+
fl_framebuffer_get_id(view_framebuffer));
387+
render(self, framebuffers, width, height);
388+
g_ptr_array_set_size(framebuffers, 0);
389+
g_ptr_array_add(framebuffers, view_framebuffer);
390+
}
391+
392+
// Read back pixel values.
393+
FlFramebuffer* framebuffer =
394+
FL_FRAMEBUFFER(g_ptr_array_index(framebuffers, 0));
395+
size_t width = fl_framebuffer_get_width(framebuffer);
396+
size_t height = fl_framebuffer_get_height(framebuffer);
397+
size_t data_length = width * height * 4;
398+
g_autofree uint8_t* data = static_cast<uint8_t*>(malloc(data_length));
399+
glBindFramebuffer(GL_READ_FRAMEBUFFER, fl_framebuffer_get_id(framebuffer));
400+
glReadPixels(0, 0, width, height, priv->general_format, GL_UNSIGNED_BYTE,
401+
data);
402+
403+
// Write into a texture in the views context.
404+
fl_renderable_make_current(renderable);
405+
FlFramebuffer* view_framebuffer =
406+
fl_framebuffer_new(priv->general_format, width, height);
407+
glBindFramebuffer(GL_DRAW_FRAMEBUFFER,
408+
fl_framebuffer_get_id(view_framebuffer));
409+
glBindTexture(GL_TEXTURE_2D,
410+
fl_framebuffer_get_texture_id(view_framebuffer));
411+
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA,
412+
GL_UNSIGNED_BYTE, data);
413+
414+
g_autoptr(GPtrArray) secondary_framebuffers =
415+
g_ptr_array_new_with_free_func(g_object_unref);
416+
g_ptr_array_add(secondary_framebuffers, g_object_ref(view_framebuffer));
417+
g_hash_table_insert(priv->framebuffers_by_view_id, GINT_TO_POINTER(view_id),
418+
g_ptr_array_ref(secondary_framebuffers));
419+
}
420+
421+
fl_renderable_redraw(renderable);
422+
423+
return TRUE;
424+
}
425+
426+
typedef struct {
427+
FlRenderer* self;
428+
429+
FlutterViewId view_id;
430+
431+
const FlutterLayer** layers;
432+
size_t layers_count;
433+
434+
gboolean result;
435+
436+
gboolean finished;
437+
} PresentLayersData;
438+
439+
// Perform the present on the main thread.
440+
static void present_layers_task_cb(gpointer user_data) {
441+
PresentLayersData* data = static_cast<PresentLayersData*>(user_data);
442+
FlRendererPrivate* priv = reinterpret_cast<FlRendererPrivate*>(
443+
fl_renderer_get_instance_private(data->self));
444+
445+
// Perform the present.
446+
fl_renderer_make_current(data->self);
447+
data->result = present_layers(data->self, data->view_id, data->layers,
448+
data->layers_count);
449+
fl_renderer_clear_current(data->self);
450+
451+
// Complete fl_renderer_present_layers().
452+
g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&priv->present_mutex);
453+
data->finished = TRUE;
454+
g_cond_signal(&priv->present_condition);
455+
}
456+
304457
static void fl_renderer_dispose(GObject* object) {
305458
FlRenderer* self = FL_RENDERER(object);
306459
FlRendererPrivate* priv = reinterpret_cast<FlRendererPrivate*>(
@@ -311,6 +464,8 @@ static void fl_renderer_dispose(GObject* object) {
311464
g_weak_ref_clear(&priv->engine);
312465
g_clear_pointer(&priv->views, g_hash_table_unref);
313466
g_clear_pointer(&priv->framebuffers_by_view_id, g_hash_table_unref);
467+
g_mutex_clear(&priv->present_mutex);
468+
g_cond_clear(&priv->present_condition);
314469

315470
G_OBJECT_CLASS(fl_renderer_parent_class)->dispose(object);
316471
}
@@ -327,6 +482,8 @@ static void fl_renderer_init(FlRenderer* self) {
327482
priv->framebuffers_by_view_id = g_hash_table_new_full(
328483
g_direct_hash, g_direct_equal, nullptr,
329484
reinterpret_cast<GDestroyNotify>(g_ptr_array_unref));
485+
g_mutex_init(&priv->present_mutex);
486+
g_cond_init(&priv->present_condition);
330487
}
331488

332489
void fl_renderer_set_engine(FlRenderer* self, FlEngine* engine) {
@@ -454,114 +611,37 @@ gboolean fl_renderer_present_layers(FlRenderer* self,
454611
FlutterViewId view_id,
455612
const FlutterLayer** layers,
456613
size_t layers_count) {
614+
// Detach the context from raster thread. Needed because blitting
615+
// will be done on the main thread, which will make the context current.
616+
fl_renderer_clear_current(self);
617+
457618
FlRendererPrivate* priv = reinterpret_cast<FlRendererPrivate*>(
458619
fl_renderer_get_instance_private(self));
620+
g_autoptr(FlEngine) engine = FL_ENGINE(g_weak_ref_get(&priv->engine));
621+
622+
// Schedule the present to run on the main thread.
623+
FlTaskRunner* task_runner = fl_engine_get_task_runner(engine);
624+
PresentLayersData data = {
625+
.self = self,
626+
.view_id = view_id,
627+
.layers = layers,
628+
.layers_count = layers_count,
629+
.result = FALSE,
630+
.finished = FALSE,
631+
};
632+
fl_task_runner_post_callback(task_runner, present_layers_task_cb, &data);
459633

460-
g_return_val_if_fail(FL_IS_RENDERER(self), FALSE);
461-
462-
// ignore incoming frame with wrong dimensions in trivial case with just one
463-
// layer
464-
if (priv->blocking_main_thread && layers_count == 1 &&
465-
layers[0]->offset.x == 0 && layers[0]->offset.y == 0 &&
466-
(layers[0]->size.width != priv->target_width ||
467-
layers[0]->size.height != priv->target_height)) {
468-
return TRUE;
469-
}
470-
471-
priv->had_first_frame = true;
472-
473-
fl_renderer_unblock_main_thread(self);
474-
475-
g_autoptr(GPtrArray) framebuffers =
476-
g_ptr_array_new_with_free_func(g_object_unref);
477-
for (size_t i = 0; i < layers_count; ++i) {
478-
const FlutterLayer* layer = layers[i];
479-
switch (layer->type) {
480-
case kFlutterLayerContentTypeBackingStore: {
481-
const FlutterBackingStore* backing_store = layer->backing_store;
482-
FlFramebuffer* framebuffer =
483-
FL_FRAMEBUFFER(backing_store->open_gl.framebuffer.user_data);
484-
g_ptr_array_add(framebuffers, g_object_ref(framebuffer));
485-
} break;
486-
case kFlutterLayerContentTypePlatformView: {
487-
// TODO(robert-ancell) Not implemented -
488-
// https://github.com/flutter/flutter/issues/41724
489-
} break;
490-
}
491-
}
492-
493-
GWeakRef* ref = static_cast<GWeakRef*>(
494-
g_hash_table_lookup(priv->views, GINT_TO_POINTER(view_id)));
495-
g_autoptr(FlRenderable) renderable =
496-
ref != nullptr ? FL_RENDERABLE(g_weak_ref_get(ref)) : nullptr;
497-
if (renderable == nullptr) {
498-
return TRUE;
499-
}
500-
501-
if (view_id == flutter::kFlutterImplicitViewId) {
502-
// Store for rendering later
503-
g_hash_table_insert(priv->framebuffers_by_view_id, GINT_TO_POINTER(view_id),
504-
g_ptr_array_ref(framebuffers));
505-
} else {
506-
// Composite into a single framebuffer.
507-
if (framebuffers->len > 1) {
508-
size_t width = 0, height = 0;
509-
510-
for (guint i = 0; i < framebuffers->len; i++) {
511-
FlFramebuffer* framebuffer =
512-
FL_FRAMEBUFFER(g_ptr_array_index(framebuffers, i));
513-
514-
size_t w = fl_framebuffer_get_width(framebuffer);
515-
size_t h = fl_framebuffer_get_height(framebuffer);
516-
if (w > width) {
517-
width = w;
518-
}
519-
if (h > height) {
520-
height = h;
521-
}
522-
}
523-
524-
FlFramebuffer* view_framebuffer =
525-
fl_framebuffer_new(priv->general_format, width, height);
526-
glBindFramebuffer(GL_DRAW_FRAMEBUFFER,
527-
fl_framebuffer_get_id(view_framebuffer));
528-
render(self, framebuffers, width, height);
529-
g_ptr_array_set_size(framebuffers, 0);
530-
g_ptr_array_add(framebuffers, view_framebuffer);
531-
}
532-
533-
// Read back pixel values.
534-
FlFramebuffer* framebuffer =
535-
FL_FRAMEBUFFER(g_ptr_array_index(framebuffers, 0));
536-
size_t width = fl_framebuffer_get_width(framebuffer);
537-
size_t height = fl_framebuffer_get_height(framebuffer);
538-
size_t data_length = width * height * 4;
539-
g_autofree uint8_t* data = static_cast<uint8_t*>(malloc(data_length));
540-
glBindFramebuffer(GL_READ_FRAMEBUFFER, fl_framebuffer_get_id(framebuffer));
541-
glReadPixels(0, 0, width, height, priv->general_format, GL_UNSIGNED_BYTE,
542-
data);
543-
544-
// Write into a texture in the views context.
545-
fl_renderable_make_current(renderable);
546-
FlFramebuffer* view_framebuffer =
547-
fl_framebuffer_new(priv->general_format, width, height);
548-
glBindFramebuffer(GL_DRAW_FRAMEBUFFER,
549-
fl_framebuffer_get_id(view_framebuffer));
550-
glBindTexture(GL_TEXTURE_2D,
551-
fl_framebuffer_get_texture_id(view_framebuffer));
552-
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA,
553-
GL_UNSIGNED_BYTE, data);
554-
555-
g_autoptr(GPtrArray) secondary_framebuffers =
556-
g_ptr_array_new_with_free_func(g_object_unref);
557-
g_ptr_array_add(secondary_framebuffers, g_object_ref(view_framebuffer));
558-
g_hash_table_insert(priv->framebuffers_by_view_id, GINT_TO_POINTER(view_id),
559-
g_ptr_array_ref(secondary_framebuffers));
634+
// Block until present completes.
635+
g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&priv->present_mutex);
636+
while (!data.finished) {
637+
g_cond_wait(&priv->present_condition, &priv->present_mutex);
560638
}
561639

562-
fl_renderable_redraw(renderable);
640+
// Restore the context to the raster thread in case the engine needs it
641+
// to do some cleanup.
642+
fl_renderer_make_current(self);
563643

564-
return TRUE;
644+
return data.result;
565645
}
566646

567647
void fl_renderer_setup(FlRenderer* self) {

0 commit comments

Comments
 (0)