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_