Visitar URL original
[Linux] Wait for frames to be rendered before using them in another context by robert-ancell · Pull Request #192094 · 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
12 changes: 12 additions & 0 deletions engine/src/flutter/shell/platform/linux/fl_opengl_frame.cc
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,18 @@ void fl_opengl_frame_composite(FlOpenGLFrame* self,
fl_framebuffer_get_id(self->framebuffer));
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, self->pixels);
glBindFramebuffer(GL_READ_FRAMEBUFFER, saved_read_framebuffer_binding);
} else {
// This frame is drawn by GTK on the main thread using a different OpenGL
// context to the one it was rendered with. Sharing a texture between
// contexts requires the rendering to have completed before the other
// context uses it - glFlush() only guarantees the commands have been
// submitted, not that they have finished. Without this GTK can sample a
// partially rendered frame, and drivers can fail in ways that leave the
// context unusable.
//
// The frame is copied out of the GPU with glReadPixels() when it can't be
// shared, which already waits for the rendering to complete.
glFinish();
Comment thread
robert-ancell marked this conversation as resolved.
Resolved
}

glBindFramebuffer(GL_DRAW_FRAMEBUFFER, saved_draw_framebuffer_binding);
Expand Down
51 changes: 51 additions & 0 deletions engine/src/flutter/shell/platform/linux/fl_opengl_frame_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,54 @@ TEST_F(FlOpenGLFrameTest, ZeroSizeClearsFrame) {
EXPECT_EQ(frame_width, 0u);
EXPECT_EQ(frame_height, 0u);
}

// Checks a shareable frame is synchronized before it is handed to GTK, which
// draws it using a different OpenGL context.
// https://github.com/flutter/flutter/issues/191775
TEST_F(FlOpenGLFrameTest, ShareableFrameSynchronized) {
constexpr size_t width = 100;
constexpr size_t height = 100;

g_autoptr(FlOpenGLFrame) frame = fl_opengl_frame_new(/*shareable=*/TRUE);
g_autoptr(FlFramebuffer) framebuffer =
fl_framebuffer_new(GL_RGBA, width, height, FALSE);
FlutterBackingStore backing_store = {
.type = kFlutterBackingStoreTypeOpenGL,
.open_gl = {
.type = kFlutterOpenGLTargetTypeFramebuffer,
.framebuffer = {.target = GL_RGBA8, .user_data = framebuffer}}};
FlutterLayer layer = {.type = kFlutterLayerContentTypeBackingStore,
.backing_store = &backing_store,
.offset = {0, 0},
.size = {width, height}};
const FlutterLayer* layers[1] = {&layer};

EXPECT_CALL(epoxy, glFinish());

fl_opengl_frame_composite(frame, compositor, layers, 1);
}

// Checks a frame that is copied into CPU memory does not pay for a second
// synchronization - glReadPixels() already waits for the frame to be rendered.
TEST_F(FlOpenGLFrameTest, UnshareableFrameNotSynchronizedTwice) {
constexpr size_t width = 100;
constexpr size_t height = 100;

g_autoptr(FlOpenGLFrame) frame = fl_opengl_frame_new(/*shareable=*/FALSE);
g_autoptr(FlFramebuffer) framebuffer =
fl_framebuffer_new(GL_RGBA, width, height, FALSE);
FlutterBackingStore backing_store = {
.type = kFlutterBackingStoreTypeOpenGL,
.open_gl = {
.type = kFlutterOpenGLTargetTypeFramebuffer,
.framebuffer = {.target = GL_RGBA8, .user_data = framebuffer}}};
FlutterLayer layer = {.type = kFlutterLayerContentTypeBackingStore,
.backing_store = &backing_store,
.offset = {0, 0},
.size = {width, height}};
const FlutterLayer* layers[1] = {&layer};

EXPECT_CALL(epoxy, glFinish()).Times(0);

fl_opengl_frame_composite(frame, compositor, layers, 1);
}
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,13 @@ static void fl_view_renderer_subsurface_present_layers(

glBindFramebuffer(GL_DRAW_FRAMEBUFFER, saved_draw_framebuffer_binding);

// The frame is presented below using the subsurface's own OpenGL context.
// Sharing a texture between contexts requires the rendering to have completed
// before the other context uses it - making a context current only flushes
// the previous one, which guarantees the commands have been submitted, not
// that they have finished.
glFinish();
Comment thread
robert-ancell marked this conversation as resolved.
Resolved

// Present the composited frame directly to the subsurface using its own EGL
// context. This reads the engine's frame texture directly, as the subsurface
// context shares resources with the engine.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,12 @@ void _glDeleteTextures(GLsizei n, const GLuint* textures) {
}
}

void _glFinish() {
if (mock) {
mock->glFinish();
}
}

static void _glDisable(GLenum cap) {
_setEnable(cap, GL_FALSE);
}
Expand Down Expand Up @@ -807,6 +813,7 @@ GLuint (*epoxy_glCreateShader)(GLenum shaderType);
void (*epoxy_glDeleteFramebuffers)(GLsizei n, const GLuint* framebuffers);
void (*expoxy_glDeleteShader)(GLuint shader);
void (*epoxy_glDeleteTextures)(GLsizei n, const GLuint* textures);
void (*epoxy_glFinish)();
void (*epoxy_glFramebufferRenderbuffer)(GLenum target,
GLenum attachment,
GLenum renderbuffertarget,
Expand Down Expand Up @@ -898,6 +905,7 @@ static void library_init() {
epoxy_glDeleteRenderbuffers = _glDeleteRenderbuffers;
epoxy_glDeleteShader = _glDeleteShader;
epoxy_glDeleteTextures = _glDeleteTextures;
epoxy_glFinish = _glFinish;
epoxy_glDisable = _glDisable;
epoxy_glEnable = _glEnable;
epoxy_glFramebufferRenderbuffer = _glFramebufferRenderbuffer;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class MockEpoxy {
glDeleteRenderbuffers,
(GLsizei n, const GLuint* renderbuffers));
MOCK_METHOD(void, glDeleteTextures, (GLsizei n, const GLuint* textures));
MOCK_METHOD(void, glFinish, ());
MOCK_METHOD(void, glGenFramebuffers, (GLsizei n, GLuint* framebuffers));
MOCK_METHOD(void, glGenRenderbuffers, (GLsizei n, GLuint* renderbuffers));
MOCK_METHOD(void, glGenTextures, (GLsizei n, GLuint* textures));
Expand Down