Visitar URL original
[Linux] Use fences to synchronize frames between OpenGL contexts by robert-ancell · Pull Request #192098 · flutter/flutter · GitHub
Skip to content
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
2 changes: 2 additions & 0 deletions engine/src/flutter/shell/platform/linux/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ source_set("flutter_linux_sources") {
"fl_engine.cc",
"fl_event_channel.cc",
"fl_framebuffer.cc",
"fl_gl_fence.cc",
"fl_gnome_settings.cc",
"fl_json_message_codec.cc",
"fl_json_method_codec.cc",
Expand Down Expand Up @@ -247,6 +248,7 @@ executable("flutter_linux_unittests") {
"fl_engine_test.cc",
"fl_event_channel_test.cc",
"fl_framebuffer_test.cc",
"fl_gl_fence_test.cc",
"fl_gnome_settings_test.cc",
"fl_json_message_codec_test.cc",
"fl_json_method_codec_test.cc",
Expand Down
15 changes: 15 additions & 0 deletions engine/src/flutter/shell/platform/linux/fl_compositor_opengl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ struct _FlCompositorOpenGL {
// TRUE if glBlitFramebuffer can be used to composite the first layer.
gboolean can_blit;

// TRUE if fences can be used to synchronize frames with other contexts.
gboolean can_fence;

// Flutter OpenGL contexts.
FlOpenGLManager* opengl_manager;

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

return self;
}
Expand Down Expand Up @@ -183,6 +187,17 @@ void fl_compositor_opengl_composite_layers(FlCompositorOpenGL* self,
saved_dst_alpha);
}

FlOpenGLManager* fl_compositor_opengl_get_opengl_manager(
FlCompositorOpenGL* self) {
g_return_val_if_fail(FL_IS_COMPOSITOR_OPENGL(self), nullptr);
return self->opengl_manager;
}

gboolean fl_compositor_opengl_can_fence(FlCompositorOpenGL* self) {
g_return_val_if_fail(FL_IS_COMPOSITOR_OPENGL(self), FALSE);
return self->can_fence;
}

GLint fl_compositor_opengl_get_frame_format(const FlutterLayer** layers,
size_t layers_count) {
// Every backing store in a frame is created by the same engine code from
Expand Down
23 changes: 23 additions & 0 deletions engine/src/flutter/shell/platform/linux/fl_compositor_opengl.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,29 @@ G_DECLARE_FINAL_TYPE(FlCompositorOpenGL,
*/
FlCompositorOpenGL* fl_compositor_opengl_new(FlOpenGLManager* opengl_manager);

/**
* fl_compositor_opengl_get_opengl_manager:
* @compositor: an #FlCompositorOpenGL.
*
* Gets the OpenGL manager the frames are composited with.
*
* Returns: an #FlOpenGLManager.
*/
FlOpenGLManager* fl_compositor_opengl_get_opengl_manager(
FlCompositorOpenGL* compositor);

/**
* fl_compositor_opengl_can_fence:
* @compositor: an #FlCompositorOpenGL.
*
* Checks whether fences can be used to synchronize a composited frame with the
* context that presents it. Callers have to fall back to waiting for the
* rendering to complete, e.g. with glFinish(), when this returns %FALSE.
*
* Returns: %TRUE if fences are available.
*/
gboolean fl_compositor_opengl_can_fence(FlCompositorOpenGL* compositor);

/**
* fl_compositor_opengl_composite_layers:
* @compositor: an #FlCompositorOpenGL.
Expand Down
98 changes: 98 additions & 0 deletions engine/src/flutter/shell/platform/linux/fl_gl_fence.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "fl_gl_fence.h"

#include <epoxy/egl.h>
#include <epoxy/gl.h>

// How long to wait for a fence before giving up on it. Reaching this means the
// driver has taken an unreasonable amount of time to render a frame; carry on
// and use the frame rather than blocking the thread indefinitely.
static constexpr EGLTimeKHR kFenceTimeoutNanoseconds = 1000000000; // 1s

struct _FlGLFence {
GObject parent_instance;

// Display [sync] was created on, stored so the fence can be destroyed without
// a context being current.
EGLDisplay display;

EGLSyncKHR sync;

// TRUE if OpenGL can wait for the fence, i.e. without blocking this thread.
gboolean can_wait_sync;
};

G_DEFINE_TYPE(FlGLFence, fl_gl_fence, G_TYPE_OBJECT)

static void fl_gl_fence_dispose(GObject* object) {
FlGLFence* self = FL_GL_FENCE(object);

if (self->sync != EGL_NO_SYNC_KHR) {
eglDestroySyncKHR(self->display, self->sync);
self->sync = EGL_NO_SYNC_KHR;
}

G_OBJECT_CLASS(fl_gl_fence_parent_class)->dispose(object);
}

static void fl_gl_fence_class_init(FlGLFenceClass* klass) {
G_OBJECT_CLASS(klass)->dispose = fl_gl_fence_dispose;
}

static void fl_gl_fence_init(FlGLFence* self) {
self->display = EGL_NO_DISPLAY;
self->sync = EGL_NO_SYNC_KHR;
}

FlGLFence* fl_gl_fence_new(FlOpenGLManager* opengl_manager) {
g_return_val_if_fail(FL_IS_OPENGL_MANAGER(opengl_manager), nullptr);

FlGLFence* self = FL_GL_FENCE(g_object_new(fl_gl_fence_get_type(), nullptr));
Comment thread
robert-ancell marked this conversation as resolved.
Resolved

EGLDisplay display = fl_opengl_manager_get_display(opengl_manager);
EGLSyncKHR sync = eglCreateSyncKHR(display, EGL_SYNC_FENCE_KHR, nullptr);
// Creating a fence only adds it to the command stream; the commands have to
// be flushed for it to be reached, otherwise waiting on it can never return.
glFlush();
if (sync == EGL_NO_SYNC_KHR) {
g_warning("Failed to create fence");
return self;
}

self->display = display;
self->sync = sync;
self->can_wait_sync = fl_opengl_manager_can_wait_sync(opengl_manager);

return self;
}

// Blocks the calling thread until the fence is reached.
static void client_wait(FlGLFence* self) {
// The commands were flushed when the fence was created, so they don't need to
// be flushed again here. Flushing wouldn't work anyway, as that only applies
// to a fence created in the context current on this thread.
EGLint result = eglClientWaitSyncKHR(self->display, self->sync, 0,
kFenceTimeoutNanoseconds);
if (result == EGL_TIMEOUT_EXPIRED_KHR) {
g_warning("Timed out waiting for OpenGL rendering to complete");
}
}

void fl_gl_fence_wait(FlGLFence* self) {
g_return_if_fail(FL_IS_GL_FENCE(self));

// Nothing to wait for if the fence couldn't be created.
if (self->sync == EGL_NO_SYNC_KHR) {
return;
}

if (!self->can_wait_sync) {
client_wait(self);
return;
}

eglWaitSyncKHR(self->display, self->sync, 0);
}
61 changes: 61 additions & 0 deletions engine/src/flutter/shell/platform/linux/fl_gl_fence.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_GL_FENCE_H_
#define FLUTTER_SHELL_PLATFORM_LINUX_FL_GL_FENCE_H_

#include <epoxy/egl.h>
#include <glib-object.h>

#include "flutter/shell/platform/linux/fl_opengl_manager.h"

G_BEGIN_DECLS

G_DECLARE_FINAL_TYPE(FlGLFence, fl_gl_fence, FL, GL_FENCE, GObject)

/**
* FlGLFence:
*
* #FlGLFence marks a point in the OpenGL command stream that can be waited on,
* which is how rendering done in one context is made visible to another.
* Flushing a context only guarantees its commands have been submitted, not that
* they have completed, so a texture rendered in one context can't be used in
* another until a fence taken after the rendering has been reached.
*/

/**
* fl_gl_fence_new:
* @opengl_manager: an #FlOpenGLManager.
*
* Creates a fence that is reached when the commands submitted to the current
* OpenGL context so far have completed. Requires the context that did the
* rendering to be current.
*
* Only use this if fl_opengl_manager_can_fence() returns %TRUE; drivers without
* fences have to fall back to waiting for the rendering to complete, e.g. with
* glFinish().
*
* If the fence can't be created a warning is generated and an #FlGLFence that
* is not waited for is returned.
*
* Returns: a new #FlGLFence.
*/
FlGLFence* fl_gl_fence_new(FlOpenGLManager* opengl_manager);

/**
* fl_gl_fence_wait:
* @fence: an #FlGLFence.
*
* Makes the current OpenGL context wait for @fence before executing any
* commands submitted after this point. Requires the context that is going to
* use the rendered texture to be current.
*
* This doesn't block the calling thread, the waiting is done by OpenGL. Falls
* back to blocking the calling thread if the driver can't wait this way.
*/
void fl_gl_fence_wait(FlGLFence* fence);

G_END_DECLS

#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_GL_FENCE_H_
85 changes: 85 additions & 0 deletions engine/src/flutter/shell/platform/linux/fl_gl_fence_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "gtest/gtest.h"

#include "flutter/shell/platform/linux/fl_gl_fence.h"
#include "flutter/shell/platform/linux/testing/linux_test.h"
#include "flutter/shell/platform/linux/testing/mock_epoxy.h"

class FlGLFenceTest : public flutter::testing::LinuxTest {
protected:
void SetUp() override { opengl_manager = fl_opengl_manager_new(); }

~FlGLFenceTest() override { g_clear_object(&opengl_manager); }

::testing::NiceMock<flutter::testing::MockEpoxy> epoxy;
FlOpenGLManager* opengl_manager = nullptr;
};

// A fence is created in the current context, and the commands are flushed so
// that it can actually be reached.
TEST_F(FlGLFenceTest, New) {
EXPECT_CALL(epoxy,
eglCreateSyncKHR(::testing::_, EGL_SYNC_FENCE_KHR, ::testing::_));

g_autoptr(FlGLFence) fence = fl_gl_fence_new(opengl_manager);
EXPECT_NE(fence, nullptr);
}

// Drivers without fence support are reported so the caller can fall back to
// another form of synchronization.
// A driver that fails to create a fence still returns an object, which is safe
// to wait on and does nothing.
TEST_F(FlGLFenceTest, NewFails) {
EXPECT_CALL(epoxy, eglCreateSyncKHR)
.WillOnce(::testing::Return(EGL_NO_SYNC_KHR));

g_autoptr(FlGLFence) fence = fl_gl_fence_new(opengl_manager);
ASSERT_NE(fence, nullptr);

EXPECT_CALL(epoxy, eglClientWaitSyncKHR).Times(0);
EXPECT_CALL(epoxy, eglWaitSyncKHR).Times(0);
fl_gl_fence_wait(fence);
}

// OpenGL can do the waiting, which doesn't block the calling thread.
TEST_F(FlGLFenceTest, Wait) {
g_autoptr(FlGLFence) fence = fl_gl_fence_new(opengl_manager);
ASSERT_NE(fence, nullptr);

EXPECT_CALL(epoxy, eglWaitSyncKHR(::testing::_, ::testing::_, 0))
.WillOnce(::testing::Return(EGL_TRUE));
EXPECT_CALL(epoxy, eglClientWaitSyncKHR).Times(0);

fl_gl_fence_wait(fence);
}

// Drivers that can create fences but can't wait on them without blocking fall
// back to blocking, which is slower but still correct.
TEST_F(FlGLFenceTest, WaitUnsupported) {
EXPECT_CALL(epoxy, epoxy_has_egl_extension(
::testing::_, ::testing::StrEq("EGL_KHR_wait_sync")))
.WillRepeatedly(::testing::Return(false));

g_autoptr(FlGLFence) fence = fl_gl_fence_new(opengl_manager);
ASSERT_NE(fence, nullptr);

EXPECT_CALL(epoxy, eglWaitSyncKHR).Times(0);
EXPECT_CALL(epoxy, eglClientWaitSyncKHR)
.WillOnce(::testing::Return(EGL_CONDITION_SATISFIED_KHR));

fl_gl_fence_wait(fence);
}

// The fence is destroyed with the display it was created on, so it doesn't
// depend on a context being current when it is freed.
TEST_F(FlGLFenceTest, Destroy) {
FlGLFence* fence = fl_gl_fence_new(opengl_manager);
ASSERT_NE(fence, nullptr);

EXPECT_CALL(epoxy, eglDestroySyncKHR).WillOnce(::testing::Return(EGL_TRUE));

g_object_unref(fence);
}
20 changes: 20 additions & 0 deletions engine/src/flutter/shell/platform/linux/fl_opengl_manager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,23 @@ gboolean fl_opengl_manager_can_blit(FlOpenGLManager* self) {
(epoxy_gl_version() >= 30 ||
epoxy_has_gl_extension("GL_EXT_framebuffer_blit"));
}

// Checks whether @extension is supported by the EGL display the contexts are
// created on.
static gboolean has_extension(FlOpenGLManager* self, const gchar* extension) {
g_return_val_if_fail(FL_IS_OPENGL_MANAGER(self), FALSE);

if (self->display == EGL_NO_DISPLAY) {
return FALSE;
}

return epoxy_has_egl_extension(self->display, extension);
}

gboolean fl_opengl_manager_can_fence(FlOpenGLManager* self) {
return has_extension(self, "EGL_KHR_fence_sync");
}

gboolean fl_opengl_manager_can_wait_sync(FlOpenGLManager* self) {
return has_extension(self, "EGL_KHR_wait_sync");
}
26 changes: 26 additions & 0 deletions engine/src/flutter/shell/platform/linux/fl_opengl_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,32 @@ EGLContext fl_opengl_manager_get_context(FlOpenGLManager* manager);
*/
gboolean fl_opengl_manager_can_blit(FlOpenGLManager* manager);

/**
* fl_opengl_manager_can_fence:
* @manager: an #FlOpenGLManager.
*
* Checks whether fences can be used to synchronize rendering between OpenGL
* contexts. Fences are an extension that may be missing on some drivers, so
* callers should fall back to waiting for the rendering to complete, e.g. with
* glFinish(), when this returns %FALSE.
*
* Returns: %TRUE if fences are available.
*/
gboolean fl_opengl_manager_can_fence(FlOpenGLManager* manager);

/**
* fl_opengl_manager_can_wait_sync:
* @manager: an #FlOpenGLManager.
*
* Checks whether a fence can be waited for by the GPU, i.e. without blocking
* the thread doing the waiting. This is an extension that may be missing on
* some drivers, so callers should fall back to blocking when this returns
* %FALSE.
*
* Returns: %TRUE if fences can be waited for without blocking.
*/
gboolean fl_opengl_manager_can_wait_sync(FlOpenGLManager* manager);

G_END_DECLS

#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_OPENGL_MANAGER_H_
Loading
Loading