Perform OpenGL compositing in the Flutter thread and write to a framebuffer. (#172090)

This framebuffer then gets copied into GTK during the draw calls. This
disconnects the Flutter rendering from the GTK rendering which will
allow us to render Flutter using EGL directly and support rendering
without any views present (required for multi-window).

Framebuffers are shared between the Flutter and GTK OpenGL contexts
using EGLImage. When running under X11 GTK uses GLX and this is not
possible - instead the buffers are copied via the CPU in this case.
This commit is contained in:
Robert Ancell 2025-07-17 12:09:53 +12:00 committed by GitHub
parent c0f1279e77
commit e983e59620
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 344 additions and 303 deletions

View File

@ -12,12 +12,6 @@ static void fl_compositor_class_init(FlCompositorClass* klass) {}
static void fl_compositor_init(FlCompositor* self) {}
FlutterRendererType fl_compositor_get_renderer_type(FlCompositor* self) {
g_return_val_if_fail(FL_IS_COMPOSITOR(self),
static_cast<FlutterRendererType>(0));
return FL_COMPOSITOR_GET_CLASS(self)->get_renderer_type(self);
}
void fl_compositor_wait_for_frame(FlCompositor* self,
int target_width,
int target_height) {

View File

@ -16,8 +16,6 @@ G_DECLARE_DERIVABLE_TYPE(FlCompositor, fl_compositor, FL, COMPOSITOR, GObject)
struct _FlCompositorClass {
GObjectClass parent_class;
FlutterRendererType (*get_renderer_type)(FlCompositor* compositor);
gboolean (*present_layers)(FlCompositor* compositor,
const FlutterLayer** layers,
size_t layers_count);
@ -33,16 +31,6 @@ struct _FlCompositorClass {
* #FlCompositor is an abstract class that implements Flutter compositing.
*/
/**
* fl_compositor_get_renderer_type:
* @compositor: an #FlCompositor.
*
* Gets the rendering method this compositor uses.
*
* Returns: a FlutterRendererType.
*/
FlutterRendererType fl_compositor_get_renderer_type(FlCompositor* compositor);
/**
* fl_compositor_present_layers:
* @compositor: an #FlCompositor.

View File

@ -44,12 +44,18 @@ struct _FlCompositorOpenGL {
// Engine we are rendering.
GWeakRef engine;
// Target OpenGL context;
GdkGLContext* context;
// TRUE if can share framebuffers between contexts.
gboolean shareable;
// Flutter OpenGL contexts.
FlOpenGLManager* opengl_manager;
// Last rendered frame.
FlFramebuffer* framebuffer;
// Last rendered frame pixels (only set if shareable is TRUE).
uint8_t* pixels;
// target dimension for resizing
int target_width;
int target_height;
@ -61,9 +67,6 @@ struct _FlCompositorOpenGL {
// was rendered
bool had_first_frame;
// True if we can use glBlitFramebuffer.
bool has_gl_framebuffer_blit;
// Shader program.
GLuint program;
@ -76,9 +79,6 @@ struct _FlCompositorOpenGL {
// Verticies for the uniform square.
GLuint vertex_buffer;
// Framebuffers to render.
GPtrArray* framebuffers;
// Mutex used when blocking the raster thread until a task is completed on
// platform thread.
GMutex present_mutex;
@ -86,34 +86,15 @@ struct _FlCompositorOpenGL {
// Condition to unblock the raster thread after task is completed on platform
// thread.
GCond present_condition;
// Control thread access to the frame.
GMutex frame_mutex;
};
G_DEFINE_TYPE(FlCompositorOpenGL,
fl_compositor_opengl,
fl_compositor_get_type())
// Check if running on driver supporting blit.
static gboolean driver_supports_blit() {
const gchar* vendor = reinterpret_cast<const gchar*>(glGetString(GL_VENDOR));
// Note: List of unsupported vendors due to issue
// https://github.com/flutter/flutter/issues/152099
const char* unsupported_vendors_exact[] = {"Vivante Corporation", "ARM"};
const char* unsupported_vendors_fuzzy[] = {"NVIDIA"};
for (const char* unsupported : unsupported_vendors_fuzzy) {
if (strstr(vendor, unsupported) != nullptr) {
return FALSE;
}
}
for (const char* unsupported : unsupported_vendors_exact) {
if (strcmp(vendor, unsupported) == 0) {
return FALSE;
}
}
return TRUE;
}
// Returns the log for the given OpenGL shader. Must be freed by the caller.
static gchar* get_shader_log(GLuint shader) {
GLint log_length;
@ -203,31 +184,50 @@ static void setup_shader(FlCompositorOpenGL* self) {
GL_STATIC_DRAW);
}
static void render_with_blit(FlCompositorOpenGL* self,
GPtrArray* framebuffers) {
// Disable the scissor test as it can affect blit operations.
// Prevents regressions like: https://github.com/flutter/flutter/issues/140828
// See OpenGL specification version 4.6, section 18.3.1.
glDisable(GL_SCISSOR_TEST);
static void composite_layer(FlCompositorOpenGL* self,
FlFramebuffer* framebuffer,
double x,
double y,
int width,
int height) {
size_t texture_width = fl_framebuffer_get_width(framebuffer);
size_t texture_height = fl_framebuffer_get_height(framebuffer);
glUniform2f(self->offset_location, (2 * x / width) - 1.0,
(2 * y / width) - 1.0);
glUniform2f(self->scale_location, texture_width / width,
texture_height / height);
for (guint i = 0; i < framebuffers->len; i++) {
FlFramebuffer* framebuffer =
FL_FRAMEBUFFER(g_ptr_array_index(framebuffers, i));
GLuint texture_id = fl_framebuffer_get_texture_id(framebuffer);
glBindTexture(GL_TEXTURE_2D, texture_id);
GLuint framebuffer_id = fl_framebuffer_get_id(framebuffer);
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer_id);
size_t width = fl_framebuffer_get_width(framebuffer);
size_t height = fl_framebuffer_get_height(framebuffer);
glBlitFramebuffer(0, 0, width, height, 0, 0, width, height,
GL_COLOR_BUFFER_BIT, GL_NEAREST);
}
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
glDrawArrays(GL_TRIANGLES, 0, 6);
}
static void render_with_textures(FlCompositorOpenGL* self,
GPtrArray* framebuffers,
int width,
int height) {
static gboolean present_layers(FlCompositorOpenGL* self,
const FlutterLayer** layers,
size_t layers_count) {
g_return_val_if_fail(FL_IS_COMPOSITOR_OPENGL(self), FALSE);
g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&self->frame_mutex);
// ignore incoming frame with wrong dimensions in trivial case with just one
// layer
if (self->blocking_main_thread && layers_count == 1 &&
layers[0]->offset.x == 0 && layers[0]->offset.y == 0 &&
(layers[0]->size.width != self->target_width ||
layers[0]->size.height != self->target_height)) {
return TRUE;
}
if (layers_count == 0) {
return TRUE;
}
GLint general_format = GL_RGBA;
if (epoxy_has_gl_extension("GL_EXT_texture_format_BGRA8888")) {
general_format = GL_BGRA_EXT;
}
// Save bindings that are set by this function. All bindings must be restored
// to their original values because Skia expects that its bindings have not
// been altered.
@ -237,6 +237,29 @@ static void render_with_textures(FlCompositorOpenGL* self,
glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &saved_vao_binding);
GLint saved_array_buffer_binding;
glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &saved_array_buffer_binding);
GLint saved_framebuffer_binding;
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &saved_framebuffer_binding);
// Update framebuffer to write into.
size_t width = layers[0]->size.width;
size_t height = layers[0]->size.height;
if (self->framebuffer == nullptr ||
fl_framebuffer_get_width(self->framebuffer) != width ||
fl_framebuffer_get_height(self->framebuffer) != height) {
g_clear_object(&self->framebuffer);
self->framebuffer =
fl_framebuffer_new(general_format, width, height, self->shareable);
// If not shareable make buffer to copy frame pixels into.
if (!self->shareable) {
size_t data_length = width * height * 4;
self->pixels = static_cast<uint8_t*>(realloc(self->pixels, data_length));
}
}
self->had_first_frame = true;
fl_compositor_opengl_unblock_main_thread(self);
// FIXME(robert-ancell): The vertex array is the same for all views, but
// cannot be shared in OpenGL. Find a way to not generate this every time.
@ -259,23 +282,45 @@ static void render_with_textures(FlCompositorOpenGL* self,
glUseProgram(self->program);
for (guint i = 0; i < framebuffers->len; i++) {
FlFramebuffer* framebuffer =
FL_FRAMEBUFFER(g_ptr_array_index(framebuffers, i));
// Disable the scissor test as it can affect blit operations.
// Prevents regressions like: https://github.com/flutter/flutter/issues/140828
// See OpenGL specification version 4.6, section 18.3.1.
glDisable(GL_SCISSOR_TEST);
// FIXME(robert-ancell): The offset from present_layers() is not here, needs
// to be updated.
size_t texture_width = fl_framebuffer_get_width(framebuffer);
size_t texture_height = fl_framebuffer_get_height(framebuffer);
glUniform2f(self->offset_location, 0, 0);
glUniform2f(self->scale_location, texture_width / width,
texture_height / height);
GLuint texture_id = fl_framebuffer_get_texture_id(framebuffer);
glBindTexture(GL_TEXTURE_2D, texture_id);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER,
fl_framebuffer_get_id(self->framebuffer));
gboolean first_layer = TRUE;
for (size_t i = 0; i < layers_count; ++i) {
const FlutterLayer* layer = layers[i];
switch (layer->type) {
case kFlutterLayerContentTypeBackingStore: {
const FlutterBackingStore* backing_store = layer->backing_store;
FlFramebuffer* framebuffer =
FL_FRAMEBUFFER(backing_store->open_gl.framebuffer.user_data);
glBindFramebuffer(GL_READ_FRAMEBUFFER,
fl_framebuffer_get_id(framebuffer));
// The first layer can be blitted, and following layers composited with
// this.
if (first_layer) {
glBlitFramebuffer(layer->offset.x, layer->offset.y, layer->size.width,
layer->size.height, layer->offset.x,
layer->offset.y, layer->size.width,
layer->size.height, GL_COLOR_BUFFER_BIT,
GL_NEAREST);
first_layer = FALSE;
} else {
composite_layer(self, framebuffer, layer->offset.x, layer->offset.y,
width, height);
}
} break;
case kFlutterLayerContentTypePlatformView: {
// TODO(robert-ancell) Not implemented -
// https://github.com/flutter/flutter/issues/41724
} break;
}
}
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
glFlush();
glDeleteVertexArrays(1, &vao);
@ -284,118 +329,13 @@ static void render_with_textures(FlCompositorOpenGL* self,
glBindTexture(GL_TEXTURE_2D, saved_texture_binding);
glBindVertexArray(saved_vao_binding);
glBindBuffer(GL_ARRAY_BUFFER, saved_array_buffer_binding);
}
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, saved_framebuffer_binding);
static void render(FlCompositorOpenGL* self,
GPtrArray* framebuffers,
int width,
int height) {
if (self->has_gl_framebuffer_blit) {
render_with_blit(self, framebuffers);
} else {
render_with_textures(self, framebuffers, width, height);
}
}
static gboolean present_layers(FlCompositorOpenGL* self,
const FlutterLayer** layers,
size_t layers_count) {
g_return_val_if_fail(FL_IS_COMPOSITOR_OPENGL(self), FALSE);
// ignore incoming frame with wrong dimensions in trivial case with just one
// layer
if (self->blocking_main_thread && layers_count == 1 &&
layers[0]->offset.x == 0 && layers[0]->offset.y == 0 &&
(layers[0]->size.width != self->target_width ||
layers[0]->size.height != self->target_height)) {
return TRUE;
}
self->had_first_frame = true;
fl_compositor_opengl_unblock_main_thread(self);
GLint general_format = GL_RGBA;
if (epoxy_has_gl_extension("GL_EXT_texture_format_BGRA8888")) {
general_format = GL_BGRA_EXT;
}
g_autoptr(GPtrArray) framebuffers =
g_ptr_array_new_with_free_func(g_object_unref);
for (size_t i = 0; i < layers_count; ++i) {
const FlutterLayer* layer = layers[i];
switch (layer->type) {
case kFlutterLayerContentTypeBackingStore: {
const FlutterBackingStore* backing_store = layer->backing_store;
FlFramebuffer* framebuffer =
FL_FRAMEBUFFER(backing_store->open_gl.framebuffer.user_data);
g_ptr_array_add(framebuffers, g_object_ref(framebuffer));
} break;
case kFlutterLayerContentTypePlatformView: {
// TODO(robert-ancell) Not implemented -
// https://github.com/flutter/flutter/issues/41724
} break;
}
}
if (self->context == nullptr) {
// Store for rendering later
g_ptr_array_unref(self->framebuffers);
self->framebuffers = g_ptr_array_ref(framebuffers);
} else {
// Composite into a single framebuffer.
if (framebuffers->len > 1) {
size_t width = 0, height = 0;
for (guint i = 0; i < framebuffers->len; i++) {
FlFramebuffer* framebuffer =
FL_FRAMEBUFFER(g_ptr_array_index(framebuffers, i));
size_t w = fl_framebuffer_get_width(framebuffer);
size_t h = fl_framebuffer_get_height(framebuffer);
if (w > width) {
width = w;
}
if (h > height) {
height = h;
}
}
FlFramebuffer* view_framebuffer =
fl_framebuffer_new(general_format, width, height);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER,
fl_framebuffer_get_id(view_framebuffer));
render(self, framebuffers, width, height);
g_ptr_array_set_size(framebuffers, 0);
g_ptr_array_add(framebuffers, view_framebuffer);
}
// Read back pixel values.
FlFramebuffer* framebuffer =
FL_FRAMEBUFFER(g_ptr_array_index(framebuffers, 0));
size_t width = fl_framebuffer_get_width(framebuffer);
size_t height = fl_framebuffer_get_height(framebuffer);
size_t data_length = width * height * 4;
g_autofree uint8_t* data = static_cast<uint8_t*>(malloc(data_length));
glBindFramebuffer(GL_READ_FRAMEBUFFER, fl_framebuffer_get_id(framebuffer));
glReadPixels(0, 0, width, height, general_format, GL_UNSIGNED_BYTE, data);
// Write into a texture in the views context.
gdk_gl_context_make_current(self->context);
g_autoptr(FlFramebuffer) view_framebuffer =
fl_framebuffer_new(general_format, width, height);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER,
fl_framebuffer_get_id(view_framebuffer));
glBindTexture(GL_TEXTURE_2D,
fl_framebuffer_get_texture_id(view_framebuffer));
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA,
GL_UNSIGNED_BYTE, data);
g_autoptr(GPtrArray) secondary_framebuffers =
g_ptr_array_new_with_free_func(g_object_unref);
g_ptr_array_add(secondary_framebuffers, g_object_ref(view_framebuffer));
g_ptr_array_unref(self->framebuffers);
self->framebuffers = g_ptr_array_ref(secondary_framebuffers);
if (!self->shareable) {
glBindFramebuffer(GL_READ_FRAMEBUFFER,
fl_framebuffer_get_id(self->framebuffer));
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, self->pixels);
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
}
return TRUE;
@ -428,11 +368,6 @@ static void present_layers_task_cb(gpointer user_data) {
g_cond_signal(&self->present_condition);
}
static FlutterRendererType fl_compositor_opengl_get_renderer_type(
FlCompositor* compositor) {
return kOpenGL;
}
static void fl_compositor_opengl_wait_for_frame(FlCompositor* compositor,
int target_width,
int target_height) {
@ -491,9 +426,9 @@ static void fl_compositor_opengl_dispose(GObject* object) {
fl_compositor_opengl_unblock_main_thread(self);
g_weak_ref_clear(&self->engine);
g_clear_object(&self->context);
g_clear_object(&self->opengl_manager);
g_clear_pointer(&self->framebuffers, g_ptr_array_unref);
g_clear_object(&self->framebuffer);
g_clear_pointer(&self->pixels, g_free);
g_mutex_clear(&self->present_mutex);
g_cond_clear(&self->present_condition);
@ -501,8 +436,6 @@ static void fl_compositor_opengl_dispose(GObject* object) {
}
static void fl_compositor_opengl_class_init(FlCompositorOpenGLClass* klass) {
FL_COMPOSITOR_CLASS(klass)->get_renderer_type =
fl_compositor_opengl_get_renderer_type;
FL_COMPOSITOR_CLASS(klass)->wait_for_frame =
fl_compositor_opengl_wait_for_frame;
FL_COMPOSITOR_CLASS(klass)->present_layers =
@ -512,45 +445,64 @@ static void fl_compositor_opengl_class_init(FlCompositorOpenGLClass* klass) {
}
static void fl_compositor_opengl_init(FlCompositorOpenGL* self) {
self->framebuffers = g_ptr_array_new();
g_mutex_init(&self->present_mutex);
g_cond_init(&self->present_condition);
}
FlCompositorOpenGL* fl_compositor_opengl_new(FlEngine* engine,
GdkGLContext* context) {
gboolean shareable) {
FlCompositorOpenGL* self = FL_COMPOSITOR_OPENGL(
g_object_new(fl_compositor_opengl_get_type(), nullptr));
g_weak_ref_init(&self->engine, engine);
self->context =
context != nullptr ? GDK_GL_CONTEXT(g_object_ref(context)) : nullptr;
self->shareable = shareable;
self->opengl_manager =
FL_OPENGL_MANAGER(g_object_ref(fl_engine_get_opengl_manager(engine)));
fl_opengl_manager_make_current(self->opengl_manager);
self->has_gl_framebuffer_blit =
driver_supports_blit() &&
(epoxy_gl_version() >= 30 ||
epoxy_has_gl_extension("GL_EXT_framebuffer_blit"));
if (!self->has_gl_framebuffer_blit) {
setup_shader(self);
}
setup_shader(self);
return self;
}
void fl_compositor_opengl_render(FlCompositorOpenGL* self,
int width,
int height) {
void fl_compositor_opengl_render(FlCompositorOpenGL* self) {
g_return_if_fail(FL_IS_COMPOSITOR_OPENGL(self));
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&self->frame_mutex);
render(self, self->framebuffers, width, height);
if (self->framebuffer == nullptr) {
return;
}
size_t width = fl_framebuffer_get_width(self->framebuffer);
size_t height = fl_framebuffer_get_height(self->framebuffer);
if (fl_framebuffer_get_shareable(self->framebuffer)) {
g_autoptr(FlFramebuffer) sibling =
fl_framebuffer_create_sibling(self->framebuffer);
glBindFramebuffer(GL_READ_FRAMEBUFFER, fl_framebuffer_get_id(sibling));
glBlitFramebuffer(0, 0, width, height, 0, 0, width, height,
GL_COLOR_BUFFER_BIT, GL_NEAREST);
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
} else {
GLuint texture_id;
glGenTextures(1, &texture_id);
glBindTexture(GL_TEXTURE_2D, texture_id);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA,
GL_UNSIGNED_BYTE, self->pixels);
GLuint framebuffer_id;
glGenFramebuffers(1, &framebuffer_id);
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer_id);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D, texture_id, 0);
glBlitFramebuffer(0, 0, width, height, 0, 0, width, height,
GL_COLOR_BUFFER_BIT, GL_NEAREST);
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
glDeleteFramebuffers(1, &framebuffer_id);
glDeleteTextures(1, &texture_id);
}
glFlush();
}

View File

@ -9,6 +9,7 @@
#include "flutter/shell/platform/embedder/embedder.h"
#include "flutter/shell/platform/linux/fl_compositor.h"
#include "flutter/shell/platform/linux/fl_framebuffer.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_engine.h"
G_BEGIN_DECLS
@ -28,26 +29,23 @@ G_DECLARE_FINAL_TYPE(FlCompositorOpenGL,
/**
* fl_compositor_opengl_new:
* @engine: an #FlEngine.
* @context: the OpenGL context that is being rendered into.
* @shareable: %TRUE if the can use a framebuffer that is shared between
* contexts.
*
* Creates a new OpenGL compositor.
*
* Returns: a new #FlCompositorOpenGL.
*/
FlCompositorOpenGL* fl_compositor_opengl_new(FlEngine* engine,
GdkGLContext* context);
gboolean shareable);
/**
* fl_compositor_opengl_render:
* @compositor: an #FlCompositorOpenGL.
* @width: width of the window in pixels.
* @height: height of the window in pixels.
*
* Performs OpenGL commands to render current Flutter view.
* Renders the current frame.
*/
void fl_compositor_opengl_render(FlCompositorOpenGL* compositor,
int width,
int height);
void fl_compositor_opengl_render(FlCompositorOpenGL* compositor);
/**
* fl_compositor_opengl_cleanup:

View File

@ -34,14 +34,14 @@ TEST(FlCompositorOpenGLTest, RestoresGLState) {
g_autoptr(FlMockRenderable) renderable = fl_mock_renderable_new();
g_autoptr(FlCompositorOpenGL) compositor =
fl_compositor_opengl_new(engine, nullptr);
fl_compositor_opengl_new(engine, FALSE);
fl_engine_set_implicit_view(engine, FL_RENDERABLE(renderable));
fl_compositor_wait_for_frame(FL_COMPOSITOR(compositor), kWidth, kHeight);
fml::AutoResetWaitableEvent latch;
g_autoptr(FlFramebuffer) framebuffer =
fl_framebuffer_new(GL_RGB, kWidth, kHeight);
fl_framebuffer_new(GL_RGB, kWidth, kHeight, FALSE);
FlutterBackingStore backing_store = {
.type = kFlutterBackingStoreTypeOpenGL,
.open_gl = {.framebuffer = {.user_data = framebuffer}}};
@ -64,8 +64,6 @@ TEST(FlCompositorOpenGLTest, RestoresGLState) {
g_main_loop_run(loop);
fl_compositor_opengl_render(compositor, kWidth, kHeight);
GLuint texture_2d_binding;
glGetIntegerv(GL_TEXTURE_BINDING_2D,
reinterpret_cast<GLint*>(&texture_2d_binding));
@ -95,14 +93,14 @@ TEST(FlCompositorOpenGLTest, BlitFramebuffer) {
g_autoptr(FlMockRenderable) renderable = fl_mock_renderable_new();
g_autoptr(FlCompositorOpenGL) compositor =
fl_compositor_opengl_new(engine, nullptr);
fl_compositor_opengl_new(engine, FALSE);
fl_engine_set_implicit_view(engine, FL_RENDERABLE(renderable));
fl_compositor_wait_for_frame(FL_COMPOSITOR(compositor), kWidth, kHeight);
fml::AutoResetWaitableEvent latch;
g_autoptr(FlFramebuffer) framebuffer =
fl_framebuffer_new(GL_RGB, kWidth, kHeight);
fl_framebuffer_new(GL_RGB, kWidth, kHeight, FALSE);
FlutterBackingStore backing_store = {
.type = kFlutterBackingStoreTypeOpenGL,
.open_gl = {.framebuffer = {.user_data = framebuffer}}};
@ -122,8 +120,6 @@ TEST(FlCompositorOpenGLTest, BlitFramebuffer) {
g_main_loop_run(loop);
fl_compositor_opengl_render(compositor, kWidth, kHeight);
latch.Wait();
}
@ -151,14 +147,14 @@ TEST(FlCompositorOpenGLTest, BlitFramebufferExtension) {
g_autoptr(FlMockRenderable) renderable = fl_mock_renderable_new();
g_autoptr(FlCompositorOpenGL) compositor =
fl_compositor_opengl_new(engine, nullptr);
fl_compositor_opengl_new(engine, FALSE);
fl_engine_set_implicit_view(engine, FL_RENDERABLE(renderable));
fl_compositor_wait_for_frame(FL_COMPOSITOR(compositor), kWidth, kHeight);
fml::AutoResetWaitableEvent latch;
g_autoptr(FlFramebuffer) framebuffer =
fl_framebuffer_new(GL_RGB, kWidth, kHeight);
fl_framebuffer_new(GL_RGB, kWidth, kHeight, FALSE);
FlutterBackingStore backing_store = {
.type = kFlutterBackingStoreTypeOpenGL,
.open_gl = {.framebuffer = {.user_data = framebuffer}}};
@ -178,7 +174,6 @@ TEST(FlCompositorOpenGLTest, BlitFramebufferExtension) {
g_main_loop_run(loop);
fl_compositor_opengl_render(compositor, kWidth, kHeight);
// Wait until the raster thread has finished before letting
// the engine go out of scope.
latch.Wait();
@ -201,14 +196,14 @@ TEST(FlCompositorOpenGLTest, NoBlitFramebuffer) {
g_autoptr(FlMockRenderable) renderable = fl_mock_renderable_new();
g_autoptr(FlCompositorOpenGL) compositor =
fl_compositor_opengl_new(engine, nullptr);
fl_compositor_opengl_new(engine, FALSE);
fl_engine_set_implicit_view(engine, FL_RENDERABLE(renderable));
fl_compositor_wait_for_frame(FL_COMPOSITOR(compositor), kWidth, kHeight);
fml::AutoResetWaitableEvent latch;
g_autoptr(FlFramebuffer) framebuffer =
fl_framebuffer_new(GL_RGB, kWidth, kHeight);
fl_framebuffer_new(GL_RGB, kWidth, kHeight, FALSE);
FlutterBackingStore backing_store = {
.type = kFlutterBackingStoreTypeOpenGL,
.open_gl = {.framebuffer = {.user_data = framebuffer}}};
@ -228,8 +223,6 @@ TEST(FlCompositorOpenGLTest, NoBlitFramebuffer) {
g_main_loop_run(loop);
fl_compositor_opengl_render(compositor, kWidth, kHeight);
// Wait until the raster thread has finished before letting
// the engine go out of scope.
latch.Wait();
@ -253,14 +246,14 @@ TEST(FlCompositorOpenGLTest, BlitFramebufferNvidia) {
g_autoptr(FlMockRenderable) renderable = fl_mock_renderable_new();
g_autoptr(FlCompositorOpenGL) compositor =
fl_compositor_opengl_new(engine, nullptr);
fl_compositor_opengl_new(engine, FALSE);
fl_engine_set_implicit_view(engine, FL_RENDERABLE(renderable));
fl_compositor_wait_for_frame(FL_COMPOSITOR(compositor), kWidth, kHeight);
fml::AutoResetWaitableEvent latch;
g_autoptr(FlFramebuffer) framebuffer =
fl_framebuffer_new(GL_RGB, kWidth, kHeight);
fl_framebuffer_new(GL_RGB, kWidth, kHeight, FALSE);
FlutterBackingStore backing_store = {
.type = kFlutterBackingStoreTypeOpenGL,
.open_gl = {.framebuffer = {.user_data = framebuffer}}};
@ -280,8 +273,6 @@ TEST(FlCompositorOpenGLTest, BlitFramebufferNvidia) {
g_main_loop_run(loop);
fl_compositor_opengl_render(compositor, kWidth, kHeight);
// Wait until the raster thread has finished before letting
// the engine go out of scope.
latch.Wait();
@ -306,7 +297,7 @@ TEST(FlCompositorOpenGLTest, MultiView) {
g_autoptr(FlMockRenderable) secondary_renderable = fl_mock_renderable_new();
g_autoptr(FlCompositorOpenGL) compositor =
fl_compositor_opengl_new(engine, nullptr);
fl_compositor_opengl_new(engine, FALSE);
fl_engine_set_implicit_view(engine, FL_RENDERABLE(renderable));
fl_engine_add_view(engine, FL_RENDERABLE(secondary_renderable), 1024, 768,
1.0, nullptr, nullptr, nullptr);
@ -315,7 +306,7 @@ TEST(FlCompositorOpenGLTest, MultiView) {
fml::AutoResetWaitableEvent latch;
g_autoptr(FlFramebuffer) framebuffer =
fl_framebuffer_new(GL_RGB, kWidth, kHeight);
fl_framebuffer_new(GL_RGB, kWidth, kHeight, FALSE);
FlutterBackingStore backing_store = {
.type = kFlutterBackingStoreTypeOpenGL,
.open_gl = {.framebuffer = {.user_data = framebuffer}}};

View File

@ -18,11 +18,6 @@ G_DEFINE_TYPE(FlCompositorSoftware,
fl_compositor_software,
fl_compositor_get_type())
static FlutterRendererType fl_compositor_software_get_renderer_type(
FlCompositor* compositor) {
return kSoftware;
}
static void fl_compositor_software_wait_for_frame(FlCompositor* compositor,
int target_width,
int target_height) {}
@ -72,8 +67,6 @@ static void fl_compositor_software_dispose(GObject* object) {
static void fl_compositor_software_class_init(
FlCompositorSoftwareClass* klass) {
FL_COMPOSITOR_CLASS(klass)->get_renderer_type =
fl_compositor_software_get_renderer_type;
FL_COMPOSITOR_CLASS(klass)->wait_for_frame =
fl_compositor_software_wait_for_frame;
FL_COMPOSITOR_CLASS(klass)->present_layers =
@ -93,6 +86,8 @@ FlCompositorSoftware* fl_compositor_software_new() {
gboolean fl_compositor_software_render(FlCompositorSoftware* self,
cairo_t* cr,
gint scale_factor) {
g_return_val_if_fail(FL_IS_COMPOSITOR_SOFTWARE(self), FALSE);
g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&self->frame_mutex);
if (self->surface == nullptr) {

View File

@ -261,7 +261,7 @@ static bool create_opengl_backing_store(
}
FlFramebuffer* framebuffer = fl_framebuffer_new(
general_format, config->size.width, config->size.height);
general_format, config->size.width, config->size.height, FALSE);
if (!framebuffer) {
g_warning("Failed to create backing store");
return false;
@ -668,6 +668,7 @@ static FlEngine* fl_engine_new_full(FlDartProject* project,
}
self->renderer_type = kOpenGL;
}
if (binary_messenger != nullptr) {
self->binary_messenger =
FL_BINARY_MESSENGER(g_object_ref(binary_messenger));

View File

@ -4,6 +4,7 @@
#include "fl_framebuffer.h"
#include <epoxy/egl.h>
#include <epoxy/gl.h>
struct _FlFramebuffer {
@ -23,10 +24,32 @@ struct _FlFramebuffer {
// Stencil buffer associated with this framebuffer.
GLuint depth_stencil;
// EGL image for this texture.
EGLImage image;
};
G_DEFINE_TYPE(FlFramebuffer, fl_framebuffer, G_TYPE_OBJECT)
static EGLImage create_egl_image(GLuint texture_id) {
EGLDisplay egl_display = eglGetCurrentDisplay();
if (egl_display == EGL_NO_DISPLAY) {
g_warning("Failed to create EGL image: Failed to get current EGL display");
return nullptr;
}
EGLContext egl_context = eglGetCurrentContext();
if (egl_context == EGL_NO_CONTEXT) {
g_warning("Failed to create EGL image: Failed to get current EGL context");
return nullptr;
}
return eglCreateImage(
egl_display, egl_context, EGL_GL_TEXTURE_2D,
reinterpret_cast<EGLClientBuffer>(static_cast<intptr_t>(texture_id)),
nullptr);
}
static void fl_framebuffer_dispose(GObject* object) {
FlFramebuffer* self = FL_FRAMEBUFFER(object);
@ -43,19 +66,22 @@ static void fl_framebuffer_class_init(FlFramebufferClass* klass) {
static void fl_framebuffer_init(FlFramebuffer* self) {}
FlFramebuffer* fl_framebuffer_new(GLint format, size_t width, size_t height) {
FlFramebuffer* provider =
FlFramebuffer* fl_framebuffer_new(GLint format,
size_t width,
size_t height,
gboolean shareable) {
FlFramebuffer* self =
FL_FRAMEBUFFER(g_object_new(fl_framebuffer_get_type(), nullptr));
provider->width = width;
provider->height = height;
self->width = width;
self->height = height;
glGenTextures(1, &provider->texture_id);
glGenFramebuffers(1, &provider->framebuffer_id);
glGenTextures(1, &self->texture_id);
glGenFramebuffers(1, &self->framebuffer_id);
glBindFramebuffer(GL_FRAMEBUFFER, provider->framebuffer_id);
glBindFramebuffer(GL_FRAMEBUFFER, self->framebuffer_id);
glBindTexture(GL_TEXTURE_2D, provider->texture_id);
glBindTexture(GL_TEXTURE_2D, self->texture_id);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
@ -64,22 +90,59 @@ FlFramebuffer* fl_framebuffer_new(GLint format, size_t width, size_t height) {
GL_UNSIGNED_BYTE, NULL);
glBindTexture(GL_TEXTURE_2D, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
provider->texture_id, 0);
if (shareable) {
self->image = create_egl_image(self->texture_id);
}
glGenRenderbuffers(1, &provider->depth_stencil);
glBindRenderbuffer(GL_RENDERBUFFER, provider->depth_stencil);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
self->texture_id, 0);
glGenRenderbuffers(1, &self->depth_stencil);
glBindRenderbuffer(GL_RENDERBUFFER, self->depth_stencil);
glRenderbufferStorage(GL_RENDERBUFFER, // target
GL_DEPTH24_STENCIL8, // internal format
width, // width
height // height
);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
GL_RENDERBUFFER, provider->depth_stencil);
GL_RENDERBUFFER, self->depth_stencil);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
GL_RENDERBUFFER, provider->depth_stencil);
GL_RENDERBUFFER, self->depth_stencil);
return provider;
return self;
}
gboolean fl_framebuffer_get_shareable(FlFramebuffer* self) {
g_return_val_if_fail(FL_IS_FRAMEBUFFER(self), FALSE);
return self->image != nullptr;
}
FlFramebuffer* fl_framebuffer_create_sibling(FlFramebuffer* self) {
g_return_val_if_fail(FL_IS_FRAMEBUFFER(self), nullptr);
g_return_val_if_fail(self->image != nullptr, nullptr);
FlFramebuffer* sibling =
FL_FRAMEBUFFER(g_object_new(fl_framebuffer_get_type(), nullptr));
sibling->width = self->width;
sibling->height = self->height;
sibling->image = self->image;
// Make texture from existing image.
glGenTextures(1, &sibling->texture_id);
glBindTexture(GL_TEXTURE_2D, sibling->texture_id);
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, self->image);
// Make framebuffer that uses this texture.
glGenFramebuffers(1, &sibling->framebuffer_id);
GLint saved_framebuffer_binding;
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &saved_framebuffer_binding);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, sibling->framebuffer_id);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
sibling->texture_id, 0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, saved_framebuffer_binding);
return sibling;
}
GLuint fl_framebuffer_get_id(FlFramebuffer* self) {
@ -90,10 +153,6 @@ GLuint fl_framebuffer_get_texture_id(FlFramebuffer* self) {
return self->texture_id;
}
GLenum fl_framebuffer_get_target(FlFramebuffer* self) {
return GL_TEXTURE_2D;
}
size_t fl_framebuffer_get_width(FlFramebuffer* self) {
return self->width;
}

View File

@ -24,12 +24,40 @@ G_DECLARE_FINAL_TYPE(FlFramebuffer, fl_framebuffer, FL, FRAMEBUFFER, GObject)
* @format: format, e.g. GL_RGB, GL_BGR
* @width: width of texture.
* @height: height of texture.
* @shareable: %TRUE if this framebuffer can be shared between contexts
* (requires EGL).
*
* Creates a new frame buffer. Requires a valid OpenGL context to create.
*
* Returns: a new #FlFramebuffer.
*/
FlFramebuffer* fl_framebuffer_new(GLint format, size_t width, size_t height);
FlFramebuffer* fl_framebuffer_new(GLint format,
size_t width,
size_t height,
gboolean shareable);
/**
* fl_framebuffer_get_shareable:
* @framebuffer: an #FlFramebuffer.
*
* Checks if this framebuffer can be shared between contexts (using
* fl_framebuffer_create_sibling).
*
* Returns: %TRUE if this framebuffer can be shared.
*/
gboolean fl_framebuffer_get_shareable(FlFramebuffer* framebuffer);
/**
* fl_framebuffer_create_sibling:
* @framebuffer: an #FlFramebuffer.
*
* Creates a new framebuffer with the same backing texture as the original. This
* uses EGLImage to share the texture and allows a framebuffer created in one
* OpenGL context to be used in another.
*
* Returns: a new #FlFramebuffer.
*/
FlFramebuffer* fl_framebuffer_create_sibling(FlFramebuffer* framebuffer);
/**
* fl_framebuffer_get_id:
@ -51,16 +79,6 @@ GLuint fl_framebuffer_get_id(FlFramebuffer* framebuffer);
*/
GLuint fl_framebuffer_get_texture_id(FlFramebuffer* framebuffer);
/**
* fl_framebuffer_get_target:
* @framebuffer: an #FlFramebuffer.
*
* Gets target texture (example GL_TEXTURE_2D or GL_TEXTURE_RECTANGLE).
*
* Returns: target texture.
*/
GLenum fl_framebuffer_get_target(FlFramebuffer* framebuffer);
/**
* fl_framebuffer_get_width:
* @framebuffer: an #FlFramebuffer.

View File

@ -10,7 +10,8 @@
TEST(FlFramebufferTest, HasDepthStencil) {
::testing::NiceMock<flutter::testing::MockEpoxy> epoxy;
g_autoptr(FlFramebuffer) framebuffer = fl_framebuffer_new(GL_RGB, 100, 100);
g_autoptr(FlFramebuffer) framebuffer =
fl_framebuffer_new(GL_RGB, 100, 100, FALSE);
GLint depth_type = GL_NONE;
glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
@ -31,10 +32,19 @@ TEST(FlFramebufferTest, ResourcesRemoved) {
EXPECT_CALL(epoxy, glGenFramebuffers);
EXPECT_CALL(epoxy, glGenTextures);
EXPECT_CALL(epoxy, glGenRenderbuffers);
FlFramebuffer* framebuffer = fl_framebuffer_new(GL_RGB, 100, 100);
FlFramebuffer* framebuffer = fl_framebuffer_new(GL_RGB, 100, 100, FALSE);
EXPECT_CALL(epoxy, glDeleteFramebuffers);
EXPECT_CALL(epoxy, glDeleteTextures);
EXPECT_CALL(epoxy, glDeleteRenderbuffers);
g_object_unref(framebuffer);
}
TEST(FlFramebufferTest, Sibling) {
::testing::NiceMock<flutter::testing::MockEpoxy> epoxy;
EXPECT_CALL(epoxy, eglCreateImage);
g_autoptr(FlFramebuffer) framebuffer =
fl_framebuffer_new(GL_RGB, 100, 100, TRUE);
g_autoptr(FlFramebuffer) sibling = fl_framebuffer_create_sibling(framebuffer);
}

View File

@ -5,6 +5,7 @@
#include "flutter/shell/platform/linux/public/flutter_linux/fl_view.h"
#include <atk/atk.h>
#include <gdk/gdkwayland.h>
#include <gtk/gtk-a11y.h>
#include <cstring>
@ -461,13 +462,16 @@ static GdkGLContext* create_context_cb(FlView* self) {
static void realize_cb(FlView* self) {
FlutterRendererType renderer_type = fl_engine_get_renderer_type(self->engine);
gboolean shareable;
switch (renderer_type) {
case kOpenGL:
self->compositor = FL_COMPOSITOR(fl_compositor_opengl_new(
self->engine,
self->view_id == flutter::kFlutterImplicitViewId
? nullptr
: gtk_gl_area_get_context(GTK_GL_AREA(self->render_area))));
// If using Wayland, then EGL is in use and we can access the frame
// from the Flutter context using EGLImage. If not (i.e. X11 using GLX)
// then we have to copy the texture via the CPU.
shareable =
GDK_IS_WAYLAND_DISPLAY(gtk_widget_get_display(GTK_WIDGET(self)));
self->compositor =
FL_COMPOSITOR(fl_compositor_opengl_new(self->engine, shareable));
break;
case kSoftware:
self->compositor = FL_COMPOSITOR(fl_compositor_software_new());
@ -529,11 +533,7 @@ static gboolean render_cb(FlView* self, GdkGLContext* context) {
return FALSE;
}
int width = gtk_widget_get_allocated_width(self->render_area);
int height = gtk_widget_get_allocated_height(self->render_area);
gint scale_factor = gtk_widget_get_scale_factor(self->render_area);
fl_compositor_opengl_render(FL_COMPOSITOR_OPENGL(self->compositor),
width * scale_factor, height * scale_factor);
fl_compositor_opengl_render(FL_COMPOSITOR_OPENGL(self->compositor));
return TRUE;
}
@ -551,14 +551,13 @@ static void unrealize_cb(FlView* self) {
return;
}
fl_opengl_manager_make_current(fl_engine_get_opengl_manager(self->engine));
GError* gl_error = gtk_gl_area_get_error(GTK_GL_AREA(self->render_area));
if (gl_error != NULL) {
g_warning("Failed to uninitialize GLArea: %s", gl_error->message);
return;
}
fl_opengl_manager_make_current(fl_engine_get_opengl_manager(self->engine));
fl_compositor_opengl_cleanup(FL_COMPOSITOR_OPENGL(self->compositor));
}

View File

@ -47,12 +47,16 @@ typedef struct {
typedef struct {
} MockSurface;
typedef struct {
} MockImage;
static MockEpoxy* mock = nullptr;
static bool display_initialized = false;
static MockDisplay mock_display;
static MockConfig mock_config;
static MockContext mock_context;
static MockSurface mock_surface;
static MockImage mock_image;
static EGLint mock_error = EGL_SUCCESS;
@ -148,6 +152,10 @@ EGLContext _eglCreateContext(EGLDisplay dpy,
return &mock_context;
}
EGLContext _eglGetCurrentContext() {
return &mock_context;
}
EGLSurface _eglCreatePbufferSurface(EGLDisplay dpy,
EGLConfig config,
const EGLint* attrib_list) {
@ -271,6 +279,10 @@ EGLDisplay _eglGetDisplay(EGLNativeDisplayType display_id) {
return &mock_display;
}
EGLDisplay _eglGetCurrentDisplay() {
return &mock_display;
}
EGLint _eglGetError() {
EGLint error = mock_error;
mock_error = EGL_SUCCESS;
@ -357,6 +369,15 @@ EGLBoolean _eglSwapBuffers(EGLDisplay dpy, EGLSurface surface) {
return bool_success();
}
EGLImage _eglCreateImage(EGLDisplay dpy,
EGLContext ctx,
EGLenum target,
EGLClientBuffer buffer,
const EGLAttrib* attrib_list) {
mock->eglCreateImage(dpy, ctx, target, buffer, attrib_list);
return &mock_image;
}
static GLuint bound_texture_2d;
static std::map<GLenum, GLuint> framebuffer_renderbuffers;
@ -599,6 +620,11 @@ EGLBoolean (*epoxy_eglMakeCurrent)(EGLDisplay dpy,
EGLSurface read,
EGLContext ctx);
EGLBoolean (*epoxy_eglSwapBuffers)(EGLDisplay dpy, EGLSurface surface);
EGLImage (*epoxy_eglCreateImage)(EGLDisplay dpy,
EGLContext ctx,
EGLenum target,
EGLClientBuffer buffer,
const EGLAttrib* attrib_list);
void (*epoxy_glAttachShader)(GLuint program, GLuint shader);
void (*epoxy_glBindFramebuffer)(GLenum target, GLuint framebuffer);
@ -661,16 +687,19 @@ static void library_init() {
epoxy_eglBindAPI = _eglBindAPI;
epoxy_eglChooseConfig = _eglChooseConfig;
epoxy_eglCreateContext = _eglCreateContext;
epoxy_eglGetCurrentContext = _eglGetCurrentContext;
epoxy_eglCreatePbufferSurface = _eglCreatePbufferSurface;
epoxy_eglCreateWindowSurface = _eglCreateWindowSurface;
epoxy_eglGetConfigAttrib = _eglGetConfigAttrib;
epoxy_eglGetDisplay = _eglGetDisplay;
epoxy_eglGetCurrentDisplay = _eglGetCurrentDisplay;
epoxy_eglGetError = _eglGetError;
epoxy_eglGetProcAddress = _eglGetProcAddress;
epoxy_eglInitialize = _eglInitialize;
epoxy_eglMakeCurrent = _eglMakeCurrent;
epoxy_eglQueryContext = _eglQueryContext;
epoxy_eglSwapBuffers = _eglSwapBuffers;
epoxy_eglCreateImage = _eglCreateImage;
epoxy_glAttachShader = _glAttachShader;
epoxy_glBindFramebuffer = _glBindFramebuffer;

View File

@ -21,6 +21,13 @@ class MockEpoxy {
MOCK_METHOD(bool, epoxy_has_gl_extension, (const char* extension));
MOCK_METHOD(bool, epoxy_is_desktop_gl, ());
MOCK_METHOD(int, epoxy_gl_version, ());
MOCK_METHOD(void,
eglCreateImage,
(EGLDisplay dpy,
EGLContext ctx,
EGLenum target,
EGLClientBuffer buffer,
const EGLAttrib* attrib_list));
MOCK_METHOD(void, glClearColor, (GLfloat r, GLfloat g, GLfloat b, GLfloat a));
MOCK_METHOD(void,
glBlitFramebuffer,