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,