Use glBlitFramebuffer when rendering (#53080)

This is much faster than using a shader which is not required currently
(we are not doing any transformations).
diff --git a/shell/platform/linux/fl_renderer.cc b/shell/platform/linux/fl_renderer.cc
index a2163aa..9db2bee 100644
--- a/shell/platform/linux/fl_renderer.cc
+++ b/shell/platform/linux/fl_renderer.cc
@@ -48,6 +48,9 @@
   // was rendered
   bool had_first_frame;
 
+  // True if we can use glBlitFramebuffer.
+  bool has_gl_framebuffer_blit;
+
   // Shader program.
   GLuint program;
 
@@ -59,7 +62,7 @@
 
 // Returns the log for the given OpenGL shader. Must be freed by the caller.
 static gchar* get_shader_log(GLuint shader) {
-  int log_length;
+  GLint log_length;
   gchar* log;
 
   glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length);
@@ -72,7 +75,7 @@
 
 // Returns the log for the given OpenGL program. Must be freed by the caller.
 static gchar* get_program_log(GLuint program) {
-  int log_length;
+  GLint log_length;
   gchar* log;
 
   glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_length);
@@ -100,6 +103,136 @@
   }
 }
 
+static void setup_shader(FlRenderer* self) {
+  FlRendererPrivate* priv = reinterpret_cast<FlRendererPrivate*>(
+      fl_renderer_get_instance_private(self));
+
+  GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER);
+  glShaderSource(vertex_shader, 1, &vertex_shader_src, nullptr);
+  glCompileShader(vertex_shader);
+  GLint vertex_compile_status;
+  glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &vertex_compile_status);
+  if (vertex_compile_status == GL_FALSE) {
+    g_autofree gchar* shader_log = get_shader_log(vertex_shader);
+    g_warning("Failed to compile vertex shader: %s", shader_log);
+  }
+
+  GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
+  glShaderSource(fragment_shader, 1, &fragment_shader_src, nullptr);
+  glCompileShader(fragment_shader);
+  GLint fragment_compile_status;
+  glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &fragment_compile_status);
+  if (fragment_compile_status == GL_FALSE) {
+    g_autofree gchar* shader_log = get_shader_log(fragment_shader);
+    g_warning("Failed to compile fragment shader: %s", shader_log);
+  }
+
+  priv->program = glCreateProgram();
+  glAttachShader(priv->program, vertex_shader);
+  glAttachShader(priv->program, fragment_shader);
+  glLinkProgram(priv->program);
+
+  GLint link_status;
+  glGetProgramiv(priv->program, GL_LINK_STATUS, &link_status);
+  if (link_status == GL_FALSE) {
+    g_autofree gchar* program_log = get_program_log(priv->program);
+    g_warning("Failed to link program: %s", program_log);
+  }
+
+  glDeleteShader(vertex_shader);
+  glDeleteShader(fragment_shader);
+}
+
+static void render_with_blit(FlRenderer* self) {
+  FlRendererPrivate* priv = reinterpret_cast<FlRendererPrivate*>(
+      fl_renderer_get_instance_private(self));
+
+  // 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);
+
+  for (guint i = 0; i < priv->textures->len; i++) {
+    FlBackingStoreProvider* texture =
+        FL_BACKING_STORE_PROVIDER(g_ptr_array_index(priv->textures, i));
+
+    uint32_t framebuffer_id =
+        fl_backing_store_provider_get_gl_framebuffer_id(texture);
+    glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer_id);
+    GdkRectangle geometry = fl_backing_store_provider_get_geometry(texture);
+    glBlitFramebuffer(0, 0, geometry.width, geometry.height, geometry.x,
+                      geometry.y, geometry.x + geometry.width,
+                      geometry.y + geometry.height, GL_COLOR_BUFFER_BIT,
+                      GL_NEAREST);
+  }
+  glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
+}
+
+static void render_with_textures(FlRenderer* self, int width, int height) {
+  FlRendererPrivate* priv = reinterpret_cast<FlRendererPrivate*>(
+      fl_renderer_get_instance_private(self));
+
+  // 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.
+  GLint saved_texture_binding;
+  glGetIntegerv(GL_TEXTURE_BINDING_2D, &saved_texture_binding);
+  GLint saved_vao_binding;
+  glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &saved_vao_binding);
+  GLint saved_array_buffer_binding;
+  glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &saved_array_buffer_binding);
+
+  glUseProgram(priv->program);
+
+  for (guint i = 0; i < priv->textures->len; i++) {
+    FlBackingStoreProvider* texture =
+        FL_BACKING_STORE_PROVIDER(g_ptr_array_index(priv->textures, i));
+
+    uint32_t texture_id = fl_backing_store_provider_get_gl_texture_id(texture);
+    glBindTexture(GL_TEXTURE_2D, texture_id);
+
+    // Translate into OpenGL co-ordinates
+    GdkRectangle texture_geometry =
+        fl_backing_store_provider_get_geometry(texture);
+    GLfloat texture_x = texture_geometry.x;
+    GLfloat texture_y = texture_geometry.y;
+    GLfloat texture_width = texture_geometry.width;
+    GLfloat texture_height = texture_geometry.height;
+    GLfloat x0 = pixels_to_gl_coords(texture_x, width);
+    GLfloat y0 =
+        pixels_to_gl_coords(height - (texture_y + texture_height), height);
+    GLfloat x1 = pixels_to_gl_coords(texture_x + texture_width, width);
+    GLfloat y1 = pixels_to_gl_coords(height - texture_y, height);
+    GLfloat vertex_data[] = {x0, y0, 0, 0, x1, y1, 1, 1, x0, y1, 0, 1,
+                             x0, y0, 0, 0, x1, y0, 1, 0, x1, y1, 1, 1};
+
+    GLuint vao, vertex_buffer;
+    glGenVertexArrays(1, &vao);
+    glBindVertexArray(vao);
+    glGenBuffers(1, &vertex_buffer);
+    glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);
+    glBufferData(GL_ARRAY_BUFFER, sizeof(vertex_data), vertex_data,
+                 GL_STATIC_DRAW);
+    GLint position_index = glGetAttribLocation(priv->program, "position");
+    glEnableVertexAttribArray(position_index);
+    glVertexAttribPointer(position_index, 2, GL_FLOAT, GL_FALSE,
+                          sizeof(GLfloat) * 4, 0);
+    GLint texcoord_index = glGetAttribLocation(priv->program, "in_texcoord");
+    glEnableVertexAttribArray(texcoord_index);
+    glVertexAttribPointer(texcoord_index, 2, GL_FLOAT, GL_FALSE,
+                          sizeof(GLfloat) * 4, (void*)(sizeof(GLfloat) * 2));
+
+    glDrawArrays(GL_TRIANGLES, 0, 6);
+
+    glDeleteVertexArrays(1, &vao);
+    glDeleteBuffers(1, &vertex_buffer);
+  }
+
+  glBindTexture(GL_TEXTURE_2D, saved_texture_binding);
+  glBindVertexArray(saved_vao_binding);
+  glBindBuffer(GL_ARRAY_BUFFER, saved_array_buffer_binding);
+}
+
 static void fl_renderer_dispose(GObject* object) {
   FlRenderer* self = FL_RENDERER(object);
   FlRendererPrivate* priv = reinterpret_cast<FlRendererPrivate*>(
@@ -244,10 +377,6 @@
 
   fl_renderer_unblock_main_thread(self);
 
-  if (!priv->view) {
-    return FALSE;
-  }
-
   g_ptr_array_set_size(priv->textures, 0);
   for (size_t i = 0; i < layers_count; ++i) {
     const FlutterLayer* layer = layers[i];
@@ -266,7 +395,9 @@
     }
   }
 
-  fl_view_redraw(priv->view);
+  if (priv->view != nullptr) {
+    fl_view_redraw(priv->view);
+  }
 
   return TRUE;
 }
@@ -277,40 +408,13 @@
 
   g_return_if_fail(FL_IS_RENDERER(self));
 
-  GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER);
-  glShaderSource(vertex_shader, 1, &vertex_shader_src, nullptr);
-  glCompileShader(vertex_shader);
-  int vertex_compile_status;
-  glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &vertex_compile_status);
-  if (vertex_compile_status == GL_FALSE) {
-    g_autofree gchar* shader_log = get_shader_log(vertex_shader);
-    g_warning("Failed to compile vertex shader: %s", shader_log);
+  priv->has_gl_framebuffer_blit =
+      epoxy_gl_version() >= 30 ||
+      epoxy_has_gl_extension("GL_EXT_framebuffer_blit");
+
+  if (!priv->has_gl_framebuffer_blit) {
+    setup_shader(self);
   }
-
-  GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
-  glShaderSource(fragment_shader, 1, &fragment_shader_src, nullptr);
-  glCompileShader(fragment_shader);
-  int fragment_compile_status;
-  glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &fragment_compile_status);
-  if (fragment_compile_status == GL_FALSE) {
-    g_autofree gchar* shader_log = get_shader_log(fragment_shader);
-    g_warning("Failed to compile fragment shader: %s", shader_log);
-  }
-
-  priv->program = glCreateProgram();
-  glAttachShader(priv->program, vertex_shader);
-  glAttachShader(priv->program, fragment_shader);
-  glLinkProgram(priv->program);
-
-  int link_status;
-  glGetProgramiv(priv->program, GL_LINK_STATUS, &link_status);
-  if (link_status == GL_FALSE) {
-    g_autofree gchar* program_log = get_program_log(priv->program);
-    g_warning("Failed to link program: %s", program_log);
-  }
-
-  glDeleteShader(vertex_shader);
-  glDeleteShader(fragment_shader);
 }
 
 void fl_renderer_render(FlRenderer* self, int width, int height) {
@@ -319,70 +423,16 @@
 
   g_return_if_fail(FL_IS_RENDERER(self));
 
-  // 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.
-  GLint saved_texture_binding;
-  glGetIntegerv(GL_TEXTURE_BINDING_2D, &saved_texture_binding);
-  GLint saved_vao_binding;
-  glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &saved_vao_binding);
-  GLint saved_array_buffer_binding;
-  glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &saved_array_buffer_binding);
-
   glClearColor(0.0, 0.0, 0.0, 1.0);
   glClear(GL_COLOR_BUFFER_BIT);
 
-  glUseProgram(priv->program);
-
-  for (guint i = 0; i < priv->textures->len; i++) {
-    FlBackingStoreProvider* texture =
-        FL_BACKING_STORE_PROVIDER(g_ptr_array_index(priv->textures, i));
-
-    uint32_t texture_id = fl_backing_store_provider_get_gl_texture_id(texture);
-    glBindTexture(GL_TEXTURE_2D, texture_id);
-
-    // Translate into OpenGL co-ordinates
-    GdkRectangle texture_geometry =
-        fl_backing_store_provider_get_geometry(texture);
-    GLfloat texture_x = texture_geometry.x;
-    GLfloat texture_y = texture_geometry.y;
-    GLfloat texture_width = texture_geometry.width;
-    GLfloat texture_height = texture_geometry.height;
-    GLfloat x0 = pixels_to_gl_coords(texture_x, width);
-    GLfloat y0 =
-        pixels_to_gl_coords(height - (texture_y + texture_height), height);
-    GLfloat x1 = pixels_to_gl_coords(texture_x + texture_width, width);
-    GLfloat y1 = pixels_to_gl_coords(height - texture_y, height);
-    GLfloat vertex_data[] = {x0, y0, 0, 0, x1, y1, 1, 1, x0, y1, 0, 1,
-                             x0, y0, 0, 0, x1, y0, 1, 0, x1, y1, 1, 1};
-
-    GLuint vao, vertex_buffer;
-    glGenVertexArrays(1, &vao);
-    glBindVertexArray(vao);
-    glGenBuffers(1, &vertex_buffer);
-    glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);
-    glBufferData(GL_ARRAY_BUFFER, sizeof(vertex_data), vertex_data,
-                 GL_STATIC_DRAW);
-    GLint position_index = glGetAttribLocation(priv->program, "position");
-    glEnableVertexAttribArray(position_index);
-    glVertexAttribPointer(position_index, 2, GL_FLOAT, GL_FALSE,
-                          sizeof(GLfloat) * 4, 0);
-    GLint texcoord_index = glGetAttribLocation(priv->program, "in_texcoord");
-    glEnableVertexAttribArray(texcoord_index);
-    glVertexAttribPointer(texcoord_index, 2, GL_FLOAT, GL_FALSE,
-                          sizeof(GLfloat) * 4, (void*)(sizeof(GLfloat) * 2));
-
-    glDrawArrays(GL_TRIANGLES, 0, 6);
-
-    glDeleteVertexArrays(1, &vao);
-    glDeleteBuffers(1, &vertex_buffer);
+  if (priv->has_gl_framebuffer_blit) {
+    render_with_blit(self);
+  } else {
+    render_with_textures(self, width, height);
   }
 
   glFlush();
-
-  glBindTexture(GL_TEXTURE_2D, saved_texture_binding);
-  glBindVertexArray(saved_vao_binding);
-  glBindBuffer(GL_ARRAY_BUFFER, saved_array_buffer_binding);
 }
 
 void fl_renderer_cleanup(FlRenderer* self) {
@@ -391,5 +441,7 @@
 
   g_return_if_fail(FL_IS_RENDERER(self));
 
-  glDeleteProgram(priv->program);
+  if (priv->program != 0) {
+    glDeleteProgram(priv->program);
+  }
 }
diff --git a/shell/platform/linux/fl_renderer_test.cc b/shell/platform/linux/fl_renderer_test.cc
index a44e546..7c5144d 100644
--- a/shell/platform/linux/fl_renderer_test.cc
+++ b/shell/platform/linux/fl_renderer_test.cc
@@ -4,14 +4,17 @@
 
 #include "gtest/gtest.h"
 
-#include <epoxy/egl.h>
-
 #include "flutter/fml/logging.h"
 #include "flutter/shell/platform/linux/fl_backing_store_provider.h"
 #include "flutter/shell/platform/linux/testing/fl_test_gtk_logs.h"
+#include "flutter/shell/platform/linux/testing/mock_epoxy.h"
 #include "flutter/shell/platform/linux/testing/mock_renderer.h"
 
+#include <epoxy/egl.h>
+
 TEST(FlRendererTest, RestoresGLState) {
+  ::testing::NiceMock<flutter::testing::MockEpoxy> epoxy;
+
   constexpr int kWidth = 100;
   constexpr int kHeight = 100;
 
@@ -67,3 +70,88 @@
       fl_renderer_get_refresh_rate(FL_RENDERER(renderer));
   EXPECT_DOUBLE_EQ(result_refresh_rate, kExpectedRefreshRate);
 }
+
+TEST(FlRendererTest, BlitFramebuffer) {
+  ::testing::NiceMock<flutter::testing::MockEpoxy> epoxy;
+
+  // OpenGL 3.0
+  ON_CALL(epoxy, epoxy_is_desktop_gl).WillByDefault(::testing::Return(true));
+  EXPECT_CALL(epoxy, epoxy_gl_version).WillRepeatedly(::testing::Return(30));
+
+  EXPECT_CALL(epoxy, glBlitFramebuffer);
+
+  g_autoptr(FlMockRenderer) renderer =
+      fl_mock_renderer_new(&renderer_get_refresh_rate);
+  fl_renderer_setup(FL_RENDERER(renderer));
+  fl_renderer_wait_for_frame(FL_RENDERER(renderer), 1024, 1024);
+  FlutterBackingStoreConfig config = {
+      .struct_size = sizeof(FlutterBackingStoreConfig),
+      .size = {.width = 1024, .height = 1024}};
+  FlutterBackingStore backing_store;
+  fl_renderer_create_backing_store(FL_RENDERER(renderer), &config,
+                                   &backing_store);
+  const FlutterLayer layer0 = {.struct_size = sizeof(FlutterLayer),
+                               .type = kFlutterLayerContentTypeBackingStore,
+                               .backing_store = &backing_store,
+                               .size = {.width = 1024, .height = 1024}};
+  const FlutterLayer* layers[] = {&layer0};
+  fl_renderer_present_layers(FL_RENDERER(renderer), layers, 1);
+  fl_renderer_render(FL_RENDERER(renderer), 1024, 1024);
+}
+
+TEST(FlRendererTest, BlitFramebufferExtension) {
+  ::testing::NiceMock<flutter::testing::MockEpoxy> epoxy;
+
+  // OpenGL 2.0 with GL_EXT_framebuffer_blit extension
+  ON_CALL(epoxy, epoxy_is_desktop_gl).WillByDefault(::testing::Return(true));
+  EXPECT_CALL(epoxy, epoxy_gl_version).WillRepeatedly(::testing::Return(20));
+  EXPECT_CALL(epoxy, epoxy_has_gl_extension(
+                         ::testing::StrEq("GL_EXT_framebuffer_blit")))
+      .WillRepeatedly(::testing::Return(true));
+
+  EXPECT_CALL(epoxy, glBlitFramebuffer);
+
+  g_autoptr(FlMockRenderer) renderer =
+      fl_mock_renderer_new(&renderer_get_refresh_rate);
+  fl_renderer_setup(FL_RENDERER(renderer));
+  fl_renderer_wait_for_frame(FL_RENDERER(renderer), 1024, 1024);
+  FlutterBackingStoreConfig config = {
+      .struct_size = sizeof(FlutterBackingStoreConfig),
+      .size = {.width = 1024, .height = 1024}};
+  FlutterBackingStore backing_store;
+  fl_renderer_create_backing_store(FL_RENDERER(renderer), &config,
+                                   &backing_store);
+  const FlutterLayer layer0 = {.struct_size = sizeof(FlutterLayer),
+                               .type = kFlutterLayerContentTypeBackingStore,
+                               .backing_store = &backing_store,
+                               .size = {.width = 1024, .height = 1024}};
+  const FlutterLayer* layers[] = {&layer0};
+  fl_renderer_present_layers(FL_RENDERER(renderer), layers, 1);
+  fl_renderer_render(FL_RENDERER(renderer), 1024, 1024);
+}
+
+TEST(FlRendererTest, NoBlitFramebuffer) {
+  ::testing::NiceMock<flutter::testing::MockEpoxy> epoxy;
+
+  // OpenGL 2.0
+  ON_CALL(epoxy, epoxy_is_desktop_gl).WillByDefault(::testing::Return(true));
+  EXPECT_CALL(epoxy, epoxy_gl_version).WillRepeatedly(::testing::Return(20));
+
+  g_autoptr(FlMockRenderer) renderer =
+      fl_mock_renderer_new(&renderer_get_refresh_rate);
+  fl_renderer_setup(FL_RENDERER(renderer));
+  fl_renderer_wait_for_frame(FL_RENDERER(renderer), 1024, 1024);
+  FlutterBackingStoreConfig config = {
+      .struct_size = sizeof(FlutterBackingStoreConfig),
+      .size = {.width = 1024, .height = 1024}};
+  FlutterBackingStore backing_store;
+  fl_renderer_create_backing_store(FL_RENDERER(renderer), &config,
+                                   &backing_store);
+  const FlutterLayer layer0 = {.struct_size = sizeof(FlutterLayer),
+                               .type = kFlutterLayerContentTypeBackingStore,
+                               .backing_store = &backing_store,
+                               .size = {.width = 1024, .height = 1024}};
+  const FlutterLayer* layers[] = {&layer0};
+  fl_renderer_present_layers(FL_RENDERER(renderer), layers, 1);
+  fl_renderer_render(FL_RENDERER(renderer), 1024, 1024);
+}
diff --git a/shell/platform/linux/testing/mock_epoxy.cc b/shell/platform/linux/testing/mock_epoxy.cc
index e82e626..0b60e9d 100644
--- a/shell/platform/linux/testing/mock_epoxy.cc
+++ b/shell/platform/linux/testing/mock_epoxy.cc
@@ -3,8 +3,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <epoxy/egl.h>
-#include <epoxy/gl.h>
+#include "flutter/shell/platform/linux/testing/mock_epoxy.h"
+
+using namespace flutter::testing;
 
 typedef struct {
   EGLint config_id;
@@ -45,6 +46,7 @@
 typedef struct {
 } MockSurface;
 
+static MockEpoxy* mock = nullptr;
 static bool display_initialized = false;
 static MockDisplay mock_display;
 static MockConfig mock_config;
@@ -53,6 +55,10 @@
 
 static EGLint mock_error = EGL_SUCCESS;
 
+MockEpoxy::MockEpoxy() {
+  mock = this;
+}
+
 static bool check_display(EGLDisplay dpy) {
   if (dpy == nullptr) {
     mock_error = EGL_BAD_DISPLAY;
@@ -346,6 +352,8 @@
 
 static GLuint bound_texture_2d;
 
+void _glAttachShader(GLuint program, GLuint shader) {}
+
 static void _glBindFramebuffer(GLenum target, GLuint framebuffer) {}
 
 static void _glBindTexture(GLenum target, GLuint texture) {
@@ -354,8 +362,34 @@
   }
 }
 
+static void _glBlitFramebuffer(GLint srcX0,
+                               GLint srcY0,
+                               GLint srcX1,
+                               GLint srcY1,
+                               GLint dstX0,
+                               GLint dstY0,
+                               GLint dstX1,
+                               GLint dstY1,
+                               GLbitfield mask,
+                               GLenum filter) {
+  mock->glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1,
+                          dstY1, mask, filter);
+}
+
+GLuint _glCreateProgram() {
+  return 0;
+}
+
+void _glCompileShader(GLuint shader) {}
+
+GLuint _glCreateShader(GLenum shaderType) {
+  return 0;
+}
+
 void _glDeleteFramebuffers(GLsizei n, const GLuint* framebuffers) {}
 
+void _glDeleteShader(GLuint shader) {}
+
 void _glDeleteTextures(GLsizei n, const GLuint* textures) {}
 
 static void _glFramebufferTexture2D(GLenum target,
@@ -382,6 +416,28 @@
   }
 }
 
+static void _glGetProgramiv(GLuint program, GLenum pname, GLint* params) {
+  if (pname == GL_LINK_STATUS) {
+    *params = GL_TRUE;
+  }
+}
+
+static void _glGetProgramInfoLog(GLuint program,
+                                 GLsizei maxLength,
+                                 GLsizei* length,
+                                 GLchar* infoLog) {}
+
+static void _glGetShaderiv(GLuint shader, GLenum pname, GLint* params) {
+  if (pname == GL_COMPILE_STATUS) {
+    *params = GL_TRUE;
+  }
+}
+
+static void _glGetShaderInfoLog(GLuint shader,
+                                GLsizei maxLength,
+                                GLsizei* length,
+                                GLchar* infoLog) {}
+
 static void _glTexParameterf(GLenum target, GLenum pname, GLfloat param) {}
 
 static void _glTexParameteri(GLenum target, GLenum pname, GLint param) {}
@@ -400,16 +456,23 @@
   return GL_NO_ERROR;
 }
 
+void _glLinkProgram(GLuint program) {}
+
+void _glShaderSource(GLuint shader,
+                     GLsizei count,
+                     const GLchar* const* string,
+                     const GLint* length) {}
+
 bool epoxy_has_gl_extension(const char* extension) {
-  return false;
+  return mock->epoxy_has_gl_extension(extension);
 }
 
 bool epoxy_is_desktop_gl(void) {
-  return false;
+  return mock->epoxy_is_desktop_gl();
 }
 
 int epoxy_gl_version(void) {
-  return 0;
+  return mock->epoxy_gl_version();
 }
 
 #ifdef __GNUC__
@@ -463,9 +526,24 @@
                                    EGLContext ctx);
 EGLBoolean (*epoxy_eglSwapBuffers)(EGLDisplay dpy, EGLSurface surface);
 
+void (*epoxy_glAttachShader)(GLuint program, GLuint shader);
 void (*epoxy_glBindFramebuffer)(GLenum target, GLuint framebuffer);
 void (*epoxy_glBindTexture)(GLenum target, GLuint texture);
+void (*epoxy_glBlitFramebuffer)(GLint srcX0,
+                                GLint srcY0,
+                                GLint srcX1,
+                                GLint srcY1,
+                                GLint dstX0,
+                                GLint dstY0,
+                                GLint dstX1,
+                                GLint dstY1,
+                                GLbitfield mask,
+                                GLenum filter);
+void (*epoxy_glCompileShader)(GLuint shader);
+GLuint (*epoxy_glCreateProgram)();
+GLuint (*epoxy_glCreateShader)(GLenum shaderType);
 void (*epoxy_glDeleteFramebuffers)(GLsizei n, const GLuint* framebuffers);
+void (*expoxy_glDeleteShader)(GLuint shader);
 void (*epoxy_glDeleteTextures)(GLsizei n, const GLuint* textures);
 void (*epoxy_glFramebufferTexture2D)(GLenum target,
                                      GLenum attachment,
@@ -474,6 +552,11 @@
                                      GLint level);
 void (*epoxy_glGenFramebuffers)(GLsizei n, GLuint* framebuffers);
 void (*epoxy_glGenTextures)(GLsizei n, GLuint* textures);
+void (*epoxy_glLinkProgram)(GLuint program);
+void (*epoxy_glShaderSource)(GLuint shader,
+                             GLsizei count,
+                             const GLchar* const* string,
+                             const GLint* length);
 void (*epoxy_glTexParameterf)(GLenum target, GLenum pname, GLfloat param);
 void (*epoxy_glTexParameteri)(GLenum target, GLenum pname, GLint param);
 void (*epoxy_glTexImage2D)(GLenum target,
@@ -502,14 +585,26 @@
   epoxy_eglQueryContext = _eglQueryContext;
   epoxy_eglSwapBuffers = _eglSwapBuffers;
 
+  epoxy_glAttachShader = _glAttachShader;
   epoxy_glBindFramebuffer = _glBindFramebuffer;
   epoxy_glBindTexture = _glBindTexture;
+  epoxy_glBlitFramebuffer = _glBlitFramebuffer;
+  epoxy_glCompileShader = _glCompileShader;
+  epoxy_glCreateProgram = _glCreateProgram;
+  epoxy_glCreateShader = _glCreateShader;
   epoxy_glDeleteFramebuffers = _glDeleteFramebuffers;
+  epoxy_glDeleteShader = _glDeleteShader;
   epoxy_glDeleteTextures = _glDeleteTextures;
   epoxy_glFramebufferTexture2D = _glFramebufferTexture2D;
   epoxy_glGenFramebuffers = _glGenFramebuffers;
   epoxy_glGenTextures = _glGenTextures;
   epoxy_glGetIntegerv = _glGetIntegerv;
+  epoxy_glGetProgramiv = _glGetProgramiv;
+  epoxy_glGetProgramInfoLog = _glGetProgramInfoLog;
+  epoxy_glGetShaderiv = _glGetShaderiv;
+  epoxy_glGetShaderInfoLog = _glGetShaderInfoLog;
+  epoxy_glLinkProgram = _glLinkProgram;
+  epoxy_glShaderSource = _glShaderSource;
   epoxy_glTexParameterf = _glTexParameterf;
   epoxy_glTexParameteri = _glTexParameteri;
   epoxy_glTexImage2D = _glTexImage2D;
diff --git a/shell/platform/linux/testing/mock_epoxy.h b/shell/platform/linux/testing/mock_epoxy.h
new file mode 100644
index 0000000..c21f9ee
--- /dev/null
+++ b/shell/platform/linux/testing/mock_epoxy.h
@@ -0,0 +1,40 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FLUTTER_SHELL_PLATFORM_LINUX_TESTING_MOCK_EPOXY_H_
+#define FLUTTER_SHELL_PLATFORM_LINUX_TESTING_MOCK_EPOXY_H_
+
+#include "gmock/gmock.h"
+
+#include <epoxy/egl.h>
+#include <epoxy/gl.h>
+
+namespace flutter {
+namespace testing {
+
+class MockEpoxy {
+ public:
+  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,
+              glBlitFramebuffer,
+              (GLint srcX0,
+               GLint srcY0,
+               GLint srcX1,
+               GLint srcY1,
+               GLint dstX0,
+               GLint dstY0,
+               GLint dstX1,
+               GLint dstY1,
+               GLbitfield mask,
+               GLenum filter));
+};
+
+}  // namespace testing
+}  // namespace flutter
+
+#endif  // FLUTTER_SHELL_PLATFORM_LINUX_TESTING_MOCK_EPOXY_H_