From e983e59620709889ad7aadc7702af3000d4429a0 Mon Sep 17 00:00:00 2001 From: Robert Ancell Date: Thu, 17 Jul 2025 12:09:53 +1200 Subject: [PATCH] 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. --- .../shell/platform/linux/fl_compositor.cc | 6 - .../shell/platform/linux/fl_compositor.h | 12 - .../platform/linux/fl_compositor_opengl.cc | 360 ++++++++---------- .../platform/linux/fl_compositor_opengl.h | 14 +- .../linux/fl_compositor_opengl_test.cc | 33 +- .../platform/linux/fl_compositor_software.cc | 9 +- .../flutter/shell/platform/linux/fl_engine.cc | 3 +- .../shell/platform/linux/fl_framebuffer.cc | 97 ++++- .../shell/platform/linux/fl_framebuffer.h | 40 +- .../platform/linux/fl_framebuffer_test.cc | 14 +- .../flutter/shell/platform/linux/fl_view.cc | 23 +- .../platform/linux/testing/mock_epoxy.cc | 29 ++ .../shell/platform/linux/testing/mock_epoxy.h | 7 + 13 files changed, 344 insertions(+), 303 deletions(-) diff --git a/engine/src/flutter/shell/platform/linux/fl_compositor.cc b/engine/src/flutter/shell/platform/linux/fl_compositor.cc index 0c98d6da061..2b69d2db0f6 100644 --- a/engine/src/flutter/shell/platform/linux/fl_compositor.cc +++ b/engine/src/flutter/shell/platform/linux/fl_compositor.cc @@ -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(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) { diff --git a/engine/src/flutter/shell/platform/linux/fl_compositor.h b/engine/src/flutter/shell/platform/linux/fl_compositor.h index 5055166e91b..d931c8937ad 100644 --- a/engine/src/flutter/shell/platform/linux/fl_compositor.h +++ b/engine/src/flutter/shell/platform/linux/fl_compositor.h @@ -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. diff --git a/engine/src/flutter/shell/platform/linux/fl_compositor_opengl.cc b/engine/src/flutter/shell/platform/linux/fl_compositor_opengl.cc index 238292dc2d8..851f145726c 100644 --- a/engine/src/flutter/shell/platform/linux/fl_compositor_opengl.cc +++ b/engine/src/flutter/shell/platform/linux/fl_compositor_opengl.cc @@ -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(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(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(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(); } diff --git a/engine/src/flutter/shell/platform/linux/fl_compositor_opengl.h b/engine/src/flutter/shell/platform/linux/fl_compositor_opengl.h index 96f4aedfc6f..576e839e2a9 100644 --- a/engine/src/flutter/shell/platform/linux/fl_compositor_opengl.h +++ b/engine/src/flutter/shell/platform/linux/fl_compositor_opengl.h @@ -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: diff --git a/engine/src/flutter/shell/platform/linux/fl_compositor_opengl_test.cc b/engine/src/flutter/shell/platform/linux/fl_compositor_opengl_test.cc index 3406d968f40..30dc2e80078 100644 --- a/engine/src/flutter/shell/platform/linux/fl_compositor_opengl_test.cc +++ b/engine/src/flutter/shell/platform/linux/fl_compositor_opengl_test.cc @@ -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(&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}}}; diff --git a/engine/src/flutter/shell/platform/linux/fl_compositor_software.cc b/engine/src/flutter/shell/platform/linux/fl_compositor_software.cc index 99c1a9e39f1..7baddf2ffc1 100644 --- a/engine/src/flutter/shell/platform/linux/fl_compositor_software.cc +++ b/engine/src/flutter/shell/platform/linux/fl_compositor_software.cc @@ -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) { diff --git a/engine/src/flutter/shell/platform/linux/fl_engine.cc b/engine/src/flutter/shell/platform/linux/fl_engine.cc index 2c54fe05417..528d42ff643 100644 --- a/engine/src/flutter/shell/platform/linux/fl_engine.cc +++ b/engine/src/flutter/shell/platform/linux/fl_engine.cc @@ -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)); diff --git a/engine/src/flutter/shell/platform/linux/fl_framebuffer.cc b/engine/src/flutter/shell/platform/linux/fl_framebuffer.cc index f6374fa7e77..2784551a6d0 100644 --- a/engine/src/flutter/shell/platform/linux/fl_framebuffer.cc +++ b/engine/src/flutter/shell/platform/linux/fl_framebuffer.cc @@ -4,6 +4,7 @@ #include "fl_framebuffer.h" +#include #include 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(static_cast(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; } diff --git a/engine/src/flutter/shell/platform/linux/fl_framebuffer.h b/engine/src/flutter/shell/platform/linux/fl_framebuffer.h index 0df5d155c3e..9192e5476a4 100644 --- a/engine/src/flutter/shell/platform/linux/fl_framebuffer.h +++ b/engine/src/flutter/shell/platform/linux/fl_framebuffer.h @@ -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. diff --git a/engine/src/flutter/shell/platform/linux/fl_framebuffer_test.cc b/engine/src/flutter/shell/platform/linux/fl_framebuffer_test.cc index f18f6570fe7..55eb4767bbc 100644 --- a/engine/src/flutter/shell/platform/linux/fl_framebuffer_test.cc +++ b/engine/src/flutter/shell/platform/linux/fl_framebuffer_test.cc @@ -10,7 +10,8 @@ TEST(FlFramebufferTest, HasDepthStencil) { ::testing::NiceMock 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 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); +} diff --git a/engine/src/flutter/shell/platform/linux/fl_view.cc b/engine/src/flutter/shell/platform/linux/fl_view.cc index bf526469094..5f40e02c56e 100644 --- a/engine/src/flutter/shell/platform/linux/fl_view.cc +++ b/engine/src/flutter/shell/platform/linux/fl_view.cc @@ -5,6 +5,7 @@ #include "flutter/shell/platform/linux/public/flutter_linux/fl_view.h" #include +#include #include #include @@ -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)); } diff --git a/engine/src/flutter/shell/platform/linux/testing/mock_epoxy.cc b/engine/src/flutter/shell/platform/linux/testing/mock_epoxy.cc index ff138d5c5c9..54cc604edde 100644 --- a/engine/src/flutter/shell/platform/linux/testing/mock_epoxy.cc +++ b/engine/src/flutter/shell/platform/linux/testing/mock_epoxy.cc @@ -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 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; diff --git a/engine/src/flutter/shell/platform/linux/testing/mock_epoxy.h b/engine/src/flutter/shell/platform/linux/testing/mock_epoxy.h index 94f0d084c0a..f001cf4c0d5 100644 --- a/engine/src/flutter/shell/platform/linux/testing/mock_epoxy.h +++ b/engine/src/flutter/shell/platform/linux/testing/mock_epoxy.h @@ -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,