When rendering into secondary views, copy the pixel contents between contexts (#55639)

This is because GTK3 can't share contexts between GtkGLAreas.

We may be able to avoid the copy using the GL_OES_EGL_image or
GL_OES_EGL_image_external extensions.

Note that keyboard input doesn't work on the secondary view, this
requires moving the keyboard handling from FlView to FlEngine. Proposing
this now as a step forwards, but not yet useful for real applications.
diff --git a/shell/platform/linux/fl_renderable.cc b/shell/platform/linux/fl_renderable.cc
index b8a44a5..a3e7eb1 100644
--- a/shell/platform/linux/fl_renderable.cc
+++ b/shell/platform/linux/fl_renderable.cc
@@ -13,3 +13,9 @@
 
   FL_RENDERABLE_GET_IFACE(self)->redraw(self);
 }
+
+void fl_renderable_make_current(FlRenderable* self) {
+  g_return_if_fail(FL_IS_RENDERABLE(self));
+
+  FL_RENDERABLE_GET_IFACE(self)->make_current(self);
+}
diff --git a/shell/platform/linux/fl_renderable.h b/shell/platform/linux/fl_renderable.h
index 8a17caa..82773b2 100644
--- a/shell/platform/linux/fl_renderable.h
+++ b/shell/platform/linux/fl_renderable.h
@@ -26,6 +26,7 @@
   GTypeInterface g_iface;
 
   void (*redraw)(FlRenderable* renderable);
+  void (*make_current)(FlRenderable* renderable);
 };
 
 /**
@@ -37,6 +38,14 @@
  */
 void fl_renderable_redraw(FlRenderable* renderable);
 
+/**
+ * fl_renderable_make_current:
+ * @renderable: an #FlRenderable
+ *
+ * Make this renderable the current OpenGL context.
+ */
+void fl_renderable_make_current(FlRenderable* renderable);
+
 G_END_DECLS
 
 #endif  // FLUTTER_SHELL_PLATFORM_LINUX_FL_RENDERABLE_H_
diff --git a/shell/platform/linux/fl_renderer.cc b/shell/platform/linux/fl_renderer.cc
index 64a891b..63e0a81 100644
--- a/shell/platform/linux/fl_renderer.cc
+++ b/shell/platform/linux/fl_renderer.cc
@@ -7,6 +7,7 @@
 #include <epoxy/egl.h>
 #include <epoxy/gl.h>
 
+#include "flutter/common/constants.h"
 #include "flutter/shell/platform/embedder/embedder.h"
 #include "flutter/shell/platform/linux/fl_engine_private.h"
 #include "flutter/shell/platform/linux/fl_framebuffer.h"
@@ -279,6 +280,20 @@
   glBindBuffer(GL_ARRAY_BUFFER, saved_array_buffer_binding);
 }
 
+static void render(FlRenderer* self,
+                   GPtrArray* framebuffers,
+                   int width,
+                   int height) {
+  FlRendererPrivate* priv = reinterpret_cast<FlRendererPrivate*>(
+      fl_renderer_get_instance_private(self));
+
+  if (priv->has_gl_framebuffer_blit) {
+    render_with_blit(self, framebuffers);
+  } else {
+    render_with_textures(self, framebuffers, width, height);
+  }
+}
+
 static void fl_renderer_dispose(GObject* object) {
   FlRenderer* self = FL_RENDERER(object);
   FlRendererPrivate* priv = reinterpret_cast<FlRendererPrivate*>(
@@ -455,14 +470,8 @@
 
   fl_renderer_unblock_main_thread(self);
 
-  GPtrArray* framebuffers = reinterpret_cast<GPtrArray*>((g_hash_table_lookup(
-      priv->framebuffers_by_view_id, GINT_TO_POINTER(view_id))));
-  if (framebuffers == nullptr) {
-    framebuffers = g_ptr_array_new_with_free_func(g_object_unref);
-    g_hash_table_insert(priv->framebuffers_by_view_id, GINT_TO_POINTER(view_id),
-                        framebuffers);
-  }
-  g_ptr_array_set_size(framebuffers, 0);
+  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) {
@@ -483,10 +492,73 @@
       g_hash_table_lookup(priv->views, GINT_TO_POINTER(view_id)));
   g_autoptr(FlRenderable) renderable =
       ref != nullptr ? FL_RENDERABLE(g_weak_ref_get(ref)) : nullptr;
-  if (renderable != nullptr) {
-    fl_renderable_redraw(renderable);
+  if (renderable == nullptr) {
+    return TRUE;
   }
 
+  if (view_id == flutter::kFlutterImplicitViewId) {
+    // Store for rendering later
+    g_hash_table_insert(priv->framebuffers_by_view_id, GINT_TO_POINTER(view_id),
+                        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(priv->general_format, width, height);
+      glBindFramebuffer(GL_DRAW_FRAMEBUFFER,
+                        fl_framebuffer_get_id(view_framebuffer));
+      render(self, framebuffers, width, height);
+      g_ptr_array_set_size(framebuffers, 0);
+      g_ptr_array_add(framebuffers, view_framebuffer);
+    }
+
+    // Read back pixel values.
+    FlFramebuffer* framebuffer =
+        FL_FRAMEBUFFER(g_ptr_array_index(framebuffers, 0));
+    size_t width = fl_framebuffer_get_width(framebuffer);
+    size_t height = fl_framebuffer_get_height(framebuffer);
+    size_t data_length = width * height * 4;
+    g_autofree uint8_t* data = static_cast<uint8_t*>(malloc(data_length));
+    glBindFramebuffer(GL_READ_FRAMEBUFFER, fl_framebuffer_get_id(framebuffer));
+    glReadPixels(0, 0, width, height, priv->general_format, GL_UNSIGNED_BYTE,
+                 data);
+
+    // Write into a texture in the views context.
+    fl_renderable_make_current(renderable);
+    FlFramebuffer* view_framebuffer =
+        fl_framebuffer_new(priv->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_hash_table_insert(priv->framebuffers_by_view_id, GINT_TO_POINTER(view_id),
+                        g_ptr_array_ref(secondary_framebuffers));
+  }
+
+  fl_renderable_redraw(renderable);
+
   return TRUE;
 }
 
@@ -524,11 +596,7 @@
   GPtrArray* framebuffers = reinterpret_cast<GPtrArray*>((g_hash_table_lookup(
       priv->framebuffers_by_view_id, GINT_TO_POINTER(view_id))));
   if (framebuffers != nullptr) {
-    if (priv->has_gl_framebuffer_blit) {
-      render_with_blit(self, framebuffers);
-    } else {
-      render_with_textures(self, framebuffers, width, height);
-    }
+    render(self, framebuffers, width, height);
   }
 
   glFlush();
diff --git a/shell/platform/linux/fl_renderer_test.cc b/shell/platform/linux/fl_renderer_test.cc
index 7c4d83c..c5d2346 100644
--- a/shell/platform/linux/fl_renderer_test.cc
+++ b/shell/platform/linux/fl_renderer_test.cc
@@ -4,6 +4,7 @@
 
 #include "gtest/gtest.h"
 
+#include "flutter/common/constants.h"
 #include "flutter/fml/logging.h"
 #include "flutter/shell/platform/linux/fl_framebuffer.h"
 #include "flutter/shell/platform/linux/testing/mock_epoxy.h"
@@ -21,8 +22,12 @@
           ::testing::Return(reinterpret_cast<const GLubyte*>("Intel")));
   EXPECT_CALL(epoxy, glClearColor(0.2, 0.3, 0.4, 0.5));
 
+  g_autoptr(FlMockRenderable) renderable = fl_mock_renderable_new();
   g_autoptr(FlMockRenderer) renderer = fl_mock_renderer_new();
   fl_renderer_setup(FL_RENDERER(renderer));
+  fl_renderer_add_renderable(FL_RENDERER(renderer),
+                             flutter::kFlutterImplicitViewId,
+                             FL_RENDERABLE(renderable));
   fl_renderer_wait_for_frame(FL_RENDERER(renderer), 1024, 1024);
   FlutterBackingStoreConfig config = {
       .struct_size = sizeof(FlutterBackingStoreConfig),
@@ -35,10 +40,12 @@
                                .backing_store = &backing_store,
                                .size = {.width = 1024, .height = 1024}};
   const FlutterLayer* layers[] = {&layer0};
-  fl_renderer_present_layers(FL_RENDERER(renderer), 0, layers, 1);
+  fl_renderer_present_layers(FL_RENDERER(renderer),
+                             flutter::kFlutterImplicitViewId, layers, 1);
   GdkRGBA background_color = {
       .red = 0.2, .green = 0.3, .blue = 0.4, .alpha = 0.5};
-  fl_renderer_render(FL_RENDERER(renderer), 0, 1024, 1024, &background_color);
+  fl_renderer_render(FL_RENDERER(renderer), flutter::kFlutterImplicitViewId,
+                     1024, 1024, &background_color);
 }
 
 TEST(FlRendererTest, RestoresGLState) {
@@ -47,12 +54,14 @@
   constexpr int kWidth = 100;
   constexpr int kHeight = 100;
 
-  g_autoptr(FlRenderable) renderable = FL_RENDERABLE(fl_mock_renderable_new());
+  g_autoptr(FlMockRenderable) renderable = fl_mock_renderable_new();
   g_autoptr(FlMockRenderer) renderer = fl_mock_renderer_new();
   g_autoptr(FlFramebuffer) framebuffer =
       fl_framebuffer_new(GL_RGB, kWidth, kHeight);
 
-  fl_renderer_add_renderable(FL_RENDERER(renderer), 0, renderable);
+  fl_renderer_add_renderable(FL_RENDERER(renderer),
+                             flutter::kFlutterImplicitViewId,
+                             FL_RENDERABLE(renderable));
   fl_renderer_wait_for_frame(FL_RENDERER(renderer), kWidth, kHeight);
 
   FlutterBackingStore backing_store;
@@ -70,12 +79,13 @@
   constexpr GLuint kFakeTextureName = 123;
   glBindTexture(GL_TEXTURE_2D, kFakeTextureName);
 
-  fl_renderer_present_layers(FL_RENDERER(renderer), 0, layers.data(),
+  fl_renderer_present_layers(FL_RENDERER(renderer),
+                             flutter::kFlutterImplicitViewId, layers.data(),
                              layers.size());
   GdkRGBA background_color = {
       .red = 0.0, .green = 0.0, .blue = 0.0, .alpha = 1.0};
-  fl_renderer_render(FL_RENDERER(renderer), 0, kWidth, kHeight,
-                     &background_color);
+  fl_renderer_render(FL_RENDERER(renderer), flutter::kFlutterImplicitViewId,
+                     kWidth, kHeight, &background_color);
 
   GLuint texture_2d_binding;
   glGetIntegerv(GL_TEXTURE_BINDING_2D,
@@ -109,8 +119,12 @@
 
   EXPECT_CALL(epoxy, glBlitFramebuffer);
 
+  g_autoptr(FlMockRenderable) renderable = fl_mock_renderable_new();
   g_autoptr(FlMockRenderer) renderer = fl_mock_renderer_new();
   fl_renderer_setup(FL_RENDERER(renderer));
+  fl_renderer_add_renderable(FL_RENDERER(renderer),
+                             flutter::kFlutterImplicitViewId,
+                             FL_RENDERABLE(renderable));
   fl_renderer_wait_for_frame(FL_RENDERER(renderer), 1024, 1024);
   FlutterBackingStoreConfig config = {
       .struct_size = sizeof(FlutterBackingStoreConfig),
@@ -123,10 +137,12 @@
                                .backing_store = &backing_store,
                                .size = {.width = 1024, .height = 1024}};
   const FlutterLayer* layers[] = {&layer0};
-  fl_renderer_present_layers(FL_RENDERER(renderer), 0, layers, 1);
+  fl_renderer_present_layers(FL_RENDERER(renderer),
+                             flutter::kFlutterImplicitViewId, layers, 1);
   GdkRGBA background_color = {
       .red = 0.0, .green = 0.0, .blue = 0.0, .alpha = 1.0};
-  fl_renderer_render(FL_RENDERER(renderer), 0, 1024, 1024, &background_color);
+  fl_renderer_render(FL_RENDERER(renderer), flutter::kFlutterImplicitViewId,
+                     1024, 1024, &background_color);
 }
 
 TEST(FlRendererTest, BlitFramebufferExtension) {
@@ -146,8 +162,12 @@
 
   EXPECT_CALL(epoxy, glBlitFramebuffer);
 
+  g_autoptr(FlMockRenderable) renderable = fl_mock_renderable_new();
   g_autoptr(FlMockRenderer) renderer = fl_mock_renderer_new();
   fl_renderer_setup(FL_RENDERER(renderer));
+  fl_renderer_add_renderable(FL_RENDERER(renderer),
+                             flutter::kFlutterImplicitViewId,
+                             FL_RENDERABLE(renderable));
   fl_renderer_wait_for_frame(FL_RENDERER(renderer), 1024, 1024);
   FlutterBackingStoreConfig config = {
       .struct_size = sizeof(FlutterBackingStoreConfig),
@@ -160,10 +180,12 @@
                                .backing_store = &backing_store,
                                .size = {.width = 1024, .height = 1024}};
   const FlutterLayer* layers[] = {&layer0};
-  fl_renderer_present_layers(FL_RENDERER(renderer), 0, layers, 1);
+  fl_renderer_present_layers(FL_RENDERER(renderer),
+                             flutter::kFlutterImplicitViewId, layers, 1);
   GdkRGBA background_color = {
       .red = 0.0, .green = 0.0, .blue = 0.0, .alpha = 1.0};
-  fl_renderer_render(FL_RENDERER(renderer), 0, 1024, 1024, &background_color);
+  fl_renderer_render(FL_RENDERER(renderer), flutter::kFlutterImplicitViewId,
+                     1024, 1024, &background_color);
 }
 
 TEST(FlRendererTest, NoBlitFramebuffer) {
@@ -176,8 +198,12 @@
   ON_CALL(epoxy, epoxy_is_desktop_gl).WillByDefault(::testing::Return(true));
   EXPECT_CALL(epoxy, epoxy_gl_version).WillRepeatedly(::testing::Return(20));
 
+  g_autoptr(FlMockRenderable) renderable = fl_mock_renderable_new();
   g_autoptr(FlMockRenderer) renderer = fl_mock_renderer_new();
   fl_renderer_setup(FL_RENDERER(renderer));
+  fl_renderer_add_renderable(FL_RENDERER(renderer),
+                             flutter::kFlutterImplicitViewId,
+                             FL_RENDERABLE(renderable));
   fl_renderer_wait_for_frame(FL_RENDERER(renderer), 1024, 1024);
   FlutterBackingStoreConfig config = {
       .struct_size = sizeof(FlutterBackingStoreConfig),
@@ -190,10 +216,12 @@
                                .backing_store = &backing_store,
                                .size = {.width = 1024, .height = 1024}};
   const FlutterLayer* layers[] = {&layer0};
-  fl_renderer_present_layers(FL_RENDERER(renderer), 0, layers, 1);
+  fl_renderer_present_layers(FL_RENDERER(renderer),
+                             flutter::kFlutterImplicitViewId, layers, 1);
   GdkRGBA background_color = {
       .red = 0.0, .green = 0.0, .blue = 0.0, .alpha = 1.0};
-  fl_renderer_render(FL_RENDERER(renderer), 0, 1024, 1024, &background_color);
+  fl_renderer_render(FL_RENDERER(renderer), flutter::kFlutterImplicitViewId,
+                     1024, 1024, &background_color);
 }
 
 TEST(FlRendererTest, BlitFramebufferNvidia) {
@@ -207,8 +235,12 @@
   ON_CALL(epoxy, epoxy_is_desktop_gl).WillByDefault(::testing::Return(true));
   EXPECT_CALL(epoxy, epoxy_gl_version).WillRepeatedly(::testing::Return(30));
 
+  g_autoptr(FlMockRenderable) renderable = fl_mock_renderable_new();
   g_autoptr(FlMockRenderer) renderer = fl_mock_renderer_new();
   fl_renderer_setup(FL_RENDERER(renderer));
+  fl_renderer_add_renderable(FL_RENDERER(renderer),
+                             flutter::kFlutterImplicitViewId,
+                             FL_RENDERABLE(renderable));
   fl_renderer_wait_for_frame(FL_RENDERER(renderer), 1024, 1024);
   FlutterBackingStoreConfig config = {
       .struct_size = sizeof(FlutterBackingStoreConfig),
@@ -221,8 +253,56 @@
                                .backing_store = &backing_store,
                                .size = {.width = 1024, .height = 1024}};
   const FlutterLayer* layers[] = {&layer0};
-  fl_renderer_present_layers(FL_RENDERER(renderer), 0, layers, 1);
+  fl_renderer_present_layers(FL_RENDERER(renderer),
+                             flutter::kFlutterImplicitViewId, layers, 1);
   GdkRGBA background_color = {
       .red = 0.0, .green = 0.0, .blue = 0.0, .alpha = 1.0};
-  fl_renderer_render(FL_RENDERER(renderer), 0, 1024, 1024, &background_color);
+  fl_renderer_render(FL_RENDERER(renderer), flutter::kFlutterImplicitViewId,
+                     1024, 1024, &background_color);
+}
+
+TEST(FlRendererTest, MultiView) {
+  ::testing::NiceMock<flutter::testing::MockEpoxy> epoxy;
+
+  // OpenGL 3.0
+  ON_CALL(epoxy, glGetString(GL_VENDOR))
+      .WillByDefault(
+          ::testing::Return(reinterpret_cast<const GLubyte*>("Intel")));
+  ON_CALL(epoxy, epoxy_is_desktop_gl).WillByDefault(::testing::Return(true));
+  EXPECT_CALL(epoxy, epoxy_gl_version).WillRepeatedly(::testing::Return(30));
+
+  g_autoptr(FlMockRenderable) renderable = fl_mock_renderable_new();
+  g_autoptr(FlMockRenderable) secondary_renderable = fl_mock_renderable_new();
+
+  g_autoptr(FlMockRenderer) renderer = fl_mock_renderer_new();
+  fl_renderer_setup(FL_RENDERER(renderer));
+  fl_renderer_add_renderable(FL_RENDERER(renderer),
+                             flutter::kFlutterImplicitViewId,
+                             FL_RENDERABLE(renderable));
+  fl_renderer_add_renderable(FL_RENDERER(renderer), 1,
+                             FL_RENDERABLE(secondary_renderable));
+  fl_renderer_wait_for_frame(FL_RENDERER(renderer), 1024, 1024);
+
+  EXPECT_EQ(fl_mock_renderable_get_redraw_count(renderable),
+            static_cast<size_t>(0));
+  EXPECT_EQ(fl_mock_renderable_get_redraw_count(secondary_renderable),
+            static_cast<size_t>(0));
+
+  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), 1, layers, 1);
+
+  EXPECT_EQ(fl_mock_renderable_get_redraw_count(renderable),
+            static_cast<size_t>(0));
+  EXPECT_EQ(fl_mock_renderable_get_redraw_count(secondary_renderable),
+            static_cast<size_t>(1));
 }
diff --git a/shell/platform/linux/fl_view.cc b/shell/platform/linux/fl_view.cc
index 5a68647..899abd0 100644
--- a/shell/platform/linux/fl_view.cc
+++ b/shell/platform/linux/fl_view.cc
@@ -335,6 +335,12 @@
   }
 }
 
+// Implements FlRenderable::make_current
+static void fl_view_make_current(FlRenderable* renderable) {
+  FlView* self = FL_VIEW(renderable);
+  gtk_gl_area_make_current(self->gl_area);
+}
+
 // Implements FlPluginRegistry::get_registrar_for_plugin.
 static FlPluginRegistrar* fl_view_get_registrar_for_plugin(
     FlPluginRegistry* registry,
@@ -348,6 +354,7 @@
 
 static void fl_renderable_iface_init(FlRenderableInterface* iface) {
   iface->redraw = fl_view_redraw;
+  iface->make_current = fl_view_make_current;
 }
 
 static void fl_view_plugin_registry_iface_init(
diff --git a/shell/platform/linux/testing/mock_renderer.cc b/shell/platform/linux/testing/mock_renderer.cc
index 1106554..247537a 100644
--- a/shell/platform/linux/testing/mock_renderer.cc
+++ b/shell/platform/linux/testing/mock_renderer.cc
@@ -11,6 +11,7 @@
 
 struct _FlMockRenderable {
   GObject parent_instance;
+  size_t redraw_count;
 };
 
 G_DEFINE_TYPE(FlMockRenderer, fl_mock_renderer, fl_renderer_get_type())
@@ -52,10 +53,16 @@
 
 static void fl_mock_renderer_init(FlMockRenderer* self) {}
 
-static void mock_renderable_redraw(FlRenderable* renderable) {}
+static void mock_renderable_redraw(FlRenderable* renderable) {
+  FlMockRenderable* self = FL_MOCK_RENDERABLE(renderable);
+  self->redraw_count++;
+}
+
+static void mock_renderable_make_current(FlRenderable* renderable) {}
 
 static void mock_renderable_iface_init(FlRenderableInterface* iface) {
   iface->redraw = mock_renderable_redraw;
+  iface->make_current = mock_renderable_make_current;
 }
 
 static void fl_mock_renderable_class_init(FlMockRenderableClass* klass) {}
@@ -76,3 +83,8 @@
   return FL_MOCK_RENDERABLE(
       g_object_new(fl_mock_renderable_get_type(), nullptr));
 }
+
+size_t fl_mock_renderable_get_redraw_count(FlMockRenderable* self) {
+  g_return_val_if_fail(FL_IS_MOCK_RENDERABLE(self), FALSE);
+  return self->redraw_count;
+}
diff --git a/shell/platform/linux/testing/mock_renderer.h b/shell/platform/linux/testing/mock_renderer.h
index 0838708..9a2207e 100644
--- a/shell/platform/linux/testing/mock_renderer.h
+++ b/shell/platform/linux/testing/mock_renderer.h
@@ -29,6 +29,8 @@
 
 FlMockRenderable* fl_mock_renderable_new();
 
+size_t fl_mock_renderable_get_redraw_count(FlMockRenderable* renderable);
+
 G_END_DECLS
 
 #endif  // FLUTTER_SHELL_PLATFORM_LINUX_TESTING_MOCK_RENDERER_H_