Conversation

knopp

Fixes #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

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

@flutter-dashboard

This comment was marked as resolved.

@github-actions-actions bot added engineflutter/engine repository. See also e: labels.platform-linuxBuilding on or for Linux specificallya: desktopRunning on desktoplabels Jan 19, 2025
@knoppknopp force-pushed the linux_raster_thread branch from c26b872 to 8703939 Compare January 19, 2025 13:37
@robert-ancellrobert-ancell self-requested a review January 20, 2025 01:54

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I understand it, this switches from the Flutter engine scheduling the render and platform threads into a single thread to us doing the same thing in the Linux embedder. So it should be the same result in terms of performance. We can't do the rendering directly into GTK, so there's no scope for performance improvement that other platforms might have.

So the only value I can see is that we can then remove than functionality from the Flutter engine at the cost of some additional complexity here. Is there any push to remove this from the engine?

As noted, I think this can be simplified if we use the GTK APIs for scheduling on the main thread.

@knopp

As I understand it, this switches from the Flutter engine scheduling the render and platform threads into a single thread to us doing the same thing in the Linux embedder. So it should be the same result in terms of performance. We can't do the rendering directly into GTK, so there's no scope for performance improvement that other platforms might have.

So the only value I can see is that we can then remove than functionality from the Flutter engine at the cost of some additional complexity here. Is there any push to remove this from the engine?

As noted, I think this can be simplified if we use the GTK APIs for scheduling on the main thread.

Not quite. When the runner is not specified, the linux embedder will by default use separate raster thread (different from the platform thread). The result of this PR is a separate raster thread, just like with any other embedder.

With this PR the actual Flutter rendering is done on raster thread, but the final blit (fl_renderer_present_layers) is performed on main thread. This works because as mentioned above gdk_gl_context_make_current, once the context is realized, is just a thin wrapper around glXMakeContextCurrent and eglMakeCurrent without any problematic sideffects.

Gtk APIs for scheduling on main thread can not be used here because of special task runner mode used when waiting for frame during resizing (introduced in flutter/engine#25884 to prevent wobbling during resizing).

I wanted to document this in the PR better before requesting review, but you seem to have beaten me to it :)

@robert-ancell

Thanks for the clarifications.

@robert-ancell

I've allocated some time to look at this next week.

@knoppknopp force-pushed the linux_raster_thread branch from 2499bd7 to e04098a Compare February 4, 2025 12:33
@knoppknopp mentioned this pull request Feb 4, 2025
9 tasks

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks again for working on this.

I'm a bit concerned about the complexity this change is adding, and it makes me think there are cleaner ways of solving this problem. This flows on from the smooth resizing work which I think also needs revisiting.

I think the solution to the above is in documentation, which will either indicate better ways of solving this or make it clearer to understand. The best way would be to write a design doc with the whole interaction between Flutter, GTK and threads laid out.

I don't want to be discouraging, however I feel we've already collected some inappropriate complexity in the Linux embedder that has already been expensive to modify and delayed the multi-window work, so I don't want to make that worse.

If we were to land this now, and roll the complexity in with the resizing issue the question is: is this feature worth taking now and paying the debt down later or better to wait and rethink?

@robert-ancell

@mattkae would love if you could have a look at this for a second opinion, thanks!

@knopp

This is prerequisite to be able to have UI isolate running on platform thread. The major implication of that is that you can call Gtk methods directly and synchronously, as well as calling dart code synchronously from Gtk callbacks for example, without having to go through platform channels. More information on that in #150525.

While the complexity is certainly there, it's not that much different to what the macOS and Windows embedders are doing. I'll be more than happy to document the flow, as well as assist with any issues you'll have while implementing multi view.

I think that especially for multiwindow, which will introduce decent chunk of API surface that interacts with the host operating system, being able to do this directly and synchronously from dart has major advantages.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some nit picks and a question around some indirection, but looks good :)

Comment on lines 437 to 440
// Executes the given function on the main thread and blocks until it completes.
static void fl_renderer_call_blocking(FlRenderer* self,
void (*callback)(gpointer data),
gpointer callback_data) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it is only used once, I find the fact that this function is generic to be a bit of an unnecessary indirection. Could we instead just provide:

Suggested change
// Executes the given function on the main thread and blocks until it completes.
static void fl_renderer_call_blocking(FlRenderer* self,
void (*callback)(gpointer data),
gpointer callback_data) {
// Presents layers on the main thread with the provided data and waits for
// the presentation to complete before calling [fl_renderer_present_layers_trampoline]
static void fl_renderer_present_layers_blocking(FlRenderer* self,,
_FlRendererPresentLayersData* callback_data) {

And then always pass fl_renderer_present_layers_trampoline as the callback in _FlRendererCallBlockingTrampolineData?

@knoppknopp force-pushed the linux_raster_thread branch from e04098a to 8e8f4ac Compare February 11, 2025 22:24
@knopp

I've simplified the changes toFlRenderer quite a bit. There is only one spot where we need to synchronize threads so the code before was unnecessary generic and hard to follow. There is now only

fl_renderer_present_layers:
called on raster thread, schedules fl_renderer_present_layers_trampoline on main thread and blocks raster thread.

fl_renderer_present_layers_trampoline:
calls fl_renderer_present_layers_on_main_thread and unblocks raster thread.

@knopp

@mattkae, @robert-ancell, this should be now ready for another pass.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will leave it to @robert-ancell to take it over the line. I have one question but I am out until Tuesday after today 😄

@robert-ancell

I'm currently working on documenting the Linux embedder threading model, so we can see how this changes things through that.

@robert-ancell

An update on playing around with the theading model:

I think the "GTK way" of doing this is to use a main loop in the render callback, and thus keep processing all events avoiding any blocking in the GTK thread. In pseudocode:

// Called by Flutter when we have an updated frame
present_cb() {
  if (in_render_cb_loop) {
    render_frame()
    loop.quit()
  } else {
    store_frame_for_later()
  }
}

// Called by GTK when it renders the window.
render_cb() {
  if (have_frame_to_render) {
    render_stored_frame()
  } else {
    loop = MainLoop()
    loop.run()
  }
}

I have the above working in a hacky branch, but for some reason there is glitching when resizing the window. I haven't yet been able to determine why this is.

@knopp

@robert-ancell, I'm a little bit confused. Your branch doesn't have separate raster thread so what exactly does it solve? If we don't have separate raster thread then we can't have merged UI/platform thread without performance degradation.

EDIT: I guess the separate raster thread was not really the point of the hacky branch?

Also, have you find any issue with the model used in this PR? This is basically what the other two desktop embedders do, are you experiencing any problems with it? It moves the rendering to separate thread and resize works as expected, on both X11 and wayland.

@knopp
// Called by GTK when it renders the window.
render_cb() {
  if (have_frame_to_render) {
    render_stored_frame()
  } else {
    loop = MainLoop()
    loop.run()
  }
}

We don't do that currently (rendering frame from within present callback), does it cause any issues (missed frame updates with compositor, etc)?

@knopp

This comment was marked as outdated.

@knopp

It seems to me that GtkWidget render is simply a result of GdkFrameClock paint event. Is there something inherently special about presenting the surfaces while the paint event is in progress? I don't quite see the connection here.

Now we can definitely improve frame pacing on linux. We don't even have custom vsync waiter right now. But it seems to me like that would be driven through GdkFrameClock in a way to ensure that we present the surfaces in a correct frame phase so that the compositor picks it up. But that also seems somewhat orthogonal to this PR.

@knopp

I tested the hacky branch, the reason why resizing is wobbly on wayland is that during resizing wayland events are dised while running the inner event loop. You can test this by disabling wayland event dis while running nested loop, i.e. add the following code

  if (g_main_depth() > 1) {
    return;
  }

into _gdk_wayland_display_queue_events (GdkDisplay *display).

That will fix the resizing wobbliness. Of course it's just a hack. This is the main reason we don't pump the Gtk main loop while waiting for frame during resizing.

engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 14, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 15, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 15, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 16, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 16, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 16, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 17, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 20, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 25, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 25, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 26, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 26, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 26, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 26, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 26, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 26, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 26, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 27, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 27, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 27, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 27, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 27, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 27, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 27, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 27, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 28, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request May 20, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request May 20, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request May 21, 2025
Sign up for free to join this conversation on . Already have an account? Sign in to comment
a: desktopRunning on desktopengineflutter/engine repository. See also e: labels.platform-linuxBuilding on or for Linux specifically
None yet

Successfully merging this pull request may close these issues.

Split platform and render threads on Linux