Visitar URL original
[Linux] Use fences to synchronize frames between OpenGL contexts (#19… · flutter/flutter@2092785 · GitHub
Skip to content

Commit 2092785

Browse files
[Linux] Use fences to synchronize frames between OpenGL contexts (#192098)
[Linux] Use fences to synchronize frames between OpenGL contexts Frames are rendered in the engine's OpenGL context and read by whoever presents them using a different context, so the rendering has to have completed before the other context uses the texture. This was done by waiting for the rendering to complete on the raster thread, which is correct but stalls that thread until the GPU has caught up, stopping it from rendering the next frame while it waits. Take a fence after the frame is rendered instead, and wait for it where the frame is used: - The subsurface renderer presents using the subsurface's context, which it makes current itself. Make that context wait for the fence, so the wait happens on the GPU and the raster thread doesn't block at all. - Frames drawn by GTK are read on the main thread, and GTK draws them using the window's paint context rather than the one that is current when the frame is handed over. A wait issued there would only order the context it was issued in, so block until the fence is reached. That wait happens on the main thread rather than the raster thread, and by the time the frame is drawn the rendering has normally completed. Fences are an extension, so add fl_opengl_manager_can_fence() to check for it. Drivers without fences wait for the rendering to complete as before. Drivers that support fences but not waiting on the GPU block instead of waiting on the GPU. See #191775
1 parent 368a6dc commit 2092785

14 files changed

Lines changed: 425 additions & 7 deletions

‎engine/src/flutter/shell/platform/linux/BUILD.gn‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ source_set("flutter_linux_sources") {
128128
"fl_engine.cc",
129129
"fl_event_channel.cc",
130130
"fl_framebuffer.cc",
131+
"fl_gl_fence.cc",
131132
"fl_gnome_settings.cc",
132133
"fl_json_message_codec.cc",
133134
"fl_json_method_codec.cc",
@@ -247,6 +248,7 @@ executable("flutter_linux_unittests") {
247248
"fl_engine_test.cc",
248249
"fl_event_channel_test.cc",
249250
"fl_framebuffer_test.cc",
251+
"fl_gl_fence_test.cc",
250252
"fl_gnome_settings_test.cc",
251253
"fl_json_message_codec_test.cc",
252254
"fl_json_method_codec_test.cc",

‎engine/src/flutter/shell/platform/linux/fl_compositor_opengl.cc‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ struct _FlCompositorOpenGL {
2121
// TRUE if glBlitFramebuffer can be used to composite the first layer.
2222
gboolean can_blit;
2323

24+
// TRUE if fences can be used to synchronize frames with other contexts.
25+
gboolean can_fence;
26+
2427
// Flutter OpenGL contexts.
2528
FlOpenGLManager* opengl_manager;
2629

@@ -55,6 +58,7 @@ FlCompositorOpenGL* fl_compositor_opengl_new(FlOpenGLManager* opengl_manager) {
5558
// Determine once whether glBlitFramebuffer is available on this driver.
5659
fl_opengl_manager_make_current(opengl_manager);
5760
self->can_blit = fl_opengl_manager_can_blit(opengl_manager);
61+
self->can_fence = fl_opengl_manager_can_fence(opengl_manager);
5862

5963
return self;
6064
}
@@ -183,6 +187,17 @@ void fl_compositor_opengl_composite_layers(FlCompositorOpenGL* self,
183187
saved_dst_alpha);
184188
}
185189

190+
FlOpenGLManager* fl_compositor_opengl_get_opengl_manager(
191+
FlCompositorOpenGL* self) {
192+
g_return_val_if_fail(FL_IS_COMPOSITOR_OPENGL(self), nullptr);
193+
return self->opengl_manager;
194+
}
195+
196+
gboolean fl_compositor_opengl_can_fence(FlCompositorOpenGL* self) {
197+
g_return_val_if_fail(FL_IS_COMPOSITOR_OPENGL(self), FALSE);
198+
return self->can_fence;
199+
}
200+
186201
GLint fl_compositor_opengl_get_frame_format(const FlutterLayer** layers,
187202
size_t layers_count) {
188203
// Every backing store in a frame is created by the same engine code from

‎engine/src/flutter/shell/platform/linux/fl_compositor_opengl.h‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,29 @@ G_DECLARE_FINAL_TYPE(FlCompositorOpenGL,
3939
*/
4040
FlCompositorOpenGL* fl_compositor_opengl_new(FlOpenGLManager* opengl_manager);
4141

42+
/**
43+
* fl_compositor_opengl_get_opengl_manager:
44+
* @compositor: an #FlCompositorOpenGL.
45+
*
46+
* Gets the OpenGL manager the frames are composited with.
47+
*
48+
* Returns: an #FlOpenGLManager.
49+
*/
50+
FlOpenGLManager* fl_compositor_opengl_get_opengl_manager(
51+
FlCompositorOpenGL* compositor);
52+
53+
/**
54+
* fl_compositor_opengl_can_fence:
55+
* @compositor: an #FlCompositorOpenGL.
56+
*
57+
* Checks whether fences can be used to synchronize a composited frame with the
58+
* context that presents it. Callers have to fall back to waiting for the
59+
* rendering to complete, e.g. with glFinish(), when this returns %FALSE.
60+
*
61+
* Returns: %TRUE if fences are available.
62+
*/
63+
gboolean fl_compositor_opengl_can_fence(FlCompositorOpenGL* compositor);
64+
4265
/**
4366
* fl_compositor_opengl_composite_layers:
4467
* @compositor: an #FlCompositorOpenGL.
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#include "fl_gl_fence.h"
6+
7+
#include <epoxy/egl.h>
8+
#include <epoxy/gl.h>
9+
10+
// How long to wait for a fence before giving up on it. Reaching this means the
11+
// driver has taken an unreasonable amount of time to render a frame; carry on
12+
// and use the frame rather than blocking the thread indefinitely.
13+
static constexpr EGLTimeKHR kFenceTimeoutNanoseconds = 1000000000; // 1s
14+
15+
struct _FlGLFence {
16+
GObject parent_instance;
17+
18+
// Display [sync] was created on, stored so the fence can be destroyed without
19+
// a context being current.
20+
EGLDisplay display;
21+
22+
EGLSyncKHR sync;
23+
24+
// TRUE if OpenGL can wait for the fence, i.e. without blocking this thread.
25+
gboolean can_wait_sync;
26+
};
27+
28+
G_DEFINE_TYPE(FlGLFence, fl_gl_fence, G_TYPE_OBJECT)
29+
30+
static void fl_gl_fence_dispose(GObject* object) {
31+
FlGLFence* self = FL_GL_FENCE(object);
32+
33+
if (self->sync != EGL_NO_SYNC_KHR) {
34+
eglDestroySyncKHR(self->display, self->sync);
35+
self->sync = EGL_NO_SYNC_KHR;
36+
}
37+
38+
G_OBJECT_CLASS(fl_gl_fence_parent_class)->dispose(object);
39+
}
40+
41+
static void fl_gl_fence_class_init(FlGLFenceClass* klass) {
42+
G_OBJECT_CLASS(klass)->dispose = fl_gl_fence_dispose;
43+
}
44+
45+
static void fl_gl_fence_init(FlGLFence* self) {
46+
self->display = EGL_NO_DISPLAY;
47+
self->sync = EGL_NO_SYNC_KHR;
48+
}
49+
50+
FlGLFence* fl_gl_fence_new(FlOpenGLManager* opengl_manager) {
51+
g_return_val_if_fail(FL_IS_OPENGL_MANAGER(opengl_manager), nullptr);
52+
53+
FlGLFence* self = FL_GL_FENCE(g_object_new(fl_gl_fence_get_type(), nullptr));
54+
55+
EGLDisplay display = fl_opengl_manager_get_display(opengl_manager);
56+
EGLSyncKHR sync = eglCreateSyncKHR(display, EGL_SYNC_FENCE_KHR, nullptr);
57+
// Creating a fence only adds it to the command stream; the commands have to
58+
// be flushed for it to be reached, otherwise waiting on it can never return.
59+
glFlush();
60+
if (sync == EGL_NO_SYNC_KHR) {
61+
g_warning("Failed to create fence");
62+
return self;
63+
}
64+
65+
self->display = display;
66+
self->sync = sync;
67+
self->can_wait_sync = fl_opengl_manager_can_wait_sync(opengl_manager);
68+
69+
return self;
70+
}
71+
72+
// Blocks the calling thread until the fence is reached.
73+
static void client_wait(FlGLFence* self) {
74+
// The commands were flushed when the fence was created, so they don't need to
75+
// be flushed again here. Flushing wouldn't work anyway, as that only applies
76+
// to a fence created in the context current on this thread.
77+
EGLint result = eglClientWaitSyncKHR(self->display, self->sync, 0,
78+
kFenceTimeoutNanoseconds);
79+
if (result == EGL_TIMEOUT_EXPIRED_KHR) {
80+
g_warning("Timed out waiting for OpenGL rendering to complete");
81+
}
82+
}
83+
84+
void fl_gl_fence_wait(FlGLFence* self) {
85+
g_return_if_fail(FL_IS_GL_FENCE(self));
86+
87+
// Nothing to wait for if the fence couldn't be created.
88+
if (self->sync == EGL_NO_SYNC_KHR) {
89+
return;
90+
}
91+
92+
if (!self->can_wait_sync) {
93+
client_wait(self);
94+
return;
95+
}
96+
97+
eglWaitSyncKHR(self->display, self->sync, 0);
98+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_GL_FENCE_H_
6+
#define FLUTTER_SHELL_PLATFORM_LINUX_FL_GL_FENCE_H_
7+
8+
#include <epoxy/egl.h>
9+
#include <glib-object.h>
10+
11+
#include "flutter/shell/platform/linux/fl_opengl_manager.h"
12+
13+
G_BEGIN_DECLS
14+
15+
G_DECLARE_FINAL_TYPE(FlGLFence, fl_gl_fence, FL, GL_FENCE, GObject)
16+
17+
/**
18+
* FlGLFence:
19+
*
20+
* #FlGLFence marks a point in the OpenGL command stream that can be waited on,
21+
* which is how rendering done in one context is made visible to another.
22+
* Flushing a context only guarantees its commands have been submitted, not that
23+
* they have completed, so a texture rendered in one context can't be used in
24+
* another until a fence taken after the rendering has been reached.
25+
*/
26+
27+
/**
28+
* fl_gl_fence_new:
29+
* @opengl_manager: an #FlOpenGLManager.
30+
*
31+
* Creates a fence that is reached when the commands submitted to the current
32+
* OpenGL context so far have completed. Requires the context that did the
33+
* rendering to be current.
34+
*
35+
* Only use this if fl_opengl_manager_can_fence() returns %TRUE; drivers without
36+
* fences have to fall back to waiting for the rendering to complete, e.g. with
37+
* glFinish().
38+
*
39+
* If the fence can't be created a warning is generated and an #FlGLFence that
40+
* is not waited for is returned.
41+
*
42+
* Returns: a new #FlGLFence.
43+
*/
44+
FlGLFence* fl_gl_fence_new(FlOpenGLManager* opengl_manager);
45+
46+
/**
47+
* fl_gl_fence_wait:
48+
* @fence: an #FlGLFence.
49+
*
50+
* Makes the current OpenGL context wait for @fence before executing any
51+
* commands submitted after this point. Requires the context that is going to
52+
* use the rendered texture to be current.
53+
*
54+
* This doesn't block the calling thread, the waiting is done by OpenGL. Falls
55+
* back to blocking the calling thread if the driver can't wait this way.
56+
*/
57+
void fl_gl_fence_wait(FlGLFence* fence);
58+
59+
G_END_DECLS
60+
61+
#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_GL_FENCE_H_
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#include "gtest/gtest.h"
6+
7+
#include "flutter/shell/platform/linux/fl_gl_fence.h"
8+
#include "flutter/shell/platform/linux/testing/linux_test.h"
9+
#include "flutter/shell/platform/linux/testing/mock_epoxy.h"
10+
11+
class FlGLFenceTest : public flutter::testing::LinuxTest {
12+
protected:
13+
void SetUp() override { opengl_manager = fl_opengl_manager_new(); }
14+
15+
~FlGLFenceTest() override { g_clear_object(&opengl_manager); }
16+
17+
::testing::NiceMock<flutter::testing::MockEpoxy> epoxy;
18+
FlOpenGLManager* opengl_manager = nullptr;
19+
};
20+
21+
// A fence is created in the current context, and the commands are flushed so
22+
// that it can actually be reached.
23+
TEST_F(FlGLFenceTest, New) {
24+
EXPECT_CALL(epoxy,
25+
eglCreateSyncKHR(::testing::_, EGL_SYNC_FENCE_KHR, ::testing::_));
26+
27+
g_autoptr(FlGLFence) fence = fl_gl_fence_new(opengl_manager);
28+
EXPECT_NE(fence, nullptr);
29+
}
30+
31+
// Drivers without fence support are reported so the caller can fall back to
32+
// another form of synchronization.
33+
// A driver that fails to create a fence still returns an object, which is safe
34+
// to wait on and does nothing.
35+
TEST_F(FlGLFenceTest, NewFails) {
36+
EXPECT_CALL(epoxy, eglCreateSyncKHR)
37+
.WillOnce(::testing::Return(EGL_NO_SYNC_KHR));
38+
39+
g_autoptr(FlGLFence) fence = fl_gl_fence_new(opengl_manager);
40+
ASSERT_NE(fence, nullptr);
41+
42+
EXPECT_CALL(epoxy, eglClientWaitSyncKHR).Times(0);
43+
EXPECT_CALL(epoxy, eglWaitSyncKHR).Times(0);
44+
fl_gl_fence_wait(fence);
45+
}
46+
47+
// OpenGL can do the waiting, which doesn't block the calling thread.
48+
TEST_F(FlGLFenceTest, Wait) {
49+
g_autoptr(FlGLFence) fence = fl_gl_fence_new(opengl_manager);
50+
ASSERT_NE(fence, nullptr);
51+
52+
EXPECT_CALL(epoxy, eglWaitSyncKHR(::testing::_, ::testing::_, 0))
53+
.WillOnce(::testing::Return(EGL_TRUE));
54+
EXPECT_CALL(epoxy, eglClientWaitSyncKHR).Times(0);
55+
56+
fl_gl_fence_wait(fence);
57+
}
58+
59+
// Drivers that can create fences but can't wait on them without blocking fall
60+
// back to blocking, which is slower but still correct.
61+
TEST_F(FlGLFenceTest, WaitUnsupported) {
62+
EXPECT_CALL(epoxy, epoxy_has_egl_extension(
63+
::testing::_, ::testing::StrEq("EGL_KHR_wait_sync")))
64+
.WillRepeatedly(::testing::Return(false));
65+
66+
g_autoptr(FlGLFence) fence = fl_gl_fence_new(opengl_manager);
67+
ASSERT_NE(fence, nullptr);
68+
69+
EXPECT_CALL(epoxy, eglWaitSyncKHR).Times(0);
70+
EXPECT_CALL(epoxy, eglClientWaitSyncKHR)
71+
.WillOnce(::testing::Return(EGL_CONDITION_SATISFIED_KHR));
72+
73+
fl_gl_fence_wait(fence);
74+
}
75+
76+
// The fence is destroyed with the display it was created on, so it doesn't
77+
// depend on a context being current when it is freed.
78+
TEST_F(FlGLFenceTest, Destroy) {
79+
FlGLFence* fence = fl_gl_fence_new(opengl_manager);
80+
ASSERT_NE(fence, nullptr);
81+
82+
EXPECT_CALL(epoxy, eglDestroySyncKHR).WillOnce(::testing::Return(EGL_TRUE));
83+
84+
g_object_unref(fence);
85+
}

‎engine/src/flutter/shell/platform/linux/fl_opengl_manager.cc‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,3 +162,23 @@ gboolean fl_opengl_manager_can_blit(FlOpenGLManager* self) {
162162
(epoxy_gl_version() >= 30 ||
163163
epoxy_has_gl_extension("GL_EXT_framebuffer_blit"));
164164
}
165+
166+
// Checks whether @extension is supported by the EGL display the contexts are
167+
// created on.
168+
static gboolean has_extension(FlOpenGLManager* self, const gchar* extension) {
169+
g_return_val_if_fail(FL_IS_OPENGL_MANAGER(self), FALSE);
170+
171+
if (self->display == EGL_NO_DISPLAY) {
172+
return FALSE;
173+
}
174+
175+
return epoxy_has_egl_extension(self->display, extension);
176+
}
177+
178+
gboolean fl_opengl_manager_can_fence(FlOpenGLManager* self) {
179+
return has_extension(self, "EGL_KHR_fence_sync");
180+
}
181+
182+
gboolean fl_opengl_manager_can_wait_sync(FlOpenGLManager* self) {
183+
return has_extension(self, "EGL_KHR_wait_sync");
184+
}

‎engine/src/flutter/shell/platform/linux/fl_opengl_manager.h‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,32 @@ EGLContext fl_opengl_manager_get_context(FlOpenGLManager* manager);
106106
*/
107107
gboolean fl_opengl_manager_can_blit(FlOpenGLManager* manager);
108108

109+
/**
110+
* fl_opengl_manager_can_fence:
111+
* @manager: an #FlOpenGLManager.
112+
*
113+
* Checks whether fences can be used to synchronize rendering between OpenGL
114+
* contexts. Fences are an extension that may be missing on some drivers, so
115+
* callers should fall back to waiting for the rendering to complete, e.g. with
116+
* glFinish(), when this returns %FALSE.
117+
*
118+
* Returns: %TRUE if fences are available.
119+
*/
120+
gboolean fl_opengl_manager_can_fence(FlOpenGLManager* manager);
121+
122+
/**
123+
* fl_opengl_manager_can_wait_sync:
124+
* @manager: an #FlOpenGLManager.
125+
*
126+
* Checks whether a fence can be waited for by the GPU, i.e. without blocking
127+
* the thread doing the waiting. This is an extension that may be missing on
128+
* some drivers, so callers should fall back to blocking when this returns
129+
* %FALSE.
130+
*
131+
* Returns: %TRUE if fences can be waited for without blocking.
132+
*/
133+
gboolean fl_opengl_manager_can_wait_sync(FlOpenGLManager* manager);
134+
109135
G_END_DECLS
110136

111137
#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_OPENGL_MANAGER_H_

0 commit comments

Comments
 (0)